├── .gitignore ├── src ├── sass │ ├── _variables.scss │ ├── _badge.scss │ ├── _type.scss │ ├── _form.scss │ ├── _utilities.scss │ ├── _modal.scss │ ├── index.scss │ └── _list.scss ├── models │ ├── EntryTypes.ts │ ├── Output.ts │ ├── Parameter.ts │ ├── ArmTemplate.ts │ ├── Resources │ │ ├── ResourceDependency.ts │ │ ├── ResourceManager.ts │ │ ├── ApplicationInsights.ts │ │ ├── StorageAccount.ts │ │ ├── StorageAccountBlobService.ts │ │ └── StorageAccountBlobContainer.ts │ └── Resource.ts ├── index.tsx └── components │ ├── Badge.tsx │ ├── TemplateViewer.tsx │ ├── FileLoader.tsx │ ├── ScriptHelper │ ├── ResourceGroupScript.tsx │ ├── ParametersVariablesScript.tsx │ ├── ConcatScript.tsx │ └── ScriptHelper.tsx │ ├── Modal.tsx │ ├── WorkingWindow │ ├── Resources │ │ ├── StorageAccountBlobServiceForm.tsx │ │ ├── StorageAccountBlobContainerForm.tsx │ │ ├── ApplicationInsightsForm.tsx │ │ ├── ResourceTypeForm.tsx │ │ └── StorageAccountForm.tsx │ ├── VariableForm.tsx │ ├── ResourceForm.tsx │ └── ParameterForm.tsx │ ├── Inputs │ ├── Select.tsx │ ├── ListInput.tsx │ ├── ConditionalInput.tsx │ ├── DependantResourceInput.tsx │ └── ResourceInput.tsx │ ├── WorkingWindow.tsx │ ├── Main.tsx │ └── Menu.tsx ├── dist ├── images │ ├── favicon_nodes.png │ └── favicon_nodes_apple.png └── react.production.min.js ├── tsconfig.json ├── gulpfile.js ├── index.html ├── package.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ -------------------------------------------------------------------------------- /src/sass/_variables.scss: -------------------------------------------------------------------------------- 1 | $primary: #D43A72; -------------------------------------------------------------------------------- /src/sass/_badge.scss: -------------------------------------------------------------------------------- 1 | .badge { 2 | padding-top: 0.4em; 3 | } -------------------------------------------------------------------------------- /src/sass/_type.scss: -------------------------------------------------------------------------------- 1 | .h3, h3 { 2 | margin-top: 1em; 3 | } -------------------------------------------------------------------------------- /src/sass/_form.scss: -------------------------------------------------------------------------------- 1 | .submit-group { 2 | margin-top: 1.5em; 3 | } -------------------------------------------------------------------------------- /src/sass/_utilities.scss: -------------------------------------------------------------------------------- 1 | .hidden { 2 | display: none !important; 3 | } -------------------------------------------------------------------------------- /dist/images/favicon_nodes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/arm-template-generator-typescript/master/dist/images/favicon_nodes.png -------------------------------------------------------------------------------- /dist/images/favicon_nodes_apple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/arm-template-generator-typescript/master/dist/images/favicon_nodes_apple.png -------------------------------------------------------------------------------- /src/models/EntryTypes.ts: -------------------------------------------------------------------------------- 1 | export enum EntryTypes { 2 | Parameter, 3 | Variable, 4 | Resource, 5 | Output 6 | } 7 | 8 | export default EntryTypes; -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | import Main from "./components/Main"; 4 | 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById("main") 9 | ); -------------------------------------------------------------------------------- /src/sass/_modal.scss: -------------------------------------------------------------------------------- 1 | .modal { 2 | &.show { 3 | display: block; 4 | background-color: rgba(100, 100, 100, 0.5); 5 | } 6 | 7 | &-dialog { 8 | width: 90%; 9 | max-width: 1000px; 10 | } 11 | } -------------------------------------------------------------------------------- /src/sass/index.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | @import '../../node_modules/bootstrap/scss/bootstrap.scss'; 3 | 4 | @import 'type'; 5 | @import 'utilities'; 6 | 7 | @import 'badge'; 8 | @import 'form'; 9 | @import 'list'; 10 | @import 'modal'; 11 | -------------------------------------------------------------------------------- /src/models/Output.ts: -------------------------------------------------------------------------------- 1 | export class Output { 2 | condition: string; 3 | type: string; 4 | value: string | string[] | number | number[] | object | object[] | boolean; 5 | 6 | static allowedTypes: string[] = ["bool", "string", "securestring", "int", "object", "secureObject", "array"]; 7 | } 8 | 9 | export default Output; -------------------------------------------------------------------------------- /src/sass/_list.scss: -------------------------------------------------------------------------------- 1 | .list-group { 2 | &-item { 3 | &.active { 4 | a { 5 | color: $white; 6 | } 7 | } 8 | 9 | a { 10 | + a { 11 | padding-left: 30px; 12 | } 13 | } 14 | } 15 | } 16 | 17 | .sub-item { 18 | padding-left: 45px; 19 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "sourceMap": true, 5 | "noImplicitAny": true, 6 | "noImplicitReturns": true, 7 | "noImplicitThis": true, 8 | "module": "commonjs", 9 | "target": "es6", 10 | "jsx": "react", 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true 13 | }, 14 | "exclude": [ 15 | "node_modules" 16 | ] 17 | } -------------------------------------------------------------------------------- /src/components/Badge.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | import React = require("react"); 3 | 4 | class BadgeProps { 5 | value: string; 6 | } 7 | 8 | export class Badge extends Component { 9 | constructor(props: BadgeProps) { 10 | super(props); 11 | } 12 | 13 | render(): JSX.Element { 14 | return ({this.props.value}) 15 | } 16 | } 17 | 18 | export default Badge; -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var sass = require('gulp-sass'); 3 | var sourcemaps = require('gulp-sourcemaps'); 4 | var cleanCss = require('gulp-clean-css'); 5 | 6 | sass.compiler = require('node-sass'); 7 | 8 | gulp.task('sass', function() { 9 | return gulp.src('./src/sass/index.scss') 10 | .pipe(sourcemaps.init()) 11 | .pipe(sass()) 12 | .pipe(cleanCss()) 13 | .pipe(sourcemaps.write()) 14 | .pipe(gulp.dest('./dist/css')); 15 | }); -------------------------------------------------------------------------------- /src/models/Parameter.ts: -------------------------------------------------------------------------------- 1 | export class Parameter { 2 | type: string; 3 | defaultValue: boolean | string | number; 4 | minLength: number; 5 | maxLength: number; 6 | minValue: number; 7 | maxValue: number; 8 | allowedValues: string[] | number[]; 9 | metadata: ParameterMetadata; 10 | 11 | static allowedTypes: string[] = ["bool", "string", "securestring", "int", "object", "secureObject", "array"]; 12 | } 13 | 14 | export class ParameterMetadata { 15 | description: string; 16 | } 17 | 18 | export default Parameter; -------------------------------------------------------------------------------- /src/models/ArmTemplate.ts: -------------------------------------------------------------------------------- 1 | import { Parameter } from "./Parameter"; 2 | import Resource from "./Resource"; 3 | import Output from "./Output"; 4 | 5 | export class ArmTemplate { 6 | $schema: string = "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#"; 7 | contentVersion: string = "1.0.0.0"; 8 | parameters: { [index: string]: Parameter }; 9 | variables: { [index: string]: string | object | object[] }; 10 | resources: Resource[]; 11 | outputs: { [index: string]: Output }; 12 | 13 | constructor() { 14 | this.parameters = {}; 15 | this.variables = {}; 16 | this.resources = []; 17 | this.outputs = {}; 18 | } 19 | } 20 | 21 | export default ArmTemplate; -------------------------------------------------------------------------------- /src/models/Resources/ResourceDependency.ts: -------------------------------------------------------------------------------- 1 | import Resource from "../Resource"; 2 | 3 | export class ResourceDependency { 4 | displayName: string; 5 | type: string; 6 | // Required resource types 7 | required: ResourceDependency[]; 8 | // Resource type to resource id mapping 9 | existingResources: { [type: string]: Resource }; 10 | // Resource type to name mapping 11 | newResources: { [type: string]: string }; 12 | 13 | constructor(displayName: string, type: string, requiredTypes: ResourceDependency[]) { 14 | this.displayName = displayName; 15 | this.type = type; 16 | this.required = requiredTypes; 17 | this.existingResources = {}; 18 | this.newResources = {}; 19 | } 20 | } 21 | 22 | export default ResourceDependency; -------------------------------------------------------------------------------- /src/components/TemplateViewer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Component } from 'react' 3 | import ArmTemplate from "../models/ArmTemplate"; 4 | 5 | interface TemplateViewerProps { 6 | template: ArmTemplate; 7 | } 8 | 9 | export class TemplateViewer extends Component { 10 | constructor(props: TemplateViewerProps) { 11 | super(props); 12 | } 13 | 14 | render(): JSX.Element { 15 | const json = JSON.stringify(this.props.template, (key, value) => { if(value !== null && key !== "simpleName" && !key.startsWith("requiredService")) return value;}, 2); 16 | const style = { 17 | height: "500px" 18 | }; 19 | 20 | return ( 21 | Template viewer 22 | 23 | 24 | ) 25 | } 26 | } 27 | 28 | export default TemplateViewer; -------------------------------------------------------------------------------- /src/models/Resources/ResourceManager.ts: -------------------------------------------------------------------------------- 1 | import Resource from "../Resource"; 2 | import StorageAccount from "./StorageAccount"; 3 | import StorageAccountBlobService from "./StorageAccountBlobService"; 4 | import StorageAccountBlobContainer from "./StorageAccountBlobContainer"; 5 | 6 | export class ResourceManager { 7 | static getSpecificResource(resource: Resource): Resource { 8 | switch(resource.type) { 9 | case StorageAccount.resourceType: 10 | return Object.assign(new StorageAccount(), resource); 11 | case StorageAccountBlobService.resourceType: 12 | return Object.assign(new StorageAccountBlobService(), resource); 13 | case StorageAccountBlobContainer.resourceType: 14 | return Object.assign(new StorageAccountBlobContainer(), resource); 15 | default: 16 | throw new Error("Unknown resource type: " + resource.type); 17 | } 18 | } 19 | } 20 | 21 | export default ResourceManager; -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Nodes - ARM Template Generator 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arm-template-generator", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "gulp sass && npx webpack" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/nodes-projects/nodes-arm-template-generator-react.git" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "bugs": { 18 | "url": "https://github.com/nodes-projects/nodes-arm-template-generator-react/issues" 19 | }, 20 | "homepage": "https://github.com/nodes-projects/nodes-arm-template-generator-react#readme", 21 | "devDependencies": { 22 | "@types/bootstrap": "^4.3.1", 23 | "@types/react": "^16.9.17", 24 | "@types/react-dom": "^16.9.4", 25 | "gulp": "^4.0.2", 26 | "gulp-clean-css": "^4.2.0", 27 | "gulp-sass": "^4.0.2", 28 | "gulp-sourcemaps": "^2.6.5", 29 | "node-sass": "^4.13.0", 30 | "source-map-loader": "^0.2.4", 31 | "ts-loader": "^6.2.1", 32 | "typescript": "^3.7.4", 33 | "webpack": "^4.41.5", 34 | "webpack-cli": "^3.3.10" 35 | }, 36 | "dependencies": { 37 | "bootstrap": "^4.4.1", 38 | "react": "^16.12.0", 39 | "react-dom": "^16.12.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/FileLoader.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Component, Fragment, ChangeEvent } from "react"; 3 | 4 | interface FileLoaderProps { 5 | text: string; 6 | onFileRead: (content: string) => void; 7 | } 8 | 9 | export class FileLoader extends Component { 10 | constructor(props: FileLoaderProps) { 11 | super(props); 12 | 13 | this.clickFileLoader = this.clickFileLoader.bind(this); 14 | this.loadFile = this.loadFile.bind(this); 15 | } 16 | 17 | private fileInput: HTMLInputElement; 18 | 19 | clickFileLoader(): void { 20 | this.fileInput.click(); 21 | } 22 | 23 | loadFile(event: ChangeEvent): void { 24 | const reader = new FileReader(); 25 | reader.onload = () => { 26 | this.props.onFileRead(String(reader.result)); 27 | } 28 | 29 | reader.readAsText(event.currentTarget.files[0]); 30 | } 31 | 32 | render(): JSX.Element { 33 | return 34 | this.fileInput = fileInput} type="file" className="hidden" onChange={this.loadFile} accept=".json" /> 35 | {this.props.text} 36 | 37 | } 38 | } 39 | 40 | export default FileLoader; -------------------------------------------------------------------------------- /src/components/ScriptHelper/ResourceGroupScript.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | import React = require("react"); 3 | import Select from "../Inputs/Select"; 4 | 5 | interface ResourceGroupScriptProps { 6 | onChange: (value: string) => void; 7 | } 8 | 9 | class ResourceGroupScriptState { 10 | value: string; 11 | } 12 | 13 | export class ResourceGroupScript extends Component { 14 | constructor(props: ResourceGroupScriptProps) { 15 | super(props); 16 | 17 | this.onChange = this.onChange.bind(this); 18 | 19 | let state = new ResourceGroupScriptState(); 20 | state.value = ""; 21 | 22 | this.state = state; 23 | } 24 | 25 | onChange(option: string): void { 26 | if(option.length > 0) { 27 | this.props.onChange("resourceGroup()." + option) 28 | } else { 29 | this.props.onChange(""); 30 | } 31 | 32 | this.setState({ 33 | value: option 34 | }); 35 | } 36 | 37 | render(): JSX.Element { 38 | const options = ["id", "name", "type", "location", "managedBy"]; 39 | 40 | return 41 | 42 | 43 | } 44 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // mode: "development", 3 | mode: "production", 4 | 5 | // Enable sourcemaps for debugging webpack's output. 6 | devtool: "source-map", 7 | 8 | resolve: { 9 | // Add '.ts' and '.tsx' as resolvable extensions. 10 | extensions: [".ts", ".tsx"] 11 | }, 12 | 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.ts(x?)$/, 17 | exclude: /node_modules/, 18 | use: [ 19 | { 20 | loader: "ts-loader" 21 | } 22 | ] 23 | }, 24 | // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'. 25 | { 26 | enforce: "pre", 27 | test: /\.js$/, 28 | loader: "source-map-loader" 29 | } 30 | ] 31 | }, 32 | 33 | // When importing a module whose path matches one of the following, just 34 | // assume a corresponding global variable exists and use that instead. 35 | // This is important because it allows us to avoid bundling all of our 36 | // dependencies, which allows browsers to cache those libraries between builds. 37 | externals: { 38 | "react": "React", 39 | "react-dom": "ReactDOM" 40 | } 41 | }; -------------------------------------------------------------------------------- /src/models/Resources/ApplicationInsights.ts: -------------------------------------------------------------------------------- 1 | import Resource from "../Resource"; 2 | 3 | export class ApplicationInsights extends Resource { 4 | static resourceType: string = "Microsoft.Insights/components"; 5 | static displayName: string = "Application Insights"; 6 | 7 | kind: string; 8 | properties: ApplicationInsightsProperties; 9 | 10 | static allowedKinds: string[] = ["web", "ios", "other", "store", "java", "phone"]; 11 | 12 | constructor() { 13 | super(); 14 | 15 | this.type = ApplicationInsights.resourceType; 16 | this.apiVersion = "2015-05-01"; 17 | } 18 | 19 | getResourceId(): string { 20 | return this.getResourceIdString(this.getNameForConcat(this.name)); 21 | } 22 | 23 | static getDefault(name: string): Resource[] { 24 | let insights = new ApplicationInsights(); 25 | 26 | insights.location = Resource.allowedLocations[0]; 27 | insights.name = name; 28 | insights.kind = ApplicationInsights.allowedKinds[0]; 29 | insights.properties = new ApplicationInsightsProperties(); 30 | insights.properties.Application_Type = ApplicationInsightsProperties.allowedApplicationTypes[0]; 31 | 32 | return [insights]; 33 | } 34 | } 35 | 36 | export class ApplicationInsightsProperties { 37 | Application_Type: string; 38 | 39 | static allowedApplicationTypes: string[] = ["web", "other"] 40 | } 41 | 42 | export default ApplicationInsights; -------------------------------------------------------------------------------- /src/components/Modal.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | import React = require("react"); 3 | 4 | interface ModalProps { 5 | show: boolean; 6 | closeModal: () => void; 7 | headline: string; 8 | } 9 | 10 | export class Modal extends Component { 11 | render(): JSX.Element { 12 | let modalClass = "modal fade"; 13 | if(this.props.show) { 14 | modalClass += " show"; 15 | } 16 | 17 | return 18 | 19 | 20 | 21 | {this.props.headline} 22 | 23 | × 24 | 25 | 26 | 27 | 28 | {this.props.children} 29 | 30 | 31 | 32 | Close 33 | 34 | 35 | 36 | 37 | } 38 | } 39 | 40 | export default Modal; -------------------------------------------------------------------------------- /src/components/WorkingWindow/Resources/StorageAccountBlobServiceForm.tsx: -------------------------------------------------------------------------------- 1 | import { ResourceTypeForm, ResourceTypeFormState, ResourceTypeFormProps } from "./ResourceTypeForm"; 2 | import StorageAccountBlobService from "../../../models/Resources/StorageAccountBlobService"; 3 | import ResourceDependency from "../../../models/Resources/ResourceDependency"; 4 | import Parameter from "../../../models/Parameter"; 5 | 6 | class StorageAccountBlobServiceState extends ResourceTypeFormState { 7 | 8 | } 9 | 10 | export class StorageAccountBlobServiceForm extends ResourceTypeForm { 11 | protected getNewResource(): StorageAccountBlobService { 12 | return new StorageAccountBlobService(); 13 | } 14 | 15 | getDependencies(): ResourceDependency { 16 | return StorageAccountBlobService.getResourceDependencyModel(); 17 | } 18 | 19 | protected getNewState(): StorageAccountBlobServiceState { 20 | return new StorageAccountBlobServiceState(); 21 | } 22 | 23 | constructor(props: ResourceTypeFormProps) { 24 | super(props); 25 | 26 | this.state = this.getBaseState(props); 27 | } 28 | 29 | getSpecificMarkup(): JSX.Element { 30 | return null; 31 | } 32 | 33 | protected setSpecificInformation(_resource: StorageAccountBlobService): void { 34 | return; 35 | } 36 | 37 | protected getSpecificNewParameters(): { [index: string]: Parameter; } { 38 | return {}; 39 | } 40 | } 41 | 42 | export default StorageAccountBlobServiceForm; -------------------------------------------------------------------------------- /src/components/ScriptHelper/ParametersVariablesScript.tsx: -------------------------------------------------------------------------------- 1 | import { Component, Fragment } from "react"; 2 | import React = require("react"); 3 | import Parameter from "../../models/Parameter"; 4 | import Select from "../Inputs/Select"; 5 | 6 | export enum ParametersVariablesScriptType { 7 | Parameters, 8 | Variables 9 | } 10 | 11 | interface ParametersVariablesScriptProps { 12 | parameters?: { [index: string]: Parameter }; 13 | variables?: { [index: string]: string | object | object[] }; 14 | type: ParametersVariablesScriptType; 15 | onChange: (output: string) => void; 16 | } 17 | 18 | export class ParametersVariablesScript extends Component { 19 | constructor(props: ParametersVariablesScriptProps) { 20 | super(props); 21 | 22 | this.parameterChosen = this.parameterChosen.bind(this); 23 | } 24 | 25 | parameterChosen(parameter: string): void { 26 | const type: string = this.props.type === ParametersVariablesScriptType.Parameters 27 | ? "parameters" 28 | : "variables"; 29 | 30 | const output: string = type + "('" + parameter + "')"; 31 | this.props.onChange(output); 32 | } 33 | 34 | render(): JSX.Element { 35 | const keys: string[] = Object.keys(this.props.type === ParametersVariablesScriptType.Parameters ? this.props.parameters : this.props.variables); 36 | const label: string = this.props.type === ParametersVariablesScriptType.Parameters ? "Choose a parameter" : "Choose a variable"; 37 | 38 | return 39 | {label} 40 | 41 | 42 | } 43 | } 44 | 45 | export default ParametersVariablesScript; -------------------------------------------------------------------------------- /src/components/Inputs/Select.tsx: -------------------------------------------------------------------------------- 1 | import { Component, ChangeEvent } from "react"; 2 | import React = require("react"); 3 | 4 | interface SelectProps { 5 | values: string[]; 6 | onOptionSelect?: (option: string) => void; 7 | id?: string; 8 | value?: string; 9 | required?: boolean; 10 | hideEmpty?: boolean; 11 | } 12 | 13 | class SelectState { 14 | selectedValue: string; 15 | } 16 | 17 | export class Select extends Component { 18 | constructor(props: SelectProps) { 19 | super(props); 20 | 21 | this.onOptionSelected = this.onOptionSelected.bind(this); 22 | let state = new SelectState(); 23 | 24 | if(this.props.value) { 25 | state.selectedValue = this.props.value; 26 | } 27 | 28 | this.state = state; 29 | } 30 | 31 | componentDidUpdate(prevProps: SelectProps): void { 32 | if(prevProps.value === this.props.value) { 33 | return; 34 | } 35 | 36 | this.setState({ 37 | selectedValue: this.props.value 38 | }); 39 | } 40 | 41 | onOptionSelected (event: ChangeEvent): void { 42 | const value = event.currentTarget.value; 43 | 44 | this.setState({ 45 | selectedValue: value 46 | }); 47 | 48 | if(this.props.onOptionSelect) { 49 | this.props.onOptionSelect(value); 50 | } 51 | } 52 | 53 | render(): JSX.Element { 54 | return ( 55 | {!this.props.hideEmpty && } 56 | {this.props.values.map((value) => { 57 | return {value}; 58 | })}; 59 | ) 60 | } 61 | } 62 | 63 | export default Select; -------------------------------------------------------------------------------- /src/models/Resource.ts: -------------------------------------------------------------------------------- 1 | import ResourceDependency from "./Resources/ResourceDependency"; 2 | 3 | export abstract class Resource { 4 | condition: string; 5 | type: string; 6 | name: string; 7 | location: string; 8 | apiVersion: string; 9 | tags: ResourceTags; 10 | dependsOn: string[]; 11 | static resourceType: string = ""; 12 | static displayName: string = ""; 13 | 14 | constructor() { 15 | this.apiVersion = "2019-04-01"; 16 | } 17 | 18 | static needLocation(): boolean { 19 | return true; 20 | } 21 | 22 | getName(): string { 23 | return this.name; 24 | } 25 | 26 | set setName(name: string) { 27 | this.name = name; 28 | } 29 | 30 | protected getResourceIdString(...parts: string[]) { 31 | if(parts.length > 0) { 32 | return "[resourceId('" + this.type + "', " + parts.join(", ") + ")]"; 33 | } else { 34 | return ""; 35 | } 36 | } 37 | 38 | abstract getResourceId(): string; 39 | 40 | setDependantResources(_allResources: Resource[]): void { } 41 | 42 | protected getNameForConcat(name: string, doNotAddApostrophes?: boolean): string { 43 | let finalName: string; 44 | 45 | if(name.startsWith("[") && name.endsWith("]")){ 46 | finalName = name.substr(1, name.length - 2); 47 | } else if(!doNotAddApostrophes) { 48 | finalName = "'" + name + "'"; 49 | } else { 50 | finalName = name; 51 | } 52 | 53 | return finalName; 54 | } 55 | 56 | //Its model for itself 57 | static getResourceDependencyModel(): ResourceDependency { 58 | return new ResourceDependency(this.displayName, this.resourceType, this.getAllRequiredResources()); 59 | } 60 | 61 | //Is to be implemented by the extending classes 62 | static getDefault(_name: string, _resourceDependency: ResourceDependency): Resource[] { 63 | return []; 64 | } 65 | 66 | //All required resources' dependency model 67 | static getAllRequiredResources(): ResourceDependency[] { 68 | return []; 69 | } 70 | 71 | static allowedLocations: string[] = ["[resourceGroup().location]"]; 72 | 73 | //Nothing to do in the base case 74 | setDependencies(_dependency: ResourceDependency, _resources: Resource[]): void { 75 | this.dependsOn = []; 76 | }; 77 | } 78 | 79 | export enum ResourceType { 80 | None, 81 | ApplicationInsights, 82 | StorageAccount, 83 | StorageAccountBlobService, 84 | StorageAccountBlobContainer 85 | } 86 | 87 | export class ResourceTags { 88 | displayName: string; 89 | } 90 | 91 | export default Resource; -------------------------------------------------------------------------------- /src/components/ScriptHelper/ConcatScript.tsx: -------------------------------------------------------------------------------- 1 | import { Component, Fragment } from "react"; 2 | import ScriptHelper, { ScriptContextType } from "./ScriptHelper"; 3 | import ArmTemplate from "../../models/ArmTemplate"; 4 | import React = require("react"); 5 | 6 | interface ConcatScriptProps { 7 | template: ArmTemplate; 8 | context: ScriptContextType; 9 | onChange: (value: string) => void; 10 | } 11 | 12 | class ConcatScriptState { 13 | values: string[]; 14 | } 15 | 16 | export class ConcatScript extends Component { 17 | constructor(props: ConcatScriptProps) { 18 | super(props); 19 | 20 | let state = new ConcatScriptState(); 21 | state.values = ["", ""]; 22 | 23 | this.state = state; 24 | 25 | this.addPart = this.addPart.bind(this); 26 | this.onChange = this.onChange.bind(this); 27 | this.removePart = this.removePart.bind(this); 28 | } 29 | 30 | addPart(): void { 31 | let values = this.state.values; 32 | 33 | values.push(""); 34 | 35 | this.setState({ 36 | values: values 37 | }); 38 | 39 | this.props.onChange(this.getRenderedString(values)); 40 | } 41 | 42 | getRenderedString(values: string[]): string { 43 | return "concat(" + values.join(",") + ")"; 44 | } 45 | 46 | onChange(value: string, index: number): void { 47 | let values = this.state.values; 48 | 49 | values[index] = value; 50 | 51 | this.setState({ 52 | values: values 53 | }); 54 | 55 | this.props.onChange(this.getRenderedString(values)); 56 | } 57 | 58 | removePart(): void { 59 | let values = this.state.values; 60 | 61 | values.pop(); 62 | 63 | this.setState({ 64 | values: values 65 | }); 66 | 67 | this.props.onChange(this.getRenderedString(values)); 68 | } 69 | 70 | render(): JSX.Element { 71 | return 72 | {this.state.values.map((_value, index) => { 73 | return 74 | part {index + 1} 75 | { this.onChange(value, index) }}> 76 | 77 | })} 78 | 79 | 80 | Add 81 | {this.state.values.length > 2 && Remove} 82 | 83 | 84 | } 85 | } 86 | 87 | export default ConcatScript; -------------------------------------------------------------------------------- /src/models/Resources/StorageAccount.ts: -------------------------------------------------------------------------------- 1 | import Resource from "../Resource"; 2 | 3 | export class StorageAccount extends Resource { 4 | static resourceType: string = "Microsoft.Storage/storageAccounts"; 5 | static displayName: string = "Storage account"; 6 | kind: string; 7 | properties: StorageAccountProperties; 8 | sku: StorageAccountSku; 9 | 10 | static allowedKinds:string[] = ["Storage", "StorageV2", "BlobStorage", "FileStorage", "BlockBlobStorage"]; 11 | 12 | constructor() { 13 | super(); 14 | 15 | this.type = StorageAccount.resourceType; 16 | } 17 | 18 | getResourceId(): string { 19 | return this.getResourceIdString(this.getNameForConcat(this.name)); 20 | } 21 | 22 | static getDefault(name: string): Resource[] { 23 | let account = new StorageAccount(); 24 | 25 | account.name = name; 26 | account.location = Resource.allowedLocations[0]; 27 | account.kind = "StorageV2"; 28 | account.properties = new StorageAccountProperties(); 29 | account.properties.accessTier = "Cool"; 30 | account.properties.supportsHttpsTrafficOnly = true; 31 | account.properties.encryption = new StorageAccountEncryption(); 32 | account.properties.encryption.services = new StorageAccountEncryptionServices(); 33 | account.properties.encryption.services.blob = new StorageAccountEncryptionService(); 34 | account.properties.encryption.services.blob.enabled = true; 35 | account.properties.encryption.services.file = new StorageAccountEncryptionService(); 36 | account.properties.encryption.services.file.enabled = true; 37 | account.sku = new StorageAccountSku(); 38 | account.sku.name = StorageAccountSku.allowedNames[0]; 39 | account.sku.tier = StorageAccountSku.allowedTiers[0]; 40 | 41 | return [account]; 42 | } 43 | } 44 | 45 | export class StorageAccountProperties { 46 | supportsHttpsTrafficOnly: boolean | string; 47 | encryption: StorageAccountEncryption; 48 | accessTier: string; 49 | 50 | static allowedAccessTiers: string[] = ["Hot", "Cool"]; 51 | } 52 | 53 | export class StorageAccountEncryption { 54 | services: StorageAccountEncryptionServices; 55 | keySource: string = "Microsoft.Storage"; 56 | } 57 | 58 | export class StorageAccountEncryptionServices { 59 | file: StorageAccountEncryptionService; 60 | blob: StorageAccountEncryptionService; 61 | } 62 | 63 | export class StorageAccountEncryptionService { 64 | enabled: boolean | string; 65 | } 66 | 67 | export class StorageAccountSku { 68 | name: string; 69 | tier: string; 70 | 71 | static allowedNames: string[] = ["Standard_LRS", "Standard_ZRS", "Standard_GRS", "Standard_RAGRS", "Premium_LRS"]; 72 | static allowedTiers: string[] = ["Standard", "Premium"]; 73 | } 74 | 75 | export default StorageAccount; -------------------------------------------------------------------------------- /src/components/Inputs/ListInput.tsx: -------------------------------------------------------------------------------- 1 | import { Component, Fragment, ChangeEvent } from "react"; 2 | import React = require("react"); 3 | 4 | interface ListInputProps { 5 | initialValue: any[]; 6 | label: string; 7 | type: string; 8 | onChange: (value: any[]) => void; 9 | } 10 | 11 | class ListInputState { 12 | value: any[]; 13 | } 14 | 15 | export class ListInput extends Component { 16 | constructor(props: ListInputProps) { 17 | super(props); 18 | 19 | this.addValue = this.addValue.bind(this); 20 | this.removeValue = this.removeValue.bind(this); 21 | this.updateValue = this.updateValue.bind(this); 22 | 23 | let state = new ListInputState(); 24 | if(props.initialValue) { 25 | state.value = props.initialValue; 26 | } else { 27 | state.value = [""]; 28 | } 29 | 30 | this.state = state; 31 | } 32 | 33 | componentDidUpdate(prevProps: ListInputProps): void { 34 | if(this.props.type !== prevProps.type) { 35 | if((this.props.type === "number" && typeof(this.state.value[0]) === "number") 36 | || this.props.type === "text" && typeof(this.state.value[0]) === "string") { 37 | return; 38 | } 39 | 40 | this.setState({ 41 | value: [""] 42 | }); 43 | 44 | this.props.onChange([""]); 45 | } 46 | 47 | if(this.props.initialValue.length !== prevProps.initialValue.length || 48 | (this.props.initialValue.length !== 0 && this.props.initialValue[0] !== prevProps.initialValue[0])) { 49 | this.setState({ 50 | value: this.props.initialValue 51 | }); 52 | 53 | this.props.onChange(this.props.initialValue); 54 | } 55 | } 56 | 57 | addValue(): void { 58 | let values = this.state.value; 59 | values.push(""); 60 | 61 | this.setState({ 62 | value: values 63 | }); 64 | } 65 | 66 | removeValue(index: number): void { 67 | let values = this.state.value; 68 | values.splice(index, 1); 69 | 70 | this.setState({ 71 | value: values 72 | }); 73 | 74 | this.props.onChange(values); 75 | } 76 | 77 | updateValue(event: ChangeEvent, index: number): void { 78 | let values = this.state.value; 79 | values[index] = this.props.type === "number" ? Number(event.currentTarget.value) : event.currentTarget.value; 80 | 81 | this.setState({ 82 | value: values 83 | }); 84 | 85 | this.props.onChange(values); 86 | } 87 | 88 | render(): JSX.Element { 89 | return 90 | {this.props.label} 91 | {this.state.value.map((value, index) => { 92 | return 93 | this.updateValue(event, index)} /> 94 | 95 | this.removeValue(index)}>Remove 96 | 97 | 98 | })} 99 | 100 | Add new value 101 | 102 | 103 | } 104 | } 105 | 106 | export default ListInput; -------------------------------------------------------------------------------- /src/components/Inputs/ConditionalInput.tsx: -------------------------------------------------------------------------------- 1 | import { Component, Fragment, ChangeEvent } from "react"; 2 | import React = require("react"); 3 | 4 | interface ConditionalInputProps { 5 | onChange: (value: any) => void; 6 | type: string; 7 | initialValue: any; 8 | conditionalLabel: string; 9 | valueLabel: string; 10 | requiredWhenOpen: boolean; 11 | id: string; 12 | } 13 | 14 | class ConditionalInputState { 15 | showInput: boolean; 16 | value: any; 17 | } 18 | 19 | export class ConditionalInput extends Component { 20 | constructor(props: ConditionalInputProps) { 21 | super(props); 22 | 23 | this.onChange = this.onChange.bind(this); 24 | this.toggleVisibility = this.toggleVisibility.bind(this); 25 | 26 | let state = new ConditionalInputState(); 27 | state.showInput = !!props.initialValue; 28 | state.value = props.initialValue === null || props.initialValue === undefined ? "" : props.initialValue; 29 | 30 | this.state = state; 31 | } 32 | 33 | componentDidUpdate(prevProps: ConditionalInputProps): void { 34 | if(this.props.initialValue !== prevProps.initialValue) { 35 | this.setState({ 36 | value: this.props.initialValue, 37 | showInput: !!this.props.initialValue 38 | }); 39 | } 40 | } 41 | 42 | onChange(event: ChangeEvent): void { 43 | let value: any; 44 | 45 | if(this.props.type === "number") { 46 | value = Number(event.currentTarget.value); 47 | } else if(this.props.type === "bool") { 48 | value = event.currentTarget.checked; 49 | } else { 50 | value = event.currentTarget.value; 51 | } 52 | 53 | this.props.onChange(value); 54 | this.setState({ 55 | value: value 56 | }); 57 | } 58 | 59 | toggleVisibility(event: ChangeEvent): void { 60 | this.setState({ 61 | showInput: event.currentTarget.checked 62 | }); 63 | 64 | if(event.currentTarget.checked === false) { 65 | this.setState({ 66 | value: "" 67 | }); 68 | 69 | this.props.onChange(""); 70 | } 71 | } 72 | 73 | render(): JSX.Element { 74 | const conditionalId = this.props.id + "-conditional"; 75 | 76 | return 77 | 78 | 79 | {this.props.conditionalLabel} 80 | 81 | 82 | 83 | {this.state.showInput && this.props.type !== "bool" && 84 | {this.props.valueLabel} 85 | 86 | 87 | 88 | } 89 | 90 | {this.state.showInput && this.props.type === "bool" && 91 | 92 | 93 | {this.props.valueLabel} 94 | 95 | } 96 | 97 | } 98 | } 99 | 100 | export default ConditionalInput; -------------------------------------------------------------------------------- /src/components/WorkingWindow/Resources/StorageAccountBlobContainerForm.tsx: -------------------------------------------------------------------------------- 1 | import { ResourceTypeForm, ResourceTypeFormState, ResourceTypeFormProps } from "./ResourceTypeForm"; 2 | import StorageAccountBlobContainer, { StorageAccountBlobContainerProperties } from "../../../models/Resources/StorageAccountBlobContainer"; 3 | import Parameter from "../../../models/Parameter"; 4 | import ResourceDependency from "../../../models/Resources/ResourceDependency"; 5 | import { Fragment } from "react"; 6 | import React = require("react"); 7 | import ResourceInput from "../../Inputs/ResourceInput"; 8 | import Select from "../../Inputs/Select"; 9 | 10 | class StorageAccountBlobContainerFormState extends ResourceTypeFormState { 11 | access: string; 12 | accessParameterName: string; 13 | } 14 | 15 | export class StorageAccountBlobContainerForm extends ResourceTypeForm { 16 | constructor(props: ResourceTypeFormProps) { 17 | super(props); 18 | 19 | this.onAccessUpdated = this.onAccessUpdated.bind(this); 20 | this.onAccessParameterNameChanged = this.onAccessParameterNameChanged.bind(this); 21 | 22 | let state = this.getBaseState(props); 23 | state.access = ""; 24 | state.accessParameterName = ""; 25 | 26 | if(props.resource && props.resource.properties && props.resource.properties.publicAccess) { 27 | state.access = props.resource.properties.publicAccess; 28 | } 29 | 30 | this.state = state; 31 | } 32 | 33 | protected getNewState(): StorageAccountBlobContainerFormState { 34 | return new StorageAccountBlobContainerFormState(); 35 | } 36 | 37 | protected getNewResource(): StorageAccountBlobContainer { 38 | return new StorageAccountBlobContainer(); 39 | } 40 | 41 | getDependencies(): ResourceDependency { 42 | return StorageAccountBlobContainer.getResourceDependencyModel(); 43 | } 44 | 45 | onAccessUpdated(access: string): void { 46 | this.setState({ 47 | access: access 48 | }); 49 | } 50 | 51 | onAccessParameterNameChanged(name: string): void { 52 | this.setState({ 53 | accessParameterName: name 54 | }); 55 | } 56 | 57 | getSpecificMarkup(): JSX.Element { 58 | let parameters = Object.keys(this.props.template.parameters); 59 | let variables = Object.keys(this.props.template.variables); 60 | 61 | return 62 | Public access* 63 | 64 | 65 | 66 | 67 | } 68 | 69 | protected setSpecificInformation(resource: StorageAccountBlobContainer): void { 70 | if(this.state.access) { 71 | if(!resource.properties) { 72 | resource.properties = new StorageAccountBlobContainerProperties(); 73 | } 74 | 75 | resource.properties.publicAccess = this.state.access; 76 | } else { 77 | resource.properties = undefined; 78 | } 79 | } 80 | 81 | protected getSpecificNewParameters(): { [index: string]: Parameter; } { 82 | let parameters: { [index: string]: Parameter } = {}; 83 | 84 | this.createParameter(this.state.accessParameterName, this.state.access, "string", StorageAccountBlobContainerProperties.allowedPublicAccesses, parameters); 85 | 86 | return parameters; 87 | } 88 | } 89 | 90 | export default StorageAccountBlobContainerForm -------------------------------------------------------------------------------- /src/components/WorkingWindow/VariableForm.tsx: -------------------------------------------------------------------------------- 1 | import { Component, ChangeEvent } from "react"; 2 | import React = require("react"); 3 | import Select from "../Inputs/Select"; 4 | 5 | interface VariableFormProps { 6 | initialValue?: string | object | object[]; 7 | name?: string; 8 | onSubmit: (value: string | object | object[], name: string) => void; 9 | } 10 | 11 | class VariableFormState { 12 | name: string; 13 | type: string; 14 | value: string; 15 | } 16 | 17 | export class VariableForm extends Component { 18 | constructor(props: VariableFormProps) { 19 | super(props); 20 | 21 | this.setName = this.setName.bind(this); 22 | this.setType = this.setType.bind(this); 23 | this.setValue = this.setValue.bind(this); 24 | this.submitForm = this.submitForm.bind(this); 25 | 26 | this.state = this.initializeState(props); 27 | } 28 | 29 | initializeState(props: VariableFormProps): VariableFormState { 30 | let state = new VariableFormState(); 31 | if(props.initialValue) { 32 | state.value = JSON.stringify(props.initialValue); 33 | 34 | switch(state.value[0]) { 35 | case "[": 36 | state.type = "array"; 37 | break; 38 | case "{": 39 | state.type = "object"; 40 | break; 41 | default: 42 | state.type = "string"; 43 | break; 44 | } 45 | 46 | if(state.value[0] === "\"") { 47 | state.value = state.value.substr(1, state.value.length - 2); 48 | } 49 | } else { 50 | state.value = ""; 51 | state.type = "string"; 52 | } 53 | 54 | if(props.name) { 55 | state.name = props.name; 56 | } else { 57 | state.name = ""; 58 | } 59 | 60 | return state; 61 | } 62 | 63 | componentDidUpdate(prevProps: VariableFormProps): void { 64 | if(this.props.name === prevProps.name) { 65 | return; 66 | } 67 | 68 | this.setState(this.initializeState(this.props)); 69 | } 70 | 71 | setName(event: ChangeEvent): void { 72 | this.setState({ 73 | name: event.currentTarget.value 74 | }); 75 | } 76 | 77 | setType(type: string): void { 78 | this.setState({ 79 | type: type 80 | }); 81 | } 82 | 83 | setValue(event: ChangeEvent): void { 84 | this.setState({ 85 | value: event.currentTarget.value 86 | }); 87 | } 88 | 89 | submitForm(): void { 90 | if(this.state.type === "string") { 91 | this.props.onSubmit(this.state.value, this.state.name); 92 | } else { 93 | this.props.onSubmit(JSON.parse(this.state.value), this.state.name); 94 | } 95 | } 96 | 97 | render(): JSX.Element { 98 | const submitText = this.props.name ? "Save" : "Add"; 99 | 100 | const selectOptions = ["string", "object", "array"]; 101 | 102 | return 103 | Name ([a-Z][a-Z0-9]*) 104 | 105 | 106 | 107 | 108 | Type of variable 109 | 110 | 111 | 112 | 113 | Value 114 | 115 | 116 | 117 | 118 | 119 | {submitText} 120 | 121 | 122 | } 123 | } 124 | 125 | export default VariableForm; -------------------------------------------------------------------------------- /src/models/Resources/StorageAccountBlobService.ts: -------------------------------------------------------------------------------- 1 | import Resource from "../Resource"; 2 | import StorageAccount from "./StorageAccount"; 3 | import ResourceDependency from "./ResourceDependency"; 4 | 5 | export class StorageAccountBlobService extends Resource { 6 | static resourceType: string = "Microsoft.Storage/storageAccounts/blobServices"; 7 | static displayName: string = "Storage account blob service"; 8 | private requiredService: StorageAccount; 9 | private simpleName: string; 10 | 11 | constructor() { 12 | super(); 13 | 14 | this.type = StorageAccountBlobService.resourceType; 15 | } 16 | 17 | getResourceId(): string { 18 | return this.getResourceIdString(this.getNameForConcat(this.requiredService.getName()), this.getNameForConcat(this.getName())); 19 | } 20 | 21 | setDependantResources(allResources: Resource[]): void { 22 | if(!this.dependsOn || this.dependsOn.length <= 0) { 23 | return; 24 | } 25 | 26 | let accountDependency: string = this.dependsOn.find(d => d.includes("'" + StorageAccount.resourceType + "'")); 27 | 28 | if(accountDependency) { 29 | let accounts: StorageAccount[] = allResources.filter(r => r.type === StorageAccount.resourceType).map(r => r as StorageAccount); 30 | 31 | this.requiredService = accounts.find(a => a.getResourceId() === accountDependency); 32 | 33 | if(this.requiredService) { 34 | let name: string = this.simpleName; 35 | 36 | if(!name) { 37 | let splitName: string[] = this.name.split('/'); 38 | name = splitName[splitName.length - 1]; 39 | } 40 | 41 | this.setName = name; 42 | } 43 | } 44 | } 45 | 46 | static needLocation(): boolean { 47 | return false; 48 | } 49 | 50 | getName(): string { 51 | return this.simpleName; 52 | } 53 | 54 | set setName(name: string) { 55 | this.simpleName = name; 56 | 57 | this.name = this.requiredService ? this.getNameForConcat(this.requiredService.name, true) + "/" + this.simpleName : this.simpleName; 58 | } 59 | 60 | set requiredResources(storageAccount: StorageAccount) { 61 | this.requiredService = storageAccount; 62 | this.dependsOn = [storageAccount.getResourceId()]; 63 | 64 | //Update full name as it depends on the storage account 65 | this.setName = this.simpleName; 66 | } 67 | 68 | setDependencies(dependency: ResourceDependency, resources: Resource[]): void { 69 | let resourceId = Object.keys(dependency.existingResources).find(k => k === StorageAccount.resourceType); 70 | 71 | if(resourceId) { 72 | this.requiredService = dependency.existingResources[resourceId] as StorageAccount; 73 | this.setName = this.simpleName; 74 | this.dependsOn = [dependency.existingResources[resourceId].getResourceId()]; 75 | } else { 76 | let name = dependency.newResources[StorageAccount.resourceType]; 77 | this.requiredService = (resources.find(r => r.getName() === name) as StorageAccount); 78 | this.setName = this.simpleName; 79 | this.dependsOn = [this.requiredService.getResourceId()]; 80 | } 81 | } 82 | 83 | static getDefault(name: string, dependencyModel: ResourceDependency): Resource[] { 84 | let resources: Resource[] = []; 85 | let storageAccount: StorageAccount; 86 | 87 | Object.keys(dependencyModel.newResources).forEach(type => { 88 | const name: string = dependencyModel.newResources[type]; 89 | if(type === StorageAccount.resourceType) { 90 | resources.push.apply(resources, StorageAccount.getDefault(name)); 91 | 92 | storageAccount = resources.find(r => r.type === StorageAccount.resourceType) as StorageAccount; 93 | } 94 | }); 95 | 96 | Object.keys(dependencyModel.existingResources).forEach(type => { 97 | let resource = dependencyModel.existingResources[type]; 98 | if(resource.type === StorageAccount.resourceType) { 99 | storageAccount = resource as StorageAccount; 100 | } 101 | }) 102 | 103 | let service = new StorageAccountBlobService(); 104 | service.requiredResources = storageAccount; 105 | service.setName = name; 106 | service.dependsOn = [storageAccount.getResourceId()]; 107 | 108 | resources.push(service); 109 | 110 | return resources; 111 | } 112 | 113 | static getAllRequiredResources(): ResourceDependency[] { 114 | return [StorageAccount.getResourceDependencyModel()]; 115 | } 116 | } 117 | 118 | export default StorageAccountBlobService; -------------------------------------------------------------------------------- /src/models/Resources/StorageAccountBlobContainer.ts: -------------------------------------------------------------------------------- 1 | import Resource from "../Resource"; 2 | import StorageAccountBlobService from "./StorageAccountBlobService"; 3 | import ResourceDependency from "./ResourceDependency"; 4 | 5 | export class StorageAccountBlobContainer extends Resource { 6 | static resourceType = "Microsoft.Storage/storageAccounts/blobServices/containers"; 7 | private requiredService : StorageAccountBlobService; 8 | private simpleName: string; 9 | properties: StorageAccountBlobContainerProperties; 10 | 11 | constructor() { 12 | super(); 13 | 14 | this.type = StorageAccountBlobContainer.resourceType; 15 | } 16 | 17 | getResourceId(): string { 18 | return this.getResourceIdString(this.requiredService.getName(), "/", this.getName()); 19 | } 20 | 21 | static needLocation(): boolean { 22 | return false; 23 | } 24 | 25 | setDependantResources(allResources: Resource[]): void { 26 | if(!this.dependsOn || this.dependsOn.length <= 0) { 27 | return; 28 | } 29 | 30 | let accountDependency: string = this.dependsOn.find(d => d.includes("'" + StorageAccountBlobService.resourceType + "'")); 31 | 32 | if(accountDependency) { 33 | let services: StorageAccountBlobService[] = allResources.filter(r => r.type === StorageAccountBlobService.resourceType).map(r => r as StorageAccountBlobService); 34 | 35 | this.requiredService = services.find(a => a.getResourceId() === accountDependency); 36 | 37 | if(this.requiredService) { 38 | let name: string = this.simpleName; 39 | 40 | if(!name) { 41 | let splitName: string[] = this.name.split('/'); 42 | name = splitName[splitName.length - 1]; 43 | } 44 | 45 | this.setName = name; 46 | } 47 | } 48 | } 49 | 50 | getName(): string { 51 | return this.simpleName; 52 | } 53 | 54 | set setName(name: string) { 55 | this.simpleName = name; 56 | 57 | this.name = this.requiredService ? this.getNameForConcat(this.requiredService.name, true) + "/" + this.simpleName : this.simpleName; 58 | } 59 | 60 | set requiredResources(blobService: StorageAccountBlobService) { 61 | this.requiredService = blobService; 62 | this.dependsOn = [blobService.getResourceId()]; 63 | 64 | //Update full name as it depends on the storage account 65 | this.setName = this.simpleName; 66 | } 67 | 68 | setDependencies(dependency: ResourceDependency, resources: Resource[]): void { 69 | let resourceId = Object.keys(dependency.existingResources).find(k => k === StorageAccountBlobService.resourceType); 70 | 71 | if(resourceId) { 72 | this.requiredResources = dependency.existingResources[resourceId] as StorageAccountBlobService; 73 | this.setName = this.simpleName; 74 | this.dependsOn = [dependency.existingResources[resourceId].getResourceId()]; 75 | } else { 76 | let name = dependency.newResources[StorageAccountBlobService.resourceType]; 77 | let blobService = resources.find(r => r.getName() === name) as StorageAccountBlobService 78 | this.requiredResources = blobService; 79 | this.setName = this.simpleName; 80 | this.dependsOn = [blobService.getResourceId()]; 81 | } 82 | } 83 | 84 | static getDefault(name: string, dependencyModel: ResourceDependency): Resource[] { 85 | let resources: Resource[] = []; 86 | let blobService: StorageAccountBlobService; 87 | 88 | Object.keys(dependencyModel.newResources).forEach(type => { 89 | const name: string = dependencyModel.newResources[type]; 90 | if(type === StorageAccountBlobService.resourceType) { 91 | resources.push.apply(resources, StorageAccountBlobService.getDefault(name, dependencyModel.required.find(r => r.type === StorageAccountBlobService.resourceType))); 92 | 93 | blobService = resources.find(r => r.type === StorageAccountBlobService.resourceType) as StorageAccountBlobService; 94 | } 95 | }); 96 | 97 | Object.keys(dependencyModel.existingResources).forEach(type => { 98 | let resource = dependencyModel.existingResources[type]; 99 | if(resource.type === StorageAccountBlobService.resourceType) { 100 | blobService = resource as StorageAccountBlobService; 101 | } 102 | }); 103 | 104 | let service = new StorageAccountBlobContainer(); 105 | service.requiredResources = blobService; 106 | service.setName = name; 107 | service.dependsOn = [blobService.getResourceId()]; 108 | 109 | resources.push(service); 110 | 111 | return resources; 112 | } 113 | 114 | static getAllRequiredResources(): ResourceDependency[] { 115 | return [StorageAccountBlobService.getResourceDependencyModel()]; 116 | } 117 | } 118 | 119 | export class StorageAccountBlobContainerProperties { 120 | publicAccess: string; 121 | 122 | static allowedPublicAccesses: string[] = ["None", "Container", "Blob"]; 123 | } 124 | 125 | export default StorageAccountBlobContainer; -------------------------------------------------------------------------------- /src/components/Inputs/DependantResourceInput.tsx: -------------------------------------------------------------------------------- 1 | import { Component, Fragment } from "react"; 2 | import ResourceDependency from "../../models/Resources/ResourceDependency"; 3 | import React = require("react"); 4 | import Resource from "../../models/Resource"; 5 | import Select from "./Select"; 6 | 7 | interface DependantResourceInputProps { 8 | dependency: ResourceDependency; 9 | resources: Resource[]; 10 | currentResource?: Resource; 11 | onDependencyUpdated: (dependency: ResourceDependency) => void; 12 | headline?: string; 13 | } 14 | 15 | class DependantResourceInputState { 16 | dependency: ResourceDependency; 17 | createNew: boolean; 18 | newName: string; 19 | } 20 | 21 | export class DependantResourceInput extends Component { 22 | constructor(props: DependantResourceInputProps) { 23 | super(props); 24 | 25 | this.onNewNameChanged = this.onNewNameChanged.bind(this); 26 | this.onResourceSelected = this.onResourceSelected.bind(this); 27 | this.onSubRequiredUpdated = this.onSubRequiredUpdated.bind(this); 28 | 29 | let state = new DependantResourceInputState(); 30 | state.dependency = props.dependency; 31 | state.createNew = false; 32 | state.newName = ""; 33 | 34 | this.state = state; 35 | } 36 | 37 | onNewNameChanged(type: string, name: string): void { 38 | let dependency = this.state.dependency; 39 | dependency.newResources[type] = name; 40 | 41 | this.setState({ 42 | dependency: dependency, 43 | newName: name 44 | }); 45 | 46 | this.props.onDependencyUpdated(dependency); 47 | } 48 | 49 | onResourceSelected(type: string, name: string): void { 50 | let dependency = this.state.dependency; 51 | 52 | if(this.props.resources.filter(r => r.type === type).map(r => r.getName()).findIndex(n => n === name) < 0) { 53 | delete(dependency.existingResources[type]); 54 | 55 | this.setState({ 56 | createNew: true 57 | }); 58 | } else { 59 | delete(dependency.newResources[type]); 60 | 61 | this.setState({ 62 | createNew: false, 63 | newName: "" 64 | }); 65 | 66 | dependency.existingResources[type] = this.props.resources.find(r => r.getName() === name); 67 | } 68 | 69 | this.setState({ 70 | dependency: dependency 71 | }); 72 | 73 | this.props.onDependencyUpdated(dependency); 74 | } 75 | 76 | onSubRequiredUpdated(dependency: ResourceDependency): void { 77 | let localDependency = this.state.dependency; 78 | let index = localDependency.required.findIndex(r => r.type === dependency.type); 79 | 80 | localDependency.required[index] = dependency; 81 | 82 | this.setState({ 83 | dependency: localDependency 84 | }); 85 | 86 | this.props.onDependencyUpdated(localDependency); 87 | } 88 | 89 | render(): JSX.Element { 90 | if(this.props.dependency.required.length <= 0) { 91 | return null; 92 | } 93 | 94 | return 95 | {this.props.headline && {this.props.headline}} 96 | {this.props.dependency.required.map(req => { 97 | let availableResources = this.props.resources.filter(r => r.type === req.type); 98 | let values = availableResources.map(r => r.getName()); 99 | values.push("Create new"); 100 | 101 | let value: string = ""; 102 | if(this.props.currentResource && this.props.currentResource.dependsOn && this.props.currentResource.dependsOn.length > 0) { 103 | let dependentResource = this.props.resources.find(r => r.type === req.type && this.props.currentResource.dependsOn.findIndex(d => d === r.getResourceId()) >= 0); 104 | if(dependentResource) { 105 | value = dependentResource.getName(); 106 | } 107 | } 108 | 109 | return 110 | {req.displayName}* 111 | { this.onResourceSelected(req.type, option) }}> 112 | {this.state.createNew && 113 | Name of new resource* 114 | 115 | { this.onNewNameChanged(req.type, e.currentTarget.value)} } /> 116 | 117 | } 118 | 119 | {req.required.length > 0 && this.state.createNew && 120 | 121 | } 122 | 123 | })} 124 | 125 | } 126 | } 127 | 128 | export default DependantResourceInput; -------------------------------------------------------------------------------- /src/components/WorkingWindow/ResourceForm.tsx: -------------------------------------------------------------------------------- 1 | import { Component, Fragment } from "react"; 2 | import ArmTemplate from "../../models/ArmTemplate"; 3 | import Resource, { ResourceType } from "../../models/Resource"; 4 | import StorageAccount from "../../models/Resources/StorageAccount"; 5 | import React = require("react"); 6 | import Select from "../Inputs/Select"; 7 | import { StorageAccountForm } from "./Resources/StorageAccountForm"; 8 | import Parameter from "../../models/Parameter"; 9 | import StorageAccountBlobServiceForm from "./Resources/StorageAccountBlobServiceForm"; 10 | import StorageAccountBlobService from "../../models/Resources/StorageAccountBlobService"; 11 | import StorageAccountBlobContainer from "../../models/Resources/StorageAccountBlobContainer"; 12 | import StorageAccountBlobContainerForm from "./Resources/StorageAccountBlobContainerForm"; 13 | import ApplicationInsights from "../../models/Resources/ApplicationInsights"; 14 | import ApplicationInsightsForm from "./Resources/ApplicationInsightsForm"; 15 | 16 | interface ResourceFormProps { 17 | template: ArmTemplate; 18 | resource?: Resource; 19 | onSubmit: (resources: Resource[], parameters: { [index: string]: Parameter }) => ArmTemplate; 20 | } 21 | 22 | class ResourceFormState { 23 | type: ResourceType; 24 | } 25 | 26 | export class ResourceForm extends Component { 27 | constructor(props: ResourceFormProps) { 28 | super(props); 29 | 30 | this.typeSelected = this.typeSelected.bind(this); 31 | 32 | this.state = this.buildState(props); 33 | } 34 | 35 | buildState(props: ResourceFormProps): ResourceFormState { 36 | let state = new ResourceFormState(); 37 | state.type = ResourceType.None; 38 | 39 | if(props.resource) { 40 | switch(props.resource.type) { 41 | case ApplicationInsights.resourceType: 42 | state.type = ResourceType.ApplicationInsights; 43 | break; 44 | case StorageAccount.resourceType: 45 | state.type = ResourceType.StorageAccount; 46 | break; 47 | case StorageAccountBlobService.resourceType: 48 | state.type = ResourceType.StorageAccountBlobService; 49 | break; 50 | case StorageAccountBlobContainer.resourceType: 51 | state.type = ResourceType.StorageAccountBlobContainer; 52 | break; 53 | } 54 | } 55 | 56 | return state; 57 | } 58 | 59 | componentDidUpdate(prevProps: ResourceFormProps): void { 60 | let prevResource: string, curResource: string; 61 | 62 | prevResource = prevProps.resource && prevProps.resource.getName() ? prevProps.resource.getName() : ""; 63 | curResource = this.props.resource && this.props.resource.getName() ? this.props.resource.getName() : ""; 64 | 65 | if(prevResource === curResource) { 66 | return; 67 | } 68 | 69 | this.setState(this.buildState(this.props)); 70 | } 71 | 72 | typeSelected(option: string): void { 73 | if(option === "") { 74 | this.setState({ 75 | type: ResourceType.None 76 | }); 77 | } else { 78 | let key = option as keyof typeof ResourceType; 79 | 80 | this.setState({ 81 | type: ResourceType[key] 82 | }); 83 | } 84 | } 85 | 86 | render(): JSX.Element { 87 | const types: string[] = [ 88 | ResourceType[ResourceType.None], 89 | ResourceType[ResourceType.ApplicationInsights], 90 | ResourceType[ResourceType.StorageAccount], 91 | ResourceType[ResourceType.StorageAccountBlobService], 92 | ResourceType[ResourceType.StorageAccountBlobContainer] 93 | ]; 94 | 95 | let value: string = this.state.type ? ResourceType[this.state.type] : ResourceType[ResourceType.None]; 96 | 97 | return 98 | Type 99 | 100 | 101 | 102 | 103 | {this.state.type === ResourceType.ApplicationInsights && } 104 | {this.state.type === ResourceType.StorageAccount && } 105 | {this.state.type === ResourceType.StorageAccountBlobService && } 106 | {this.state.type === ResourceType.StorageAccountBlobContainer && } 107 | 108 | } 109 | } -------------------------------------------------------------------------------- /src/components/WorkingWindow/Resources/ApplicationInsightsForm.tsx: -------------------------------------------------------------------------------- 1 | import { ResourceTypeForm, ResourceTypeFormState, ResourceTypeFormProps } from "./ResourceTypeForm"; 2 | import ApplicationInsights, { ApplicationInsightsProperties } from "../../../models/Resources/ApplicationInsights"; 3 | import Parameter from "../../../models/Parameter"; 4 | import { Fragment } from "react"; 5 | import React = require("react"); 6 | import ResourceInput from "../../Inputs/ResourceInput"; 7 | import Select from "../../Inputs/Select"; 8 | import ResourceDependency from "../../../models/Resources/ResourceDependency"; 9 | 10 | class ApplicationInsightsFormState extends ResourceTypeFormState { 11 | kind: string; 12 | kindParameterName: string; 13 | applicationType: string; 14 | applicationTypeParameterName: string; 15 | } 16 | 17 | export class ApplicationInsightsForm extends ResourceTypeForm { 18 | constructor(props: ResourceTypeFormProps) { 19 | super(props); 20 | 21 | this.onKindUpdated = this.onKindUpdated.bind(this); 22 | this.onKindParameterNameUpdated = this.onKindParameterNameUpdated.bind(this); 23 | this.onApplicationTypeUpdated = this.onApplicationTypeUpdated.bind(this); 24 | this.onApplicationTypeParameterNameUpdated = this.onApplicationTypeParameterNameUpdated.bind(this); 25 | 26 | let state = this.getBaseState(props); 27 | 28 | state.kind = ""; 29 | state.kindParameterName = ""; 30 | state.applicationType = ""; 31 | state.applicationTypeParameterName = ""; 32 | 33 | if(props.resource) { 34 | if(props.resource.kind) { 35 | state.kind = props.resource.kind; 36 | } 37 | 38 | if(props.resource.properties && props.resource.properties.Application_Type) { 39 | state.applicationType = props.resource.properties.Application_Type; 40 | } 41 | } 42 | 43 | this.state = state; 44 | } 45 | 46 | protected getNewState(): ApplicationInsightsFormState { 47 | return new ApplicationInsightsFormState(); 48 | } 49 | 50 | protected getNewResource(): ApplicationInsights { 51 | return new ApplicationInsights(); 52 | } 53 | 54 | getDependencies(): ResourceDependency { 55 | return ApplicationInsights.getResourceDependencyModel(); 56 | } 57 | 58 | protected setSpecificInformation(resource: ApplicationInsights): void { 59 | resource.kind = this.state.kindParameterName 60 | ? this.getParameterString(this.state.kindParameterName) 61 | : this.state.kind; 62 | 63 | if(!resource.properties) { 64 | resource.properties = new ApplicationInsightsProperties(); 65 | } 66 | 67 | resource.properties.Application_Type = this.state.applicationTypeParameterName 68 | ? this.getParameterString(this.state.applicationTypeParameterName) 69 | : this.state.applicationType; 70 | } 71 | 72 | protected getSpecificNewParameters(): { [index: string]: Parameter; } { 73 | let parametersToCreate: { [index: string]: Parameter; } = {}; 74 | 75 | this.createParameter(this.state.kindParameterName, this.state.kind, "string", ApplicationInsights.allowedKinds, parametersToCreate); 76 | 77 | this.createParameter(this.state.applicationTypeParameterName, this.state.applicationType, "string", ApplicationInsightsProperties.allowedApplicationTypes, parametersToCreate); 78 | 79 | return parametersToCreate; 80 | } 81 | 82 | onKindUpdated(kind: string): void { 83 | this.setState({ 84 | kind: kind 85 | }); 86 | } 87 | 88 | onKindParameterNameUpdated(name: string): void { 89 | this.setState({ 90 | kindParameterName: name 91 | }); 92 | } 93 | 94 | onApplicationTypeUpdated(applicationType: string): void { 95 | this.setState({ 96 | applicationType: applicationType 97 | }); 98 | } 99 | 100 | onApplicationTypeParameterNameUpdated(name: string): void { 101 | this.setState({ 102 | applicationTypeParameterName: name 103 | }); 104 | } 105 | 106 | getSpecificMarkup(): JSX.Element { 107 | const parameters = Object.keys(this.props.template.parameters); 108 | const variables = Object.keys(this.props.template.variables); 109 | 110 | return 111 | Kind* 112 | 113 | 114 | 115 | 116 | Application Type* 117 | 118 | 119 | 120 | 121 | } 122 | } 123 | 124 | export default ApplicationInsightsForm; -------------------------------------------------------------------------------- /src/components/ScriptHelper/ScriptHelper.tsx: -------------------------------------------------------------------------------- 1 | import { Component, Fragment, RefObject } from "react"; 2 | import ArmTemplate from "../../models/ArmTemplate"; 3 | import React = require("react"); 4 | import Select from "../Inputs/Select"; 5 | import ParametersVariablesScript, { ParametersVariablesScriptType } from "./ParametersVariablesScript"; 6 | import ConcatScript from "./ConcatScript"; 7 | import { ResourceGroupScript } from "./ResourceGroupScript"; 8 | 9 | export enum ScriptContextType { 10 | Parameters, 11 | Variables, 12 | Resources, 13 | Outputs 14 | } 15 | 16 | enum Types { 17 | Concat, 18 | Variables, 19 | Parameters, 20 | Resources, 21 | ResourceGroup, 22 | Text, 23 | Custom, 24 | None 25 | } 26 | 27 | interface ScriptHelperProps { 28 | template: ArmTemplate; 29 | context: ScriptContextType; 30 | onChange?: (value: string) => void; 31 | topLevel: boolean; 32 | includeText?: boolean; 33 | } 34 | 35 | class ScriptHelperState { 36 | chosenType: Types; 37 | renderedValue: string; 38 | } 39 | 40 | export class ScriptHelper extends Component { 41 | constructor(props: ScriptHelperProps) { 42 | super(props); 43 | 44 | this.copyToClipboard = this.copyToClipboard.bind(this); 45 | this.onChange = this.onChange.bind(this); 46 | this.typeChosen = this.typeChosen.bind(this); 47 | 48 | this.renderedInput = React.createRef(); 49 | 50 | let state = new ScriptHelperState(); 51 | state.chosenType = Types.None; 52 | state.renderedValue = ""; 53 | 54 | this.state = state; 55 | } 56 | 57 | private renderedInput: RefObject; 58 | 59 | copyToClipboard(): void { 60 | this.renderedInput.current.focus(); 61 | this.renderedInput.current.select(); 62 | document.execCommand('copy'); 63 | this.renderedInput.current.blur(); 64 | } 65 | 66 | onChange(value: string): void { 67 | let renderedValue = this.props.topLevel 68 | ? "[" + value + "]" 69 | : value; 70 | 71 | if(this.props.onChange) { 72 | this.props.onChange(renderedValue); 73 | } 74 | 75 | this.setState({ 76 | renderedValue: renderedValue 77 | }); 78 | } 79 | 80 | typeChosen(option: string): void { 81 | if(option === "") { 82 | this.setState({ 83 | chosenType: Types.None 84 | }); 85 | 86 | return; 87 | } 88 | 89 | let key = option as keyof typeof Types; 90 | this.setState({ 91 | chosenType: Types[key] 92 | }); 93 | 94 | this.onChange(""); 95 | } 96 | 97 | render(): JSX.Element { 98 | let options: string[]; 99 | 100 | if(this.props.context === ScriptContextType.Parameters) { 101 | options = [ 102 | Types[Types.None], 103 | Types[Types.Concat], 104 | Types[Types.ResourceGroup], 105 | Types[Types.Custom] 106 | ] 107 | } 108 | 109 | if(this.props.context === ScriptContextType.Variables || ScriptContextType.Resources) { 110 | options = [ 111 | Types[Types.None], 112 | Types[Types.Concat], 113 | Types[Types.Parameters], 114 | Types[Types.ResourceGroup], 115 | Types[Types.Variables], 116 | Types[Types.Custom] 117 | ]; 118 | } 119 | 120 | if(this.props.includeText === true) { 121 | options.push(Types[Types.Text]) 122 | } 123 | 124 | return 125 | 126 | {this.state.chosenType === Types.Parameters && } 127 | {this.state.chosenType === Types.ResourceGroup && } 128 | {this.state.chosenType === Types.Variables && } 129 | {this.state.chosenType === Types.Custom && this.onChange(e.currentTarget.value)} className="form-control" />} 130 | {this.state.chosenType === Types.Text && this.onChange("'" + e.currentTarget.value + "'")} className="form-control" />} 131 | {this.state.chosenType === Types.Concat && } 132 | {this.props.topLevel === true && 133 | Script: 134 | 135 | 136 | 137 | Copy 138 | 139 | 140 | } 141 | 142 | } 143 | } 144 | 145 | export default ScriptHelper; -------------------------------------------------------------------------------- /src/components/WorkingWindow.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Component, Fragment } from 'react'; 3 | import ArmTemplate from "../models/ArmTemplate"; 4 | import ParameterForm from "./WorkingWindow/ParameterForm"; 5 | import Parameter from "../models/Parameter"; 6 | import ScriptHelper, { ScriptContextType } from "./ScriptHelper/ScriptHelper"; 7 | import Modal from "./Modal"; 8 | import VariableForm from "./WorkingWindow/VariableForm"; 9 | import { ResourceForm } from "./WorkingWindow/ResourceForm"; 10 | import Resource from "../models/Resource"; 11 | 12 | export enum Windows { 13 | AddParameter, 14 | EditParameter, 15 | AddVariable, 16 | EditVariable, 17 | AddResource, 18 | EditResource, 19 | AddOutput, 20 | EditOutput, 21 | None 22 | } 23 | 24 | interface WorkingWindowProps { 25 | template: ArmTemplate; 26 | onSubmitParameter: (parameter: Parameter, name: string) => void; 27 | onSubmitVariable: (variable: string | object | object[], name: string) => void; 28 | onSubmitResource: (resources: Resource[], parameters: { [index: string]: Parameter }) => ArmTemplate; 29 | window: Windows; 30 | editKey?: string; 31 | } 32 | 33 | class WorkingWindowState { 34 | showModal: boolean; 35 | } 36 | 37 | export class WorkingWindow extends Component { 38 | constructor(props: WorkingWindowProps) { 39 | super(props); 40 | 41 | this.closeModal = this.closeModal.bind(this); 42 | this.openModal = this.openModal.bind(this); 43 | 44 | let state = new WorkingWindowState(); 45 | state.showModal = false; 46 | 47 | this.state = state; 48 | } 49 | 50 | getHeadline(currentWindow: Windows, key?: string): string { 51 | switch(currentWindow) { 52 | case Windows.AddOutput: 53 | return "Add output"; 54 | case Windows.AddParameter: 55 | return "Add parameter"; 56 | case Windows.AddResource: 57 | return "Add resource"; 58 | case Windows.AddVariable: 59 | return "Add variable"; 60 | case Windows.EditOutput: 61 | return "Edit output: " + key; 62 | case Windows.EditParameter: 63 | return "Edit parameter: " + key; 64 | case Windows.EditResource: 65 | return "Edit resource: " + key; 66 | case Windows.EditVariable: 67 | return "Edit variable: " + key; 68 | case Windows.None: 69 | return "Select something in the menu"; 70 | } 71 | 72 | return "Unknown window selected"; 73 | } 74 | 75 | closeModal(): void { 76 | this.setState({ 77 | showModal: false 78 | }); 79 | } 80 | 81 | openModal(): void { 82 | this.setState({ 83 | showModal: true 84 | }); 85 | } 86 | 87 | render(): JSX.Element { 88 | let headline: string = this.getHeadline(this.props.window, this.props.editKey); 89 | 90 | let showScriptHelper: boolean = false; 91 | let scriptContextType: ScriptContextType; 92 | 93 | if(this.props.window === Windows.AddParameter || this.props.window === Windows.EditParameter) { 94 | showScriptHelper = true; 95 | scriptContextType = ScriptContextType.Parameters; 96 | } 97 | 98 | if(this.props.window === Windows.AddVariable || this.props.window === Windows.EditVariable) { 99 | showScriptHelper = true; 100 | scriptContextType = ScriptContextType.Variables; 101 | } 102 | 103 | if(this.props.window === Windows.AddResource || this.props.window === Windows.EditResource) { 104 | showScriptHelper = true; 105 | scriptContextType = ScriptContextType.Resources; 106 | } 107 | 108 | return ( 109 | 110 | 111 | {headline} 112 | 113 | {showScriptHelper && 114 | Open script helper 115 | } 116 | 117 | {this.props.window === Windows.AddParameter && } 118 | {this.props.window === Windows.EditParameter && } 119 | {this.props.window === Windows.AddVariable && } 120 | {this.props.window === Windows.EditVariable && } 121 | {this.props.window === Windows.AddResource && } 122 | {this.props.window === Windows.EditResource && v.name === this.props.editKey)}>} 123 | {showScriptHelper && 124 | 125 | 126 | } 127 | ) 128 | } 129 | } 130 | 131 | export default WorkingWindow; -------------------------------------------------------------------------------- /src/components/Inputs/ResourceInput.tsx: -------------------------------------------------------------------------------- 1 | import { Component, Fragment, ChangeEvent } from "react"; 2 | import React = require("react"); 3 | import Select from "./Select"; 4 | 5 | interface ResourceInputProps { 6 | id: string; 7 | value: string; 8 | variables: string[]; 9 | parameters: string[]; 10 | onValueUpdated: (value: string) => void; 11 | onNewParameterNameChanged: (name: string) => void; 12 | } 13 | 14 | class ResourceInputState { 15 | value: string; 16 | realValue: string; 17 | variableName: string; 18 | type: ResourceInputOptions; 19 | } 20 | 21 | enum ResourceInputOptions { 22 | Parameter, 23 | Variable, 24 | Custom 25 | } 26 | 27 | export class ResourceInput extends Component { 28 | constructor(props: ResourceInputProps) { 29 | super(props); 30 | 31 | this.onTypeSelected = this.onTypeSelected.bind(this); 32 | this.onValueSelected = this.onValueSelected.bind(this); 33 | this.setVariableName = this.setVariableName.bind(this); 34 | 35 | this.state = this.getState(props); 36 | } 37 | 38 | getState(props: ResourceInputProps): ResourceInputState { 39 | let state = new ResourceInputState(); 40 | state.type = ResourceInputOptions.Custom; 41 | state.value = ""; 42 | state.realValue = ""; 43 | state.variableName = ""; 44 | 45 | if(props.value.startsWith("[parameters('")) { 46 | state.type = ResourceInputOptions.Parameter; 47 | } 48 | 49 | if(props.value.startsWith("[variables('")) { 50 | state.type = ResourceInputOptions.Variable; 51 | } 52 | 53 | if(state.type !== ResourceInputOptions.Custom) { 54 | state.value = props.value.split("'")[1]; 55 | state.realValue = props.value; 56 | } 57 | 58 | return state; 59 | } 60 | 61 | componentDidUpdate(prevProps: ResourceInputProps): void { 62 | if(this.props.value === prevProps.value || this.props.value === this.state.realValue) { 63 | return; 64 | } 65 | 66 | this.setState(this.getState(this.props)); 67 | } 68 | 69 | onTypeSelected(option: string): void { 70 | const key = option as keyof typeof ResourceInputOptions; 71 | 72 | this.setState({ 73 | type: ResourceInputOptions[key], 74 | value: "", 75 | realValue: "", 76 | variableName: "" 77 | }); 78 | 79 | this.props.onValueUpdated(""); 80 | this.props.onNewParameterNameChanged(""); 81 | } 82 | 83 | onValueSelected(option: string): void { 84 | const typeString = this.state.type === ResourceInputOptions.Parameter ? "parameters" : "variables"; 85 | const realValue = "[" + typeString + "('" + option + "')]"; 86 | 87 | this.setState({ 88 | value: option, 89 | realValue: realValue 90 | }); 91 | 92 | this.props.onValueUpdated(realValue); 93 | } 94 | 95 | setVariableName(event: ChangeEvent): void { 96 | this.setState({ 97 | variableName: event.currentTarget.value 98 | }); 99 | 100 | this.props.onNewParameterNameChanged(event.currentTarget.value); 101 | } 102 | 103 | render(): JSX.Element { 104 | let options: string[] = []; 105 | 106 | if(this.props.parameters && this.props.parameters.length > 0){ 107 | options.push(ResourceInputOptions[ResourceInputOptions.Parameter]); 108 | } 109 | 110 | if(this.props.variables && this.props.variables.length > 0){ 111 | options.push(ResourceInputOptions[ResourceInputOptions.Variable]); 112 | } 113 | 114 | options.push(ResourceInputOptions[ResourceInputOptions.Custom]); 115 | 116 | let typeValue = ResourceInputOptions[this.state.type]; 117 | 118 | let valueLabel: string; 119 | 120 | switch(this.state.type) { 121 | case ResourceInputOptions.Variable: 122 | valueLabel = "Variable"; 123 | break; 124 | case ResourceInputOptions.Parameter: 125 | valueLabel = "Parameter"; 126 | break; 127 | case ResourceInputOptions.Custom: 128 | default: 129 | valueLabel = "Value"; 130 | break; 131 | } 132 | 133 | return 134 | Type 135 | 136 | 137 | 138 | 139 | {valueLabel} 140 | 141 | {this.state.type === ResourceInputOptions.Custom && this.props.children} 142 | {this.state.type !== ResourceInputOptions.Custom && 143 | } 144 | 145 | 146 | {this.state.type === ResourceInputOptions.Custom && 147 | New parameter name ([a-Z][a-Z0-9]*). Leave empty if no parameter is to be made 148 | 149 | 150 | 151 | } 152 | 153 | } 154 | } 155 | 156 | export default ResourceInput; -------------------------------------------------------------------------------- /src/components/Main.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Component } from 'react' 3 | import Menu from "./Menu"; 4 | import WorkingWindow, { Windows } from "./WorkingWindow"; 5 | import TemplateViewer from "./TemplateViewer"; 6 | import ArmTemplate from "../models/ArmTemplate"; 7 | import Parameter from "../models/Parameter"; 8 | import EntryTypes from "../models/EntryTypes"; 9 | import FileLoader from "./FileLoader"; 10 | import Resource from "../models/Resource"; 11 | import ResourceManager from "../models/Resources/ResourceManager"; 12 | 13 | interface MainProps {} 14 | 15 | class MainState { 16 | template: ArmTemplate; 17 | window: Windows; 18 | editKey?: string 19 | } 20 | 21 | export class Main extends Component { 22 | constructor(props: MainProps) { 23 | super(props); 24 | 25 | this.closeWindow = this.closeWindow.bind(this); 26 | this.loadFile = this.loadFile.bind(this); 27 | this.onSubmitParameter = this.onSubmitParameter.bind(this); 28 | this.onSubmitResource = this.onSubmitResource.bind(this); 29 | this.onSubmitVariable = this.onSubmitVariable.bind(this); 30 | this.onDeleteEntry = this.onDeleteEntry.bind(this); 31 | this.onOpenWindow = this.onOpenWindow.bind(this); 32 | 33 | this.state = { 34 | template: new ArmTemplate(), 35 | window: Windows.None 36 | }; 37 | } 38 | 39 | //Base function for closing working window 40 | closeWindow(): void { 41 | this.setState({ 42 | window: Windows.None, 43 | editKey: null 44 | }); 45 | } 46 | 47 | loadFile(fileContent: string): void { 48 | let dumbTemplate: ArmTemplate = JSON.parse(fileContent); 49 | 50 | let template = new ArmTemplate(); 51 | 52 | Object.keys(dumbTemplate.parameters).forEach((key) => { 53 | //Need to assign to object as simply JSON parsing doesn't produce objects with logics but only structs 54 | template.parameters[key] = Object.assign(new Parameter(), dumbTemplate.parameters[key]); 55 | }); 56 | 57 | template.variables = dumbTemplate.variables; 58 | 59 | dumbTemplate.resources.map(r => { 60 | template.resources.push(ResourceManager.getSpecificResource(r)); 61 | }); 62 | 63 | template.resources.map(r => { 64 | r.setDependantResources(template.resources); 65 | }); 66 | 67 | this.setState({ 68 | template: template 69 | }); 70 | 71 | this.closeWindow(); 72 | } 73 | 74 | onSubmitParameter(parameter: Parameter, name: string): void { 75 | let template = this.state.template; 76 | 77 | template.parameters[name] = parameter; 78 | 79 | this.setState({ 80 | template: template 81 | }); 82 | 83 | this.closeWindow(); 84 | } 85 | 86 | onSubmitVariable(variable: string | object | object[], name: string): void { 87 | let template = this.state.template; 88 | 89 | template.variables[name] = variable; 90 | 91 | this.setState({ 92 | template: template 93 | }); 94 | 95 | this.closeWindow(); 96 | } 97 | 98 | onSubmitResource(resources: Resource[], parameters: { [index: string]: Parameter }): ArmTemplate { 99 | let template = this.state.template; 100 | 101 | resources.forEach((resource: Resource) => { 102 | let index = template.resources.findIndex(r => r.name === resource.name); 103 | 104 | if(index < 0) { 105 | template.resources.push(resource); 106 | } else { 107 | template.resources[index] = resource; 108 | } 109 | }); 110 | 111 | Object.keys(parameters).forEach(key => { 112 | template.parameters[key] = parameters[key]; 113 | }); 114 | 115 | this.closeWindow(); 116 | this.setState({ 117 | template: template 118 | }); 119 | 120 | return template; 121 | } 122 | 123 | onDeleteEntry(entryType: EntryTypes, key: string): void { 124 | let template = this.state.template; 125 | 126 | if(entryType === EntryTypes.Parameter) { 127 | delete(template.parameters[key]); 128 | } 129 | 130 | if(entryType === EntryTypes.Variable) { 131 | delete(template.variables[key]); 132 | } 133 | 134 | if(entryType === EntryTypes.Output) { 135 | delete(template.outputs[key]); 136 | } 137 | 138 | if(entryType === EntryTypes.Resource) { 139 | let index = template.resources.findIndex(r => r.name === key); 140 | template.resources.splice(index, 1); 141 | } 142 | 143 | this.setState({ 144 | template: template 145 | }); 146 | 147 | if(key && this.state.editKey === key) { 148 | this.closeWindow(); 149 | } 150 | } 151 | 152 | onOpenWindow(windowToOpen: Windows, key?: string): void { 153 | this.setState({ 154 | window: windowToOpen, 155 | editKey: key 156 | }); 157 | } 158 | 159 | render(): JSX.Element { 160 | return ( 161 | Welcome 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | ) 175 | } 176 | } 177 | 178 | export default Main; -------------------------------------------------------------------------------- /src/components/WorkingWindow/Resources/ResourceTypeForm.tsx: -------------------------------------------------------------------------------- 1 | import { Component, Fragment } from "react"; 2 | import ArmTemplate from "../../../models/ArmTemplate"; 3 | import Resource, { ResourceTags } from "../../../models/Resource"; 4 | import Parameter from "../../../models/Parameter"; 5 | import React = require("react"); 6 | import ResourceInput from "../../Inputs/ResourceInput"; 7 | import DependantResourceInput from "../../Inputs/DependantResourceInput"; 8 | import ResourceDependency from "../../../models/Resources/ResourceDependency"; 9 | import StorageAccount from "../../../models/Resources/StorageAccount"; 10 | import StorageAccountBlobService from "../../../models/Resources/StorageAccountBlobService"; 11 | import StorageAccountBlobContainer from "../../../models/Resources/StorageAccountBlobContainer"; 12 | import Select from "../../Inputs/Select"; 13 | 14 | export interface ResourceTypeFormProps { 15 | template: ArmTemplate; 16 | onSave: (resources: Resource[], parameters: { [index: string]: Parameter }) => ArmTemplate; 17 | resource?: TResource; 18 | showLocation: boolean; 19 | } 20 | 21 | export abstract class ResourceTypeFormState { 22 | name: string; 23 | nameParameterName: string; 24 | location: string; 25 | locationParameterName: string; 26 | condition: string; 27 | displayName: string; 28 | dependency: ResourceDependency; 29 | } 30 | 31 | export abstract class ResourceTypeForm extends Component, TState> { 32 | protected abstract getNewState(): TState; 33 | protected abstract getNewResource(): TResource; 34 | 35 | constructor(props: ResourceTypeFormProps) { 36 | super(props); 37 | 38 | this.onNameUpdated = this.onNameUpdated.bind(this); 39 | this.onNameParameterNameUpdated = this.onNameParameterNameUpdated.bind(this); 40 | this.onConditionUpdated = this.onConditionUpdated.bind(this); 41 | this.onDependencyUpdated = this.onDependencyUpdated.bind(this); 42 | this.onLocationUpdated = this.onLocationUpdated.bind(this); 43 | this.onLocationParameterNameUpdated = this.onLocationParameterNameUpdated.bind(this); 44 | 45 | this.onSubmit = this.onSubmit.bind(this); 46 | } 47 | 48 | abstract getDependencies(): ResourceDependency; 49 | 50 | protected getBaseState(props: ResourceTypeFormProps): TState { 51 | let state = this.getNewState(); 52 | 53 | state.name = ""; 54 | state.nameParameterName = ""; 55 | 56 | state.condition = ""; 57 | state.displayName = ""; 58 | state.location = ""; 59 | state.locationParameterName = ""; 60 | 61 | if(props.resource) { 62 | if(props.resource.name) { 63 | state.name = props.resource.name; 64 | } 65 | 66 | if(props.resource.condition) { 67 | state.condition = props.resource.condition; 68 | } 69 | 70 | if(props.resource.location) { 71 | state.location = props.resource.location; 72 | } 73 | 74 | if(props.resource.tags && props.resource.tags.displayName) { 75 | state.displayName = props.resource.tags.displayName; 76 | } 77 | } 78 | 79 | state.dependency = this.getDependencies(); 80 | 81 | return state; 82 | } 83 | 84 | abstract getSpecificMarkup(): JSX.Element; 85 | 86 | onNameUpdated(value: string): void { 87 | this.setState({ 88 | name: value 89 | }); 90 | } 91 | 92 | onNameParameterNameUpdated(name: string): void { 93 | this.setState({ 94 | nameParameterName: name 95 | }); 96 | } 97 | 98 | onConditionUpdated(value: string): void { 99 | this.setState({ 100 | condition: value 101 | }); 102 | } 103 | 104 | onDisplayNameUpdated(value: string): void { 105 | this.setState({ 106 | displayName: value 107 | }); 108 | } 109 | 110 | onSubmit(): void { 111 | let resource = this.props.resource ? this.props.resource : this.getNewResource(); 112 | 113 | resource.setName = this.state.nameParameterName 114 | ? this.getParameterString(this.state.nameParameterName) 115 | : this.state.name; 116 | 117 | if(this.state.condition) { 118 | resource.condition = this.state.condition; 119 | } else { 120 | resource.condition = undefined; 121 | } 122 | 123 | resource.location = this.state.locationParameterName 124 | ? this.getParameterString(this.state.locationParameterName) 125 | : this.state.location; 126 | 127 | if(this.state.displayName) { 128 | resource.tags = new ResourceTags(); 129 | resource.tags.displayName = this.state.displayName; 130 | } else { 131 | resource.tags = undefined; 132 | } 133 | 134 | this.setSpecificInformation(resource); 135 | 136 | let parametersToCreate = this.getSpecificNewParameters(); 137 | 138 | this.createParameter(this.state.nameParameterName, this.state.name, "string", [], parametersToCreate); 139 | 140 | this.createParameter(this.state.locationParameterName, this.state.location, "string", [], parametersToCreate); 141 | 142 | let resources = [resource]; 143 | 144 | let existingResources = this.buildRequiredResources(this.state.dependency); 145 | 146 | resource.setDependencies(this.state.dependency, existingResources); 147 | 148 | this.props.onSave(resources, parametersToCreate); 149 | } 150 | 151 | protected abstract setSpecificInformation(resource: TResource): void; 152 | protected abstract getSpecificNewParameters(): { [index: string]: Parameter }; 153 | 154 | protected buildRequiredResources(dependency: ResourceDependency, existingResources?: Resource[]): Resource[] { 155 | let resources: Resource[] = []; 156 | 157 | dependency.required.forEach(r => { 158 | existingResources = this.buildRequiredResources(r, existingResources); 159 | }); 160 | 161 | Object.keys(dependency.newResources).map((type) => { 162 | let name = dependency.newResources[type]; 163 | let dependencyModel = dependency.required.find(d => d.type === type); 164 | 165 | switch (type) { 166 | case StorageAccount.resourceType: 167 | resources.push.apply(resources, StorageAccount.getDefault(name)); 168 | break; 169 | case StorageAccountBlobService.resourceType: 170 | resources.push.apply(resources, StorageAccountBlobService.getDefault(name, dependencyModel)); 171 | break; 172 | case StorageAccountBlobContainer.resourceType: 173 | resources.push.apply(resources, StorageAccountBlobContainer.getDefault(name, dependencyModel)); 174 | break; 175 | } 176 | }); 177 | 178 | return this.props.onSave(resources, {}).resources; 179 | } 180 | 181 | protected getParameterString(parameterName: string): string { 182 | return "[parameters('" + parameterName + "')]"; 183 | } 184 | 185 | protected createParameter(name: string, defaultValue: boolean | number | string, type: string, allowedValues: number[] | string[], parameterList: { [index: string]: Parameter }): void { 186 | if(!name) { 187 | return null; 188 | } 189 | 190 | let parameter = new Parameter(); 191 | if(defaultValue !== null && defaultValue !== undefined && defaultValue !== "") { 192 | parameter.defaultValue = defaultValue; 193 | } 194 | if(allowedValues && allowedValues.length > 0) { 195 | parameter.allowedValues = allowedValues 196 | } 197 | parameter.type = type; 198 | 199 | parameterList[name] = parameter; 200 | } 201 | 202 | onDependencyUpdated(dependency: ResourceDependency): void { 203 | this.setState({ 204 | dependency: dependency 205 | }); 206 | } 207 | 208 | onLocationUpdated(location: string): void { 209 | this.setState({ 210 | location: location 211 | }); 212 | } 213 | 214 | onLocationParameterNameUpdated(name: string): void { 215 | this.setState({ 216 | locationParameterName: name 217 | }); 218 | } 219 | 220 | render(): JSX.Element { 221 | const parameters = Object.keys(this.props.template.parameters); 222 | const variables = Object.keys(this.props.template.variables); 223 | 224 | const submitText = this.props.resource ? "Save" : "Add"; 225 | 226 | return 227 | Name* 228 | 229 | this.onNameUpdated(e.currentTarget.value)} className="form-control" required /> 230 | 231 | 232 | {this.props.showLocation && 233 | Location* 234 | 235 | 236 | 237 | } 238 | 239 | Condition (to deploy) 240 | this.onConditionUpdated(e.currentTarget.value)} className="form-control" /> 241 | 242 | Display name 243 | this.onDisplayNameUpdated(e.currentTarget.value)} className="form-control" /> 244 | 245 | {this.getSpecificMarkup()} 246 | 247 | 248 | 249 | 250 | {submitText} 251 | 252 | 253 | } 254 | } -------------------------------------------------------------------------------- /src/components/WorkingWindow/ParameterForm.tsx: -------------------------------------------------------------------------------- 1 | import { Component, Fragment, ChangeEvent } from "react"; 2 | import React = require("react"); 3 | import Select from "../Inputs/Select"; 4 | import Parameter, { ParameterMetadata } from "../../models/Parameter"; 5 | import ConditionalInput from "../Inputs/ConditionalInput"; 6 | import ListInput from "../Inputs/ListInput"; 7 | 8 | interface ParameterFormProps { 9 | parameter?: Parameter; 10 | name?: string; 11 | onSubmit: (savedParameter: Parameter, parameterName: string) => void; 12 | } 13 | 14 | class ParameterFormState { 15 | name: string; 16 | type: string; 17 | defaultValue: any; 18 | minLength?: number; 19 | maxLength?: number; 20 | minValue?: number; 21 | maxValue?: number; 22 | allowedValues: any[]; 23 | description: string; 24 | } 25 | 26 | export class ParameterForm extends Component { 27 | constructor(props: ParameterFormProps) { 28 | super(props); 29 | 30 | this.setAllowedValues = this.setAllowedValues.bind(this); 31 | this.setDefaultValue = this.setDefaultValue.bind(this); 32 | this.setDescription = this.setDescription.bind(this); 33 | this.setMinimumLength = this.setMinimumLength.bind(this); 34 | this.setMaximumLength = this.setMaximumLength.bind(this); 35 | this.setMinimumValue = this.setMinimumValue.bind(this); 36 | this.setMaximumValue = this.setMaximumValue.bind(this); 37 | this.setName = this.setName.bind(this); 38 | this.setType = this.setType.bind(this); 39 | this.submitForm = this.submitForm.bind(this); 40 | 41 | this.state = this.initializeState(props); 42 | } 43 | 44 | initializeState(props: ParameterFormProps): ParameterFormState{ 45 | let state = new ParameterFormState(); 46 | 47 | if(props.parameter) { 48 | state.type = props.parameter.type; 49 | state.allowedValues = props.parameter.allowedValues; 50 | state.defaultValue = props.parameter.defaultValue; 51 | state.minLength = props.parameter.minLength; 52 | state.maxLength = props.parameter.maxLength; 53 | state.minValue = props.parameter.minValue; 54 | state.maxValue = props.parameter.maxValue; 55 | if(props.parameter.metadata) { 56 | state.description = props.parameter.metadata.description; 57 | } else { 58 | state.description = ""; 59 | } 60 | } else { 61 | state.defaultValue = ""; 62 | state.description = ""; 63 | } 64 | 65 | if(props.name) { 66 | state.name = props.name; 67 | } else { 68 | state.name = ""; 69 | } 70 | 71 | return state; 72 | } 73 | 74 | componentDidUpdate(prevProps: ParameterFormProps): void { 75 | if(this.props.name === prevProps.name) { 76 | return; 77 | } 78 | 79 | this.setState(this.initializeState(this.props)); 80 | } 81 | 82 | setAllowedValues(values: any[]): void { 83 | this.setState({ 84 | allowedValues: values 85 | }); 86 | } 87 | 88 | setDefaultValue(value: any): void { 89 | this.setState({ 90 | defaultValue: value 91 | }); 92 | } 93 | 94 | setDescription(event: ChangeEvent): void { 95 | this.setState({ 96 | description: event.currentTarget.value 97 | }); 98 | } 99 | 100 | setMinimumLength(value: number): void { 101 | this.setState({ 102 | minLength: value 103 | }); 104 | } 105 | 106 | setMaximumLength(value: number): void { 107 | this.setState({ 108 | maxLength: value 109 | }); 110 | } 111 | 112 | setMinimumValue(value: number): void { 113 | this.setState({ 114 | minValue: value 115 | }); 116 | } 117 | 118 | setMaximumValue(value: number): void { 119 | this.setState({ 120 | maxValue: value 121 | }); 122 | } 123 | 124 | setName(event: ChangeEvent): void { 125 | this.setState({ 126 | name: event.currentTarget.value 127 | }); 128 | } 129 | 130 | setType(type: string): void { 131 | this.setState({ 132 | type: type, 133 | //Reset default value to make sure that the type is correct 134 | defaultValue: "" 135 | }); 136 | } 137 | 138 | submitForm(): void { 139 | let parameter = new Parameter(); 140 | 141 | if(this.state.defaultValue) { 142 | parameter.defaultValue = this.state.defaultValue; 143 | } 144 | 145 | if(this.state.allowedValues) { 146 | let allowedValues = this.state.allowedValues.filter(v => v !== ""); 147 | 148 | if(allowedValues.length >= 1) { 149 | parameter.allowedValues = allowedValues; 150 | } 151 | } 152 | 153 | if(this.state.description) { 154 | parameter.metadata = new ParameterMetadata(); 155 | parameter.metadata.description = this.state.description; 156 | } 157 | 158 | if(typeof(this.state.maxLength) === "number") { 159 | parameter.maxLength = this.state.maxLength; 160 | } 161 | 162 | if(typeof(this.state.maxValue) === "number") { 163 | parameter.maxValue = this.state.maxValue; 164 | } 165 | 166 | if(typeof(this.state.minLength) === "number") { 167 | parameter.minLength = this.state.minLength; 168 | } 169 | 170 | if(typeof(this.state.minValue) === "number") { 171 | parameter.minValue = this.state.minValue; 172 | } 173 | 174 | parameter.type = this.state.type; 175 | 176 | this.props.onSubmit(parameter, this.state.name); 177 | } 178 | 179 | render(): JSX.Element { 180 | let defaultValueType: string; 181 | 182 | switch(this.state.type) { 183 | case "string": 184 | case "securestring": 185 | case "object": 186 | case "secureObject": 187 | case "array": 188 | defaultValueType = "text"; 189 | break; 190 | case "int": 191 | defaultValueType = "number"; 192 | break; 193 | case "bool": 194 | defaultValueType = "bool"; 195 | } 196 | 197 | const allowedValuesType = this.state.type === "int" ? "number" : "text"; 198 | const submitText = this.props.name ? "Save" : "Add"; 199 | 200 | let defaultValue: string | number | boolean = null; 201 | let minValue: number = null; 202 | let maxValue: number = null; 203 | let minLength: number = null; 204 | let maxLength: number = null; 205 | let allowedValues: any[] = []; 206 | 207 | if(this.props.parameter) { 208 | if(this.props.parameter.defaultValue) 209 | defaultValue = this.props.parameter.defaultValue; 210 | 211 | if(this.props.parameter.minValue) 212 | minValue = this.props.parameter.minValue; 213 | 214 | if(this.props.parameter.maxValue) 215 | maxValue = this.props.parameter.maxValue; 216 | 217 | if(this.props.parameter.minLength) 218 | minLength = this.props.parameter.minLength; 219 | 220 | if(this.props.parameter.maxLength) 221 | maxLength = this.props.parameter.maxLength; 222 | 223 | if(this.props.parameter.allowedValues) 224 | allowedValues = this.props.parameter.allowedValues; 225 | } 226 | 227 | return ( 228 | 229 | Name ([a-Z][a-Z0-9]*) 230 | 231 | 232 | 233 | 234 | Type 235 | 236 | 237 | 238 | 239 | {defaultValueType && } 240 | 241 | Description 242 | 243 | 244 | 245 | 246 | {this.state.type === "int" && 247 | 248 | 249 | } 250 | 251 | {(this.state.type === "string" || this.state.type === "securestring" || this.state.type === "array") && 252 | 253 | 254 | } 255 | 256 | {(this.state.type === "string" || this.state.type === "securestring" || this.state.type === "int") && 257 | } 258 | 259 | 260 | {submitText} 261 | 262 | ); 263 | } 264 | } 265 | 266 | export default ParameterForm; -------------------------------------------------------------------------------- /src/components/Menu.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Component, Fragment } from 'react' 3 | import ArmTemplate from "../models/ArmTemplate"; 4 | import Parameter from "../models/Parameter"; 5 | import { Windows } from "./WorkingWindow"; 6 | import EntryTypes from "../models/EntryTypes"; 7 | import Badge from "./Badge"; 8 | import Resource from "../models/Resource"; 9 | 10 | interface MenuProps { 11 | template: ArmTemplate; 12 | currentlyOpenWindow: Windows, 13 | openWindow: (window: Windows, key?: string) => void, 14 | deleteEntry: (entryType: EntryTypes, key: string) => void 15 | } 16 | 17 | enum MenuOption { 18 | Parameters, 19 | Variables, 20 | Resources, 21 | Outputs 22 | } 23 | 24 | class MenuState { 25 | activeGroup: MenuOption; 26 | activeKey: string; 27 | parameterCount: number; 28 | variableCount: number; 29 | resourceCount: number; 30 | outputCount: number; 31 | } 32 | 33 | export class Menu extends Component { 34 | constructor(props: MenuProps) { 35 | super(props); 36 | 37 | this.onAddParameter = this.onAddParameter.bind(this); 38 | this.onAddVariable = this.onAddVariable.bind(this); 39 | this.onAddResource = this.onAddResource.bind(this); 40 | this.onAddOutput = this.onAddOutput.bind(this); 41 | 42 | this.onDeleteParameter = this.onDeleteParameter.bind(this); 43 | this.onEditParameter = this.onEditParameter.bind(this); 44 | this.renderParameters = this.renderParameters.bind(this); 45 | this.renderParameter = this.renderParameter.bind(this); 46 | 47 | this.onDeleteVariable = this.onDeleteVariable.bind(this); 48 | this.onEditVariable = this.onEditVariable.bind(this); 49 | this.renderVariables = this.renderVariables.bind(this); 50 | this.renderVariable = this.renderVariable.bind(this); 51 | 52 | this.onDeleteResource = this.onDeleteResource.bind(this); 53 | this.onEditResource = this.onEditResource.bind(this); 54 | this.renderResources = this.renderResources.bind(this); 55 | this.renderResource = this.renderResource.bind(this); 56 | 57 | this.state = new MenuState(); 58 | } 59 | 60 | componentDidUpdate(): void { 61 | let parameterCount = Object.keys(this.props.template.parameters).length; 62 | let variableCount = Object.keys(this.props.template.variables).length; 63 | let resourceCount = this.props.template.resources.length; 64 | let outputCount = Object.keys(this.props.template.outputs).length; 65 | 66 | if(parameterCount != this.state.parameterCount 67 | || variableCount != this.state.variableCount 68 | || resourceCount != this.state.resourceCount 69 | || outputCount != this.state.outputCount) { 70 | this.setState({ 71 | parameterCount: Object.keys(this.props.template.parameters).length, 72 | variableCount: Object.keys(this.props.template.variables).length, 73 | resourceCount: this.props.template.resources.length, 74 | outputCount: Object.keys(this.props.template.outputs).length 75 | }); 76 | } 77 | } 78 | 79 | onAddParameter(): void { 80 | this.setState({ 81 | activeGroup: MenuOption.Parameters, 82 | activeKey: null 83 | }); 84 | 85 | this.props.openWindow(Windows.AddParameter); 86 | } 87 | 88 | onAddVariable(): void { 89 | this.setState({ 90 | activeGroup: MenuOption.Variables, 91 | activeKey: null 92 | }); 93 | 94 | this.props.openWindow(Windows.AddVariable); 95 | } 96 | 97 | onAddResource(): void { 98 | this.setState({ 99 | activeGroup: MenuOption.Resources, 100 | activeKey: null 101 | }); 102 | 103 | this.props.openWindow(Windows.AddResource); 104 | } 105 | 106 | onAddOutput(): void { 107 | this.setState({ 108 | activeGroup: MenuOption.Outputs, 109 | activeKey: null 110 | }); 111 | 112 | this.props.openWindow(Windows.AddOutput); 113 | } 114 | 115 | onDeleteParameter(parameterName: string): void { 116 | if(window.confirm('Are you sure you want to delete ' + parameterName + '?')) { 117 | this.props.deleteEntry(EntryTypes.Parameter, parameterName); 118 | } 119 | } 120 | 121 | onEditParameter(parameterName: string): void { 122 | this.setState({ 123 | activeGroup: MenuOption.Parameters, 124 | activeKey: parameterName 125 | }); 126 | 127 | this.props.openWindow(Windows.EditParameter, parameterName); 128 | } 129 | 130 | renderParameter(parameterName: string): JSX.Element { 131 | let className = "list-group-item sub-item d-flex justify-content-between" 132 | if(this.state.activeKey === parameterName && (this.props.currentlyOpenWindow === Windows.AddParameter || this.props.currentlyOpenWindow === Windows.EditParameter)) 133 | className += " active"; 134 | 135 | return 136 | {parameterName} this.onEditParameter(parameterName)}>Edit 137 | this.onDeleteParameter(parameterName)}>Delete 138 | 139 | } 140 | 141 | renderParameters(parameters: { [index: string]: Parameter }): JSX.Element { 142 | if(this.state.parameterCount <= 0) 143 | return null; 144 | 145 | return ( 146 | {Object.keys(parameters).sort().map((key) => { 147 | return this.renderParameter(key); 148 | })} 149 | ) 150 | } 151 | 152 | onDeleteVariable(variableName: string): void { 153 | if(window.confirm('Are you sure you want to delete ' + variableName + '?')) { 154 | this.props.deleteEntry(EntryTypes.Variable, variableName); 155 | } 156 | } 157 | 158 | onEditVariable(variableName: string): void { 159 | this.setState({ 160 | activeGroup: MenuOption.Variables, 161 | activeKey: variableName 162 | }); 163 | 164 | this.props.openWindow(Windows.EditVariable, variableName); 165 | } 166 | 167 | renderVariable(variableName: string): JSX.Element { 168 | let className = "list-group-item sub-item d-flex justify-content-between" 169 | if(this.state.activeKey === variableName && (this.props.currentlyOpenWindow === Windows.AddVariable || this.props.currentlyOpenWindow === Windows.EditVariable)) 170 | className += " active"; 171 | 172 | return 173 | {variableName} this.onEditVariable(variableName)}>Edit 174 | this.onDeleteVariable(variableName)}>Delete 175 | 176 | } 177 | 178 | renderVariables(variables: { [index: string]: string | object | object[] }): JSX.Element { 179 | if(this.state.parameterCount <= 0) 180 | return null; 181 | 182 | return ( 183 | {Object.keys(variables).sort().map((key) => { 184 | return this.renderVariable(key); 185 | })} 186 | ) 187 | } 188 | 189 | onDeleteResource(resourceName: string): void { 190 | if(window.confirm('Are you sure you want to delete ' + resourceName + '?')) { 191 | this.props.deleteEntry(EntryTypes.Resource, resourceName); 192 | } 193 | } 194 | 195 | onEditResource(resourceName: string): void { 196 | this.setState({ 197 | activeGroup: MenuOption.Resources, 198 | activeKey: resourceName 199 | }); 200 | 201 | this.props.openWindow(Windows.EditResource, resourceName); 202 | } 203 | 204 | renderResource(displayName: string, resourceName: string, type: string): JSX.Element { 205 | let className = "list-group-item sub-item d-flex justify-content-between"; 206 | if(this.state.activeKey === resourceName && (this.props.currentlyOpenWindow === Windows.AddResource || this.props.currentlyOpenWindow === Windows.EditResource)) 207 | className += " active"; 208 | 209 | return 210 | {displayName} ({type}) this.onEditResource(resourceName)}>Edit 211 | this.onDeleteResource(resourceName)}>Delete 212 | 213 | } 214 | 215 | renderResources(resources: Resource[]): JSX.Element { 216 | if(this.state.resourceCount <= 0) 217 | return null; 218 | 219 | return 220 | {resources.map(resource => { 221 | let displayName = resource.tags && resource.tags.displayName ? resource.tags.displayName : resource.getName(); 222 | return this.renderResource(displayName, resource.name, resource.type); 223 | })} 224 | 225 | } 226 | 227 | render(): JSX.Element { 228 | const baseClassName: string = "list-group-item d-flex justify-content-between"; 229 | 230 | let parametersMenuClass: string = baseClassName; 231 | let variablesMenuClass: string = baseClassName; 232 | let resourcesMenuClass: string = baseClassName; 233 | let outputsMenuClass: string = baseClassName; 234 | if(this.state.activeGroup == MenuOption.Parameters && this.props.currentlyOpenWindow !== Windows.None) 235 | parametersMenuClass += " active"; 236 | 237 | else if(this.state.activeGroup == MenuOption.Variables && this.props.currentlyOpenWindow !== Windows.None) 238 | variablesMenuClass += " active"; 239 | 240 | 241 | else if(this.state.activeGroup == MenuOption.Resources && this.props.currentlyOpenWindow !== Windows.None) 242 | resourcesMenuClass += " active"; 243 | 244 | else if(this.state.activeGroup == MenuOption.Outputs && this.props.currentlyOpenWindow !== Windows.None) 245 | outputsMenuClass += " active"; 246 | 247 | return ( 248 | Menu 249 | 250 | 251 | 252 | Parameters this.onAddParameter()}>Add 253 | 254 | 255 | 256 | {this.renderParameters(this.props.template.parameters)} 257 | 258 | 259 | Variables this.onAddVariable()}>Add 260 | 261 | 262 | 263 | {this.renderVariables(this.props.template.variables)} 264 | 265 | 266 | Resources this.onAddResource()}>Add 267 | 268 | 269 | 270 | {this.renderResources(this.props.template.resources)} 271 | 272 | 273 | Outputs this.onAddOutput()}>Add 274 | 275 | 276 | 277 | ) 278 | } 279 | } 280 | 281 | export default Menu; -------------------------------------------------------------------------------- /dist/react.production.min.js: -------------------------------------------------------------------------------- 1 | /** @license React v16.12.0 2 | * react.production.min.js 3 | * 4 | * Copyright (c) Facebook, Inc. and its affiliates. 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE file in the root directory of this source tree. 8 | */ 9 | 'use strict';(function(v,m){"object"===typeof exports&&"undefined"!==typeof module?module.exports=m():"function"===typeof define&&define.amd?define(m):v.React=m()})(this,function(){function v(a){for(var b="https://reactjs.org/docs/error-decoder.html?invariant="+a,c=1;cD.length&&D.push(a)}function P(a,b,c,d){var e=typeof a;if("undefined"===e||"boolean"===e)a=null;var p=!1;if(null===a)p=!0;else switch(e){case "string":case "number":p=!0;break;case "object":switch(a.$$typeof){case y:case ya:p=!0}}if(p)return c(d,a,""===b?"."+Q(a,0):b),1;p=0;b=""===b?".":b+":";if(Array.isArray(a))for(var f=0;fE(h,c))void 0!==k&&0>E(k,h)?(a[d]=k,a[g]=c,d=g):(a[d]=h,a[f]=c,d=f);else if(void 0!==k&&0>E(k,c))a[d]=k,a[g]=c,d=g;else break a}}return b}return null}function E(a,b){var c=a.sortIndex-b.sortIndex;return 0!==c?c:a.id-b.id}function G(a){for(var b=n(u);null!==b;){if(null=== 16 | b.callback)F(u);else if(b.startTime<=a)F(u),b.sortIndex=b.expirationTime,T(q,b);else break;b=n(u)}}function U(a){z=!1;G(a);if(!w)if(null!==n(q))w=!0,A(V);else{var b=n(u);null!==b&&H(U,b.startTime-a)}}function V(a,b){w=!1;z&&(z=!1,W());I=!0;var c=h;try{G(b);for(l=n(q);null!==l&&(!(l.expirationTime>b)||a&&!X());){var d=l.callback;if(null!==d){l.callback=null;h=l.priorityLevel;var e=d(l.expirationTime<=b);b=r();"function"===typeof e?l.callback=e:l===n(q)&&F(q);G(b)}else F(q);l=n(q)}if(null!==l)var f= 17 | !0;else{var m=n(u);null!==m&&H(U,m.startTime-b);f=!1}return f}finally{l=null,h=c,I=!1}}function pa(a){switch(a){case 1:return-1;case 2:return 250;case 5:return 1073741823;case 4:return 1E4;default:return 5E3}}var f="function"===typeof Symbol&&Symbol.for,y=f?Symbol.for("react.element"):60103,ya=f?Symbol.for("react.portal"):60106,x=f?Symbol.for("react.fragment"):60107,Y=f?Symbol.for("react.strict_mode"):60108,Ba=f?Symbol.for("react.profiler"):60114,Ca=f?Symbol.for("react.provider"):60109,Da=f?Symbol.for("react.context"): 18 | 60110,Ea=f?Symbol.for("react.forward_ref"):60112,Fa=f?Symbol.for("react.suspense"):60113;f&&Symbol.for("react.suspense_list");var Ga=f?Symbol.for("react.memo"):60115,Ha=f?Symbol.for("react.lazy"):60116;f&&Symbol.for("react.fundamental");f&&Symbol.for("react.responder");f&&Symbol.for("react.scope");var ma="function"===typeof Symbol&&Symbol.iterator,qa=Object.getOwnPropertySymbols,Ia=Object.prototype.hasOwnProperty,Ja=Object.prototype.propertyIsEnumerable,J=function(){try{if(!Object.assign)return!1; 19 | var a=new String("abc");a[5]="de";if("5"===Object.getOwnPropertyNames(a)[0])return!1;var b={};for(a=0;10>a;a++)b["_"+String.fromCharCode(a)]=a;if("0123456789"!==Object.getOwnPropertyNames(b).map(function(a){return b[a]}).join(""))return!1;var c={};"abcdefghijklmnopqrst".split("").forEach(function(a){c[a]=a});return"abcdefghijklmnopqrst"!==Object.keys(Object.assign({},c)).join("")?!1:!0}catch(d){return!1}}()?Object.assign:function(a,b){if(null===a||void 0===a)throw new TypeError("Object.assign cannot be called with null or undefined"); 20 | var c=Object(a);for(var d,e=1;e=va};f=function(){};Z=function(a){0>a||125d?(a.sortIndex=e,T(u,a),null===n(q)&&a===n(u)&&(z?W():z=!0,H(U,e-d))):(a.sortIndex=c,T(q,a),w||I||(w=!0,A(V)));return a},unstable_cancelCallback:function(a){a.callback=null},unstable_wrapCallback:function(a){var b=h;return function(){var c=h;h=b;try{return a.apply(this,arguments)}finally{h=c}}},unstable_getCurrentPriorityLevel:function(){return h}, 27 | unstable_shouldYield:function(){var a=r();G(a);var b=n(q);return b!==l&&null!==l&&null!==b&&null!==b.callback&&b.startTime<=a&&b.expirationTime { 31 | protected getNewResource(): StorageAccount { 32 | return new StorageAccount(); 33 | } 34 | 35 | getDependencies(): ResourceDependency { 36 | return StorageAccount.getResourceDependencyModel(); 37 | } 38 | 39 | protected getNewState(): StorageAccountFormState { 40 | return new StorageAccountFormState(); 41 | } 42 | 43 | constructor(props: ResourceTypeFormProps) { 44 | super(props); 45 | 46 | this.onAccessTierParameterNameUpdated = this.onAccessTierParameterNameUpdated.bind(this); 47 | this.onAccessTierSelected = this.onAccessTierSelected.bind(this); 48 | this.onKindParameterNameUpdated = this.onKindParameterNameUpdated.bind(this); 49 | this.onKindSelected = this.onKindSelected.bind(this); 50 | this.onHttpsOnlyUpdated = this.onHttpsOnlyUpdated.bind(this); 51 | this.onHttpsOnlyParameterNameUpdated = this.onHttpsOnlyParameterNameUpdated.bind(this); 52 | this.onBlobEncryptionUpdated = this.onBlobEncryptionUpdated.bind(this); 53 | this.onBlobEncryptionParameterNameUpdated = this.onBlobEncryptionParameterNameUpdated.bind(this); 54 | this.onFileEncryptionUpdated = this.onFileEncryptionUpdated.bind(this); 55 | this.onFileEncryptionParameterNameUpdated = this.onFileEncryptionParameterNameUpdated.bind(this); 56 | this.onSkuNameUpdated = this.onSkuNameUpdated.bind(this); 57 | this.onSkuNameParameterNameUpdated = this.onSkuNameParameterNameUpdated.bind(this); 58 | this.onSkuTierUpdated = this.onSkuTierUpdated.bind(this); 59 | this.onSkuTierParameterNameUpdated = this.onSkuTierParameterNameUpdated.bind(this); 60 | 61 | this.onSubmit = this.onSubmit.bind(this); 62 | 63 | let state = this.getBaseState(props); 64 | state.kind = ""; 65 | state.kindParameterName = ""; 66 | state.accessTier = ""; 67 | state.accessTierParameterName = ""; 68 | state.httpsOnly = false; 69 | state.httpsOnlyString = ""; 70 | state.httpsOnlyParameterName = ""; 71 | state.blobEncryption = false; 72 | state.blobEncryptionString = ""; 73 | state.blobEncryptionParameterName = ""; 74 | state.fileEncryption = false; 75 | state.fileEncryptionString = ""; 76 | state.fileEncryptionParameterName = ""; 77 | state.skuName = ""; 78 | state.skuNameParameterName = ""; 79 | state.skuTier = ""; 80 | state.skuTierParameterName = ""; 81 | 82 | if(props.resource) { 83 | if(props.resource.kind) { 84 | state.kind = props.resource.kind; 85 | } 86 | 87 | if(props.resource.properties) { 88 | if(props.resource.properties.accessTier) { 89 | state.accessTier = props.resource.properties.accessTier; 90 | } 91 | 92 | if(props.resource.properties.supportsHttpsTrafficOnly !== null && props.resource.properties.supportsHttpsTrafficOnly !== undefined) { 93 | state.httpsOnly = props.resource.properties.supportsHttpsTrafficOnly; 94 | state.httpsOnlyString = String(props.resource.properties.supportsHttpsTrafficOnly); 95 | } 96 | 97 | if(props.resource.properties.encryption && props.resource.properties.encryption.services) { 98 | if(props.resource.properties.encryption.services.blob) { 99 | state.blobEncryption = props.resource.properties.encryption.services.blob.enabled; 100 | state.blobEncryptionString = String(props.resource.properties.encryption.services.blob.enabled); 101 | } 102 | 103 | if(props.resource.properties.encryption.services.file) { 104 | state.fileEncryption = props.resource.properties.encryption.services.file.enabled; 105 | state.fileEncryptionString = String(props.resource.properties.encryption.services.file.enabled); 106 | } 107 | } 108 | } 109 | 110 | if(props.resource.sku) { 111 | if(props.resource.sku.name) { 112 | state.skuName = props.resource.sku.name; 113 | } 114 | 115 | if(props.resource.sku.tier) { 116 | state.skuTier = props.resource.sku.tier; 117 | } 118 | } 119 | } 120 | 121 | this.state = state; 122 | } 123 | 124 | onAccessTierSelected(option: string): void { 125 | this.setState({ 126 | accessTier: option 127 | }); 128 | } 129 | 130 | onAccessTierParameterNameUpdated(name: string): void { 131 | this.setState({ 132 | accessTierParameterName: name 133 | }); 134 | } 135 | 136 | onKindParameterNameUpdated(name: string): void { 137 | this.setState({ 138 | kindParameterName: name 139 | }); 140 | } 141 | 142 | onKindSelected(option: string): void { 143 | this.setState({ 144 | kind: option 145 | }); 146 | } 147 | 148 | onHttpsOnlyUpdated(value: boolean | string): void { 149 | this.setState({ 150 | httpsOnly: value === "" ? false : value, 151 | httpsOnlyString: String(value) 152 | }); 153 | } 154 | 155 | onHttpsOnlyParameterNameUpdated(name: string): void { 156 | this.setState({ 157 | httpsOnlyParameterName: name 158 | }); 159 | } 160 | 161 | onBlobEncryptionUpdated(value: boolean | string): void { 162 | this.setState({ 163 | blobEncryption: value === "" ? false : value, 164 | blobEncryptionString: String(value) 165 | }); 166 | } 167 | 168 | onBlobEncryptionParameterNameUpdated(name: string): void { 169 | this.setState({ 170 | blobEncryptionParameterName: name 171 | }); 172 | } 173 | 174 | onFileEncryptionUpdated(value: boolean | string): void { 175 | this.setState({ 176 | fileEncryption: value === "" ? false : value, 177 | fileEncryptionString: String(value) 178 | }); 179 | } 180 | 181 | onFileEncryptionParameterNameUpdated(name: string): void { 182 | this.setState({ 183 | fileEncryptionParameterName: name 184 | }); 185 | } 186 | 187 | onSkuNameUpdated(name: string): void { 188 | this.setState({ 189 | skuName: name 190 | }); 191 | } 192 | 193 | onSkuNameParameterNameUpdated(name: string): void { 194 | this.setState({ 195 | skuNameParameterName: name 196 | }); 197 | } 198 | 199 | onSkuTierUpdated(tier: string): void { 200 | this.setState({ 201 | skuTier: tier 202 | }); 203 | } 204 | 205 | onSkuTierParameterNameUpdated(name: string): void { 206 | this.setState({ 207 | skuTierParameterName: name 208 | }); 209 | } 210 | 211 | protected setSpecificInformation(resource: StorageAccount): void { 212 | resource.kind = this.state.kindParameterName 213 | ? this.getParameterString(this.state.kindParameterName) 214 | : this.state.kind; 215 | 216 | if(!resource.properties) { 217 | resource.properties = new StorageAccountProperties(); 218 | } 219 | 220 | if(this.state.accessTier) { 221 | resource.properties.accessTier = this.state.accessTierParameterName 222 | ? this.getParameterString(this.state.accessTierParameterName) 223 | : this.state.accessTier; 224 | } 225 | 226 | resource.properties.supportsHttpsTrafficOnly = this.state.httpsOnlyParameterName 227 | ? this.getParameterString(this.state.httpsOnlyParameterName) 228 | : this.state.httpsOnly; 229 | 230 | if(!resource.properties.encryption) { 231 | resource.properties.encryption = new StorageAccountEncryption(); 232 | } 233 | 234 | if(!resource.properties.encryption.services) { 235 | resource.properties.encryption.services = new StorageAccountEncryptionServices(); 236 | } 237 | 238 | if(!resource.properties.encryption.services.blob) { 239 | resource.properties.encryption.services.blob = new StorageAccountEncryptionService(); 240 | } 241 | 242 | if(!resource.properties.encryption.services.file) { 243 | resource.properties.encryption.services.file = new StorageAccountEncryptionService(); 244 | } 245 | 246 | resource.properties.encryption.services.blob.enabled = this.state.blobEncryptionParameterName 247 | ? this.getParameterString(this.state.blobEncryptionParameterName) 248 | : this.state.blobEncryption; 249 | 250 | resource.properties.encryption.services.file.enabled = this.state.fileEncryptionParameterName 251 | ? this.getParameterString(this.state.fileEncryptionParameterName) 252 | : this.state.fileEncryption; 253 | 254 | if(!resource.sku) { 255 | resource.sku = new StorageAccountSku(); 256 | } 257 | 258 | resource.sku.name = this.state.skuNameParameterName 259 | ? this.getParameterString(this.state.skuNameParameterName) 260 | : this.state.skuName; 261 | 262 | resource.sku.tier = this.state.skuTierParameterName 263 | ? this.getParameterString(this.state.skuTierParameterName) 264 | : this.state.skuTier; 265 | } 266 | 267 | protected getSpecificNewParameters(): { [index: string]: Parameter; } { 268 | let parametersToCreate: { [index: string]: Parameter; } = {}; 269 | 270 | this.createParameter(this.state.kindParameterName, this.state.kind, "string", StorageAccount.allowedKinds, parametersToCreate); 271 | 272 | this.createParameter(this.state.accessTierParameterName, this.state.accessTier, "string", StorageAccountProperties.allowedAccessTiers, parametersToCreate); 273 | 274 | this.createParameter(this.state.httpsOnlyParameterName, this.state.httpsOnly, "boolean", [], parametersToCreate); 275 | 276 | this.createParameter(this.state.blobEncryptionParameterName, this.state.blobEncryption, "boolean", [], parametersToCreate); 277 | 278 | this.createParameter(this.state.fileEncryptionParameterName, this.state.fileEncryption, "boolean", [], parametersToCreate); 279 | 280 | this.createParameter(this.state.skuNameParameterName, this.state.skuName, "string", StorageAccountSku.allowedNames, parametersToCreate); 281 | 282 | this.createParameter(this.state.skuTierParameterName, this.state.skuTier, "string", StorageAccountSku.allowedTiers, parametersToCreate); 283 | 284 | return parametersToCreate; 285 | } 286 | 287 | getSpecificMarkup(): JSX.Element { 288 | const parameters = Object.keys(this.props.template.parameters); 289 | const variables = Object.keys(this.props.template.variables); 290 | 291 | let httpsOnly = this.state.httpsOnly === true; 292 | let blobEncryption = this.state.blobEncryption === true; 293 | let fileEncryption = this.state.fileEncryption === true; 294 | 295 | return ( 296 | Kind* 297 | 298 | 299 | 300 | 301 | Account Type* 302 | 303 | 304 | 305 | 306 | Account Tier* 307 | 308 | 309 | 310 | 311 | Access tier 312 | 313 | 314 | 315 | 316 | Only allow HTTPS connections? 317 | 318 | 319 | this.onHttpsOnlyUpdated(e.currentTarget.checked)} /> Only allow HTTPS connections 320 | 321 | 322 | 323 | Encrypt blob storage? 324 | 325 | 326 | this.onBlobEncryptionUpdated(e.currentTarget.checked)} /> Encrypt blob storage 327 | 328 | 329 | 330 | Encrypt file storage? 331 | 332 | 333 | this.onFileEncryptionUpdated(e.currentTarget.checked)} /> Encrypt file storage 334 | 335 | 336 | ); 337 | } 338 | } --------------------------------------------------------------------------------