├── .bowerrc ├── .deployment ├── .editorconfig ├── .gitignore ├── .jshintrc ├── .travis.yml ├── .vscode ├── launch.json └── tasks.json ├── .yo-rc.json ├── LICENSE ├── arm-diagram.jpg ├── bower.json ├── deploy.cmd ├── e2e ├── .jshintrc ├── main.po.js └── main.spec.js ├── gulp ├── .jshintrc ├── build.js ├── conf.js ├── e2e-tests.js ├── inject.js ├── scripts.js ├── server.js ├── styles.js ├── unit-tests.js └── watch.js ├── gulpfile.js ├── karma.conf.js ├── package.json ├── protractor.conf.js ├── readme.md ├── scripts └── deploy.sh ├── src ├── Web.config ├── app │ ├── createVisualizerButton │ │ ├── createVisualizerButton.controller.ts │ │ └── createVisualizerButton.html │ ├── directives │ │ └── FileSelectDirective.ts │ ├── index.module.ts │ ├── index.route.ts │ ├── index.scss │ ├── index.ts │ ├── main │ │ ├── ArmTemplate.spec.js │ │ ├── ArmTemplate.ts │ │ ├── DependencyId.ts │ │ ├── Expression.spec.js │ │ ├── Expression.ts │ │ ├── ExpressionParser.spec.js │ │ ├── ExpressionParser.ts │ │ ├── Parameter.ts │ │ ├── Resource.spec.js │ │ ├── Resource.ts │ │ ├── ResourceShape.ts │ │ ├── ResourceShapeLink.ts │ │ ├── Telemetry.ts │ │ ├── ToolboxITems.ts │ │ ├── ToolboxResource.ts │ │ ├── graph.ts │ │ ├── main.controller.ts │ │ ├── main.html │ │ ├── sampleARM.ts │ │ ├── templateTests │ │ │ ├── 201-1-vm-loadbalancer-2-nics.spec.js │ │ │ ├── 201-alert-to-queue-with-logic-app.spec.js │ │ │ ├── 201-vmss-windows-nat.spec.js │ │ │ └── 401gen.spec.js │ │ └── toolbox.scss │ ├── openExistingTemplateDialog │ │ ├── OpenDialog.controller.ts │ │ └── OpenDialog.html │ ├── portalUIEditor │ │ ├── PortalUIEditorDialog.controller.ts │ │ └── PortalUIEditorDialog.html │ ├── quickstartLoadDialog │ │ ├── DialogResult.ts │ │ ├── GithubTemplateReader.ts │ │ ├── QuickstartLoadDialog.controller.ts │ │ └── QuickstartLoadDialog.html │ ├── resourceEditorDialog │ │ ├── ResourceEditor.controller.ts │ │ └── ResourceEditor.html │ ├── templateParameterEditor │ │ ├── ParameterModelItem.ts │ │ ├── ParameterValueManager.ts │ │ ├── ParameterValuesTemplate.ts │ │ ├── TemplateParameterManager.ts │ │ ├── TemplateProperties.html │ │ └── templateParameterEditor.controller.ts │ └── vendor.scss ├── assets │ ├── images │ │ ├── angular.png │ │ ├── bootstrap.png │ │ ├── browsersync.png │ │ ├── gulp.png │ │ ├── jasmine.png │ │ ├── karma.png │ │ ├── node-sass.png │ │ ├── protractor.png │ │ ├── ui-bootstrap.png │ │ └── yeoman.png │ ├── toolbox-data │ │ ├── Microsoft.Cdn.Profiles.Endpoints.json │ │ ├── Microsoft.Cdn.Profiles.json │ │ ├── Microsoft.Compute.virtualMachines.json │ │ ├── Microsoft.Network.loadBalancers.json │ │ ├── Microsoft.Network.networkInterfaces.json │ │ ├── Microsoft.Network.publicIPAddresses.json │ │ ├── Microsoft.Network.virtualNetworks.json │ │ └── Microsoft.Storage.storageAccounts.json │ └── toolbox-icons │ │ ├── API App.png │ │ ├── API Management.png │ │ ├── Access Control.png │ │ ├── App Service.png │ │ ├── Application Insights.png │ │ ├── Autoscaling.png │ │ ├── Availability Set.PNG │ │ ├── Azure (automatic) load balancer.png │ │ ├── Azure Active Directory.png │ │ ├── Azure Batch.png │ │ ├── Azure Cache including Redis.png │ │ ├── Azure Certificate.png │ │ ├── Azure DNS.png │ │ ├── Azure Files Service.png │ │ ├── Azure Marketplace.png │ │ ├── Azure Rights Management (RMS).png │ │ ├── Azure SDK.png │ │ ├── Azure SQL Database.png │ │ ├── Azure Search.png │ │ ├── Azure alert.png │ │ ├── Azure automation.png │ │ ├── Azure load balancer (feature).png │ │ ├── Azure load balancer.png │ │ ├── Azure subscription.png │ │ ├── Backup Service.png │ │ ├── Bitbucket code source.png │ │ ├── BizTalk Services.png │ │ ├── Cloud Service.png │ │ ├── Cloud, Office 365.png │ │ ├── CodePlex.png │ │ ├── Content Delivery Network (CDN).png │ │ ├── Data Factory.png │ │ ├── Default Resource.png │ │ ├── Deployment.png │ │ ├── DocumentDB.png │ │ ├── Dropbox code source.png │ │ ├── Event Hubs.png │ │ ├── ExpressRoute.png │ │ ├── Git repository.png │ │ ├── GitHub.png │ │ ├── HDInsight.png │ │ ├── Health monitoring.png │ │ ├── Healthy.png │ │ ├── Hybrid Connections (BizTalk).png │ │ ├── Hybrid connection manager for BizTalk Hybrid Connection.png │ │ ├── IoT.png │ │ ├── Key Vault.png │ │ ├── Logic App.png │ │ ├── Machine Learning.png │ │ ├── Media Services.png │ │ ├── Microsoft Azure.png │ │ ├── Microsoft account.png │ │ ├── Mobile App (was Mobile Services).png │ │ ├── Mobile Engagement.png │ │ ├── Multi-Factor Authentication.png │ │ ├── MySQL database.png │ │ ├── NIC.PNG │ │ ├── Notification Hubs.png │ │ ├── Notification topic.png │ │ ├── OS image.png │ │ ├── Office 365 subscription.png │ │ ├── Office 365.png │ │ ├── Operational Insights.png │ │ ├── RemoteApp.png │ │ ├── Resource group.png │ │ ├── SQL Data Sync.png │ │ ├── SQL Database (generic).png │ │ ├── Scheduler.png │ │ ├── Service Bus Queue.png │ │ ├── Service Bus Relay.png │ │ ├── Service Bus Topic.png │ │ ├── Service Bus.png │ │ ├── Service Endpoint.png │ │ ├── Service Fabric.png │ │ ├── Service package.png │ │ ├── Site Recovery.png │ │ ├── Startup task.png │ │ ├── StorSimple.png │ │ ├── Storage (Azure).png │ │ ├── Storage blob.png │ │ ├── Storage queue.png │ │ ├── Storage table.png │ │ ├── Stream Analytics.png │ │ ├── Traffic Manager.png │ │ ├── Unidentified feature object.png │ │ ├── VHD data disk.png │ │ ├── VHD.png │ │ ├── VM symbol only.png │ │ ├── VPN Gateway.png │ │ ├── Virtual Network.png │ │ ├── Virtual machine container.png │ │ ├── Virtual machine.png │ │ ├── Visual Studio Online.png │ │ ├── Web App (was Websites).png │ │ ├── Web role.png │ │ ├── Web roles.png │ │ ├── WebJobs.png │ │ ├── Work account.png │ │ ├── Worker role.png │ │ ├── Worker roles.png │ │ ├── cdn.png │ │ ├── rdp remoting file.png │ │ └── rpd Remoting file.png ├── favicon.ico ├── index.html ├── tsconfig.json └── visualizebutton.png ├── tslint.json └── typings.json /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.deployment: -------------------------------------------------------------------------------- 1 | [config] 2 | command = deploy.cmd -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studo 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | *_i.c 42 | *_p.c 43 | *_i.h 44 | *.ilk 45 | *.meta 46 | *.obj 47 | *.pch 48 | *.pdb 49 | *.pgc 50 | *.pgd 51 | *.rsp 52 | *.sbr 53 | *.tlb 54 | *.tli 55 | *.tlh 56 | *.tmp 57 | *.tmp_proj 58 | *.log 59 | *.vspscc 60 | *.vssscc 61 | .builds 62 | *.pidb 63 | *.svclog 64 | *.scc 65 | 66 | # Chutzpah Test files 67 | _Chutzpah* 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | *.cachefile 76 | 77 | # Visual Studio profiler 78 | *.psess 79 | *.vsp 80 | *.vspx 81 | 82 | # TFS 2012 Local Workspace 83 | $tf/ 84 | 85 | # Guidance Automation Toolkit 86 | *.gpState 87 | 88 | # ReSharper is a .NET coding add-in 89 | _ReSharper*/ 90 | *.[Rr]e[Ss]harper 91 | *.DotSettings.user 92 | 93 | # JustCode is a .NET coding addin-in 94 | .JustCode 95 | 96 | # TeamCity is a build add-in 97 | _TeamCity* 98 | 99 | # DotCover is a Code Coverage Tool 100 | *.dotCover 101 | 102 | # NCrunch 103 | _NCrunch_* 104 | .*crunch*.local.xml 105 | 106 | # MightyMoose 107 | *.mm.* 108 | AutoTest.Net/ 109 | 110 | # Web workbench (sass) 111 | .sass-cache/ 112 | 113 | # Installshield output folder 114 | [Ee]xpress/ 115 | 116 | # DocProject is a documentation generator add-in 117 | DocProject/buildhelp/ 118 | DocProject/Help/*.HxT 119 | DocProject/Help/*.HxC 120 | DocProject/Help/*.hhc 121 | DocProject/Help/*.hhk 122 | DocProject/Help/*.hhp 123 | DocProject/Help/Html2 124 | DocProject/Help/html 125 | 126 | # Click-Once directory 127 | publish/ 128 | 129 | # Publish Web Output 130 | *.[Pp]ublish.xml 131 | *.azurePubxml 132 | # TODO: Comment the next line if you want to checkin your web deploy settings 133 | # but database connection strings (with potential passwords) will be unencrypted 134 | *.pubxml 135 | *.publishproj 136 | 137 | # NuGet Packages 138 | *.nupkg 139 | # The packages folder can be ignored because of Package Restore 140 | **/packages/* 141 | # except build/, which is used as an MSBuild target. 142 | !**/packages/build/ 143 | # Uncomment if necessary however generally it will be regenerated when needed 144 | #!**/packages/repositories.config 145 | 146 | # Windows Azure Build Output 147 | csx/ 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | *.[Cc]ache 155 | ClientBin/ 156 | [Ss]tyle[Cc]op.* 157 | ~$* 158 | *~ 159 | *.dbmdl 160 | *.dbproj.schemaview 161 | *.pfx 162 | *.publishsettings 163 | node_modules/ 164 | bower_components/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | 189 | # Node.js Tools for Visual Studio 190 | .ntvs_analysis.dat 191 | 192 | # Visual Studio 6 build log 193 | *.plg 194 | 195 | # Visual Studio 6 workspace options file 196 | *.opt 197 | 198 | /dist/* 199 | 200 | *.js.map 201 | 202 | /typings/* 203 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globalstrict": true, 3 | "bitwise": true, 4 | "camelcase": true, 5 | "curly": true, 6 | "eqeqeq": true, 7 | "immed": true, 8 | "indent": 2, 9 | "latedef": true, 10 | "newcap": true, 11 | "noarg": true, 12 | "quotmark": "single", 13 | "regexp": true, 14 | "undef": true, 15 | "unused": true, 16 | "strict": true, 17 | "trailing": true, 18 | "smarttabs": true, 19 | "white": true, 20 | "validthis": true, 21 | "globals": { 22 | "angular": false, 23 | // Angular Mocks 24 | "inject": false, 25 | "module": false, 26 | // JASMINE 27 | "describe": false, 28 | "it": false, 29 | "before": false, 30 | "beforeEach": false, 31 | "after": false, 32 | "afterEach": false, 33 | "expect": false 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 4.1 4 | 5 | env: 6 | - CXX=g++-4.8 7 | addons: 8 | apt: 9 | sources: 10 | - ubuntu-toolchain-r-test 11 | packages: 12 | - g++-4.8 13 | 14 | notifications: 15 | slack: armvisualizer:${slack_notification_key} 16 | 17 | before_script: 18 | - npm install -g gulp bower typings typescript 19 | - bower install 20 | - typings install 21 | script: gulp 22 | branches: 23 | only: 24 | - master 25 | 26 | deploy: 27 | skip_cleanup: true 28 | provider: script 29 | script: scripts/deploy.sh 30 | on: 31 | branch: master 32 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | // List of configurations. Add new configurations or edit existing ones. 4 | // ONLY "node" and "mono" are supported, change "type" to switch. 5 | "configurations": [ 6 | { 7 | // Name of configuration; appears in the launch configuration drop down menu. 8 | "name": "Gulp Serve", 9 | // Type of configuration. Possible values: "node", "mono". 10 | "type": "node", 11 | // Workspace relative or absolute path to the program. 12 | "program": "gulpfile.js", 13 | // Automatically stop program after launch. 14 | "stopOnEntry": false, 15 | // Command line arguments passed to the program. 16 | "args": ["serve"], 17 | // Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace. 18 | "cwd": ".", 19 | // Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. 20 | "runtimeExecutable": null, 21 | // Environment variables passed to the program. 22 | "env": { } 23 | }, 24 | { 25 | "name": "Attach", 26 | "type": "node", 27 | // TCP/IP address. Default is "localhost". 28 | "address": "localhost", 29 | // Port to attach to. 30 | "port": 5858 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | 9 | // A task runner that calls the Typescript compiler (tsc) and 10 | // Compiles a HelloWorld.ts program 11 | /* 12 | { 13 | "version": "0.1.0", 14 | 15 | // The command is tsc. Assumes that tsc has been installed using npm install -g typescript 16 | "command": "tsc", 17 | 18 | // The command is a shell script 19 | "isShellCommand": true, 20 | 21 | // Show the output window only if unrecognized errors occur. 22 | "showOutput": "silent", 23 | 24 | // args is the HelloWorld program to compile. 25 | "args": ["HelloWorld.ts"], 26 | 27 | // use the standard tsc problem matcher to find compile problems 28 | // in the output. 29 | "problemMatcher": "$tsc" 30 | } 31 | */ 32 | 33 | // A task runner that calls the Typescript compiler (tsc) and 34 | // compiles based on a tsconfig.json file that is present in 35 | // the root of the folder open in VSCode 36 | /* 37 | { 38 | "version": "0.1.0", 39 | 40 | // The command is tsc. Assumes that tsc has been installed using npm install -g typescript 41 | "command": "tsc", 42 | 43 | // The command is a shell script 44 | "isShellCommand": true, 45 | 46 | // Show the output window only if unrecognized errors occur. 47 | "showOutput": "silent", 48 | 49 | // Tell the tsc compiler to use the tsconfig.json from the open folder. 50 | "args": ["-p", "."], 51 | 52 | // use the standard tsc problem matcher to find compile problems 53 | // in the output. 54 | "problemMatcher": "$tsc" 55 | } 56 | */ 57 | 58 | // A task runner configuration for gulp. Gulp provides a less task 59 | // which compiles less to css. 60 | { 61 | "version": "0.1.0", 62 | "command": "gulp", 63 | "isShellCommand": true, 64 | "tasks": [ 65 | { 66 | "taskName": "build", 67 | // Make this the default build command. 68 | "isBuildCommand": true 69 | // Show the output window only if unrecognized errors occur. 70 | //"showOutput": "silent" 71 | } 72 | ] 73 | } 74 | 75 | // Uncomment the following section to use jake to build a workspace 76 | // cloned from https://github.com/Microsoft/TypeScript.git 77 | /* 78 | { 79 | "version": "0.1.0", 80 | // Task runner is jake 81 | "command": "jake", 82 | // Need to be executed in shell / cmd 83 | "isShellCommand": true, 84 | "showOutput": "silent", 85 | "tasks": [ 86 | { 87 | // TS build command is local. 88 | "taskName": "local", 89 | // Make this the default build command. 90 | "isBuildCommand": true, 91 | // Show the output window only if unrecognized errors occur. 92 | "showOutput": "silent", 93 | // Use the redefined Typescript output problem matcher. 94 | "problemMatcher": [ 95 | "$tsc" 96 | ] 97 | } 98 | ] 99 | } 100 | */ 101 | 102 | // Uncomment the section below to use msbuild and generate problems 103 | // for csc, cpp, tsc and vb. The configuration assumes that msbuild 104 | // is available on the path and a solution file exists in the 105 | // workspace folder root. 106 | /* 107 | { 108 | "version": "0.1.0", 109 | "command": "msbuild", 110 | "args": [ 111 | // Ask msbuild to generate full paths for file names. 112 | "/property:GenerateFullPaths=true" 113 | ], 114 | "taskSelector": "/t:", 115 | "showOutput": "silent", 116 | "tasks": [ 117 | { 118 | "taskName": "build", 119 | // Show the output window only if unrecognized errors occur. 120 | "showOutput": "silent", 121 | // Use the standard MS compiler pattern to detect errors, warnings 122 | // and infos in the output. 123 | "problemMatcher": "$msCompile" 124 | } 125 | ] 126 | } 127 | */ 128 | 129 | // Uncomment the following section to use msbuild which compiles Typescript 130 | // and less files. 131 | /* 132 | { 133 | "version": "0.1.0", 134 | "command": "msbuild", 135 | "args": [ 136 | // Ask msbuild to generate full paths for file names. 137 | "/property:GenerateFullPaths=true" 138 | ], 139 | "taskSelector": "/t:", 140 | "showOutput": "silent", 141 | "tasks": [ 142 | { 143 | "taskName": "build", 144 | // Show the output window only if unrecognized errors occur. 145 | "showOutput": "silent", 146 | // Use the standard MS compiler pattern to detect errors, warnings 147 | // and infos in the output. 148 | "problemMatcher": [ 149 | "$msCompile", 150 | "$lessCompile" 151 | ] 152 | } 153 | ] 154 | } 155 | */ 156 | // A task runner example that defines a problemMatcher inline instead of using 157 | // a predfined one. 158 | /* 159 | { 160 | "version": "0.1.0", 161 | "command": "tsc", 162 | "isShellCommand": true, 163 | "args": ["HelloWorld.ts"], 164 | "showOutput": "silent", 165 | "problemMatcher": { 166 | // The problem is owned by the typescript language service. Ensure that the problems 167 | // are merged with problems produced by Visual Studio's language service. 168 | "owner": "typescript", 169 | // The file name for reported problems is relative to the current working directory. 170 | "fileLocation": ["relative", "${cwd}"], 171 | // The actual pattern to match problems in the output. 172 | "pattern": { 173 | // The regular expression. Matches HelloWorld.ts(2,10): error TS2339: Property 'logg' does not exist on type 'Console'. 174 | "regexp": "^([^\\s].*)\\((\\d+|\\d+,\\d+|\\d+,\\d+,\\d+,\\d+)\\):\\s+(error|warning|info)\\s+(TS\\d+)\\s*:\\s*(.*)$", 175 | // The match group that denotes the file containing the problem. 176 | "file": 1, 177 | // The match group that denotes the problem location. 178 | "location": 2, 179 | // The match group that denotes the problem's severity. Can be omitted. 180 | "severity": 3, 181 | // The match group that denotes the problem code. Can be omitted. 182 | "code": 4, 183 | // The match group that denotes the problem's message. 184 | "message": 5 185 | } 186 | } 187 | } 188 | */ -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-gulp-angular": { 3 | "version": "0.11.0", 4 | "props": { 5 | "angularVersion": "~1.3.4", 6 | "angularModules": [ 7 | { 8 | "key": "animate", 9 | "module": "ngAnimate" 10 | }, 11 | { 12 | "key": "cookies", 13 | "module": "ngCookies" 14 | }, 15 | { 16 | "key": "touch", 17 | "module": "ngTouch" 18 | }, 19 | { 20 | "key": "sanitize", 21 | "module": "ngSanitize" 22 | } 23 | ], 24 | "jQuery": { 25 | "key": "jquery2" 26 | }, 27 | "resource": { 28 | "key": "angular-resource", 29 | "module": "ngResource" 30 | }, 31 | "router": { 32 | "key": "ui-router", 33 | "module": "ui.router" 34 | }, 35 | "ui": { 36 | "key": "bootstrap", 37 | "module": null 38 | }, 39 | "bootstrapComponents": { 40 | "key": "ui-bootstrap", 41 | "module": "ui.bootstrap" 42 | }, 43 | "cssPreprocessor": { 44 | "key": "node-sass", 45 | "extension": "scss" 46 | }, 47 | "jsPreprocessor": { 48 | "key": "none", 49 | "extension": "js", 50 | "srcExtension": "js" 51 | }, 52 | "htmlPreprocessor": { 53 | "key": "none", 54 | "extension": "html" 55 | }, 56 | "foundationComponents": { 57 | "name": null, 58 | "version": null, 59 | "key": null, 60 | "module": null 61 | }, 62 | "paths": { 63 | "src": "src", 64 | "dist": "dist", 65 | "e2e": "e2e", 66 | "tmp": ".tmp" 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /arm-diagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/arm-diagram.jpg -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AzureResourceVisualizer", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "~1.5.7", 6 | "angular-animate": "~1.5.7", 7 | "angular-bootstrap": "~0.13.4", 8 | "angular-cookies": "~1.5.7", 9 | "angular-resource": "~1.5.7", 10 | "angular-sanitize": "~1.5.7", 11 | "angular-touch": "~1.5.7", 12 | "angular-ui-router": "~0.3.1", 13 | "angular-growl-v2": "~0.7.9", 14 | "bootstrap-sass-official": "~3.3.1", 15 | "dagre": "~0.7.1", 16 | "file-saver.js": "~1.20150507.2", 17 | "jointjs": "0.9.6", 18 | "jquery": "2.0.3", 19 | "knockout": "~3.3.0", 20 | "lodash": "~3.10.1", 21 | "underscore": "~1.8.3", 22 | "ace-builds": "~1.2.2", 23 | "angular-ui-ace": "~0.2.3", 24 | "requirejs": "~2.1.22" 25 | }, 26 | "overrides": { 27 | "underscore": { 28 | "main": [] 29 | }, 30 | "ace-builds": { 31 | "main": [ 32 | "src-noconflict/ace.js" 33 | ] 34 | } 35 | }, 36 | "devDependencies": { 37 | "angular-mocks": "~1.5.7" 38 | }, 39 | "resolutions": { 40 | "jquery": "~2.1.1", 41 | "angular": "~1.5.7" 42 | }, 43 | "authors": [ 44 | "Jason Young " 45 | ], 46 | "keywords": [ 47 | "azure", 48 | "arm" 49 | ], 50 | "license": "Apache-2", 51 | "ignore": [ 52 | "**/.*", 53 | "node_modules", 54 | "bower_components", 55 | "test", 56 | "tests" 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /deploy.cmd: -------------------------------------------------------------------------------- 1 | @if "%SCM_TRACE_LEVEL%" NEQ "4" @echo off 2 | 3 | :: ---------------------- 4 | :: KUDU Deployment Script 5 | :: Version: 0.2.2 6 | :: ---------------------- 7 | 8 | :: Prerequisites 9 | :: ------------- 10 | 11 | :: Verify node.js installed 12 | where node 2>nul >nul 13 | IF %ERRORLEVEL% NEQ 0 ( 14 | echo Missing node.js executable, please install node.js, if already installed make sure it can be reached from current environment. 15 | goto error 16 | ) 17 | 18 | :: Setup 19 | :: ----- 20 | 21 | setlocal enabledelayedexpansion 22 | 23 | SET ARTIFACTS=%~dp0%..\artifacts 24 | 25 | IF NOT DEFINED DEPLOYMENT_SOURCE ( 26 | SET DEPLOYMENT_SOURCE=%~dp0%. 27 | ) 28 | 29 | IF NOT DEFINED DEPLOYMENT_TARGET ( 30 | SET DEPLOYMENT_TARGET=%ARTIFACTS%\wwwroot 31 | ) 32 | 33 | IF NOT DEFINED NEXT_MANIFEST_PATH ( 34 | SET NEXT_MANIFEST_PATH=%ARTIFACTS%\manifest 35 | 36 | IF NOT DEFINED PREVIOUS_MANIFEST_PATH ( 37 | SET PREVIOUS_MANIFEST_PATH=%ARTIFACTS%\manifest 38 | ) 39 | ) 40 | 41 | IF NOT DEFINED KUDU_SYNC_CMD ( 42 | :: Install kudu sync 43 | echo Installing Kudu Sync 44 | call npm install kudusync -g --silent 45 | IF !ERRORLEVEL! NEQ 0 goto error 46 | 47 | :: Locally just running "kuduSync" would also work 48 | SET KUDU_SYNC_CMD=%appdata%\npm\kuduSync.cmd 49 | ) 50 | goto Deployment 51 | 52 | :: Utility Functions 53 | :: ----------------- 54 | 55 | :SelectNodeVersion 56 | 57 | IF DEFINED KUDU_SELECT_NODE_VERSION_CMD ( 58 | :: The following are done only on Windows Azure Websites environment 59 | call %KUDU_SELECT_NODE_VERSION_CMD% "%DEPLOYMENT_SOURCE%" "%DEPLOYMENT_TARGET%" "%DEPLOYMENT_TEMP%" 60 | IF !ERRORLEVEL! NEQ 0 goto error 61 | 62 | IF EXIST "%DEPLOYMENT_TEMP%\__nodeVersion.tmp" ( 63 | SET /p NODE_EXE=<"%DEPLOYMENT_TEMP%\__nodeVersion.tmp" 64 | IF !ERRORLEVEL! NEQ 0 goto error 65 | ) 66 | 67 | IF EXIST "%DEPLOYMENT_TEMP%\__npmVersion.tmp" ( 68 | SET /p NPM_JS_PATH=<"%DEPLOYMENT_TEMP%\__npmVersion.tmp" 69 | IF !ERRORLEVEL! NEQ 0 goto error 70 | ) 71 | 72 | IF NOT DEFINED NODE_EXE ( 73 | SET NODE_EXE=node 74 | ) 75 | 76 | SET NPM_CMD="!NODE_EXE!" "!NPM_JS_PATH!" 77 | ) ELSE ( 78 | SET NPM_CMD=npm 79 | SET NODE_EXE=node 80 | ) 81 | 82 | goto :EOF 83 | 84 | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 85 | :: Deployment 86 | :: ---------- 87 | 88 | :Deployment 89 | echo Handling node.js deployment. 90 | 91 | :: 1. KuduSync 92 | IF /I "%IN_PLACE_DEPLOYMENT%" NEQ "1" ( 93 | call :ExecuteCmd "%KUDU_SYNC_CMD%" -v 50 -f "%DEPLOYMENT_SOURCE%" -t "%DEPLOYMENT_TARGET%" -n "%NEXT_MANIFEST_PATH%" -p "%PREVIOUS_MANIFEST_PATH%" -i ".git;.hg;.deployment;deploy.cmd" 94 | IF !ERRORLEVEL! NEQ 0 goto error 95 | ) 96 | 97 | :: 2. Select node version 98 | call :SelectNodeVersion 99 | 100 | :: 3. Install npm packages 101 | IF EXIST "%DEPLOYMENT_TARGET%\package.json" ( 102 | pushd "%DEPLOYMENT_TARGET%" 103 | call :ExecuteCmd !NPM_CMD! set registry http://registry.npmjs.org/ 104 | call :ExecuteCmd !NPM_CMD! install 105 | IF !ERRORLEVEL! NEQ 0 goto error 106 | popd 107 | ) 108 | 109 | 110 | :: 4. Install bower packages 111 | IF EXIST "%DEPLOYMENT_TARGET%\bower.json" ( 112 | pushd "%DEPLOYMENT_TARGET%" 113 | call :ExecuteCmd .\node_modules\.bin\bower install 114 | IF !ERRORLEVEL! NEQ 0 goto error 115 | popd 116 | ) 117 | 118 | IF EXIST "%DEPLOYMENT_TARGET%\typings.json" ( 119 | pushd "%DEPLOYMENT_TARGET%" 120 | call :ExecuteCmd .\node_modules\.bin\typings install 121 | IF !ERRORLEVEL! NEQ 0 goto error 122 | popd 123 | ) 124 | 125 | 126 | :: 5. Run gulp transformations 127 | IF EXIST "%DEPLOYMENT_TARGET%\gulpfile.js" ( 128 | pushd "%DEPLOYMENT_TARGET%" 129 | call :ExecuteCmd .\node_modules\.bin\gulp 130 | IF !ERRORLEVEL! NEQ 0 goto error 131 | popd 132 | ) 133 | 134 | 135 | 136 | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 137 | 138 | :: Post deployment stub 139 | IF DEFINED POST_DEPLOYMENT_ACTION call "%POST_DEPLOYMENT_ACTION%" 140 | IF !ERRORLEVEL! NEQ 0 goto error 141 | 142 | goto end 143 | 144 | :: Execute command routine that will echo out when error 145 | :ExecuteCmd 146 | setlocal 147 | set _CMD_=%* 148 | call %_CMD_% 149 | if "%ERRORLEVEL%" NEQ "0" echo Failed exitCode=%ERRORLEVEL%, command=%_CMD_% 150 | exit /b %ERRORLEVEL% 151 | 152 | :error 153 | endlocal 154 | echo An error has occurred during web site deployment. 155 | call :exitSetErrorLevel 156 | call :exitFromFunction 2>nul 157 | 158 | :exitSetErrorLevel 159 | exit /b 1 160 | 161 | :exitFromFunction 162 | () 163 | 164 | :end 165 | endlocal 166 | echo Finished successfully. 167 | -------------------------------------------------------------------------------- /e2e/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.jshintrc", 3 | "globals": { 4 | "browser": false, 5 | "element": false, 6 | "by": false, 7 | "$": false, 8 | "$$": false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /e2e/main.po.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file uses the Page Object pattern to define the main page for tests 3 | * https://docs.google.com/presentation/d/1B6manhG0zEXkC-H-tPo2vwU06JhL8w9-XCF9oehXzAQ 4 | */ 5 | 6 | 'use strict'; 7 | 8 | var MainPage = function() { 9 | this.jumbEl = element(by.css('.jumbotron')); 10 | this.h1El = this.jumbEl.element(by.css('h1')); 11 | this.imgEl = this.jumbEl.element(by.css('img')); 12 | this.thumbnailEls = element(by.css('body')).all(by.repeater('awesomeThing in awesomeThings')); 13 | }; 14 | 15 | module.exports = new MainPage(); 16 | -------------------------------------------------------------------------------- /e2e/main.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('The main view', function () { 4 | var page; 5 | 6 | beforeEach(function () { 7 | browser.get('http://localhost:3000/index.html'); 8 | page = require('./main.po'); 9 | }); 10 | 11 | it('should include jumbotron with correct data', function() { 12 | expect(page.h1El.getText()).toBe('\'Allo, \'Allo!'); 13 | expect(page.imgEl.getAttribute('src')).toMatch(/assets\/images\/yeoman.png$/); 14 | expect(page.imgEl.getAttribute('alt')).toBe('I\'m Yeoman'); 15 | }); 16 | 17 | it('list more than 5 awesome things', function () { 18 | expect(page.thumbnailEls.count()).toBeGreaterThan(5); 19 | }); 20 | 21 | }); 22 | -------------------------------------------------------------------------------- /gulp/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.jshintrc", 3 | "node": true 4 | } 5 | -------------------------------------------------------------------------------- /gulp/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var gulp = require('gulp'); 5 | var conf = require('./conf'); 6 | 7 | var $ = require('gulp-load-plugins')({ 8 | pattern: ['gulp-*', 'main-bower-files', 'uglify-save-license', 'del'] 9 | }); 10 | 11 | gulp.task('partials', function () { 12 | return gulp.src([ 13 | path.join(conf.paths.src, '/app/**/*.html'), 14 | path.join(conf.paths.tmp, '/serve/app/**/*.html') 15 | ]) 16 | .pipe($.minifyHtml({ 17 | empty: true, 18 | spare: true, 19 | quotes: true 20 | })) 21 | .pipe($.angularTemplatecache('templateCacheHtml.js', { 22 | module: 'ArmViz', 23 | root: 'app' 24 | })) 25 | .pipe(gulp.dest(conf.paths.tmp + '/partials/')); 26 | }); 27 | 28 | gulp.task('html', ['inject', 'partials'], function () { 29 | 30 | //I added this to the standard yeoman template 31 | console.log('Copying HTML partials'); 32 | gulp.src(path.join(conf.paths.src, '/app/**/*.html')) 33 | .pipe(gulp.dest(path.join(conf.paths.dist, '/app/'))); 34 | 35 | var partialsInjectFile = gulp.src(path.join(conf.paths.tmp, '/partials/templateCacheHtml.js'), { read: false }); 36 | var partialsInjectOptions = { 37 | starttag: '', 38 | ignorePath: path.join(conf.paths.tmp, '/partials'), 39 | addRootSlash: false 40 | }; 41 | 42 | var htmlFilter = $.filter('*.html'); 43 | var jsFilter = $.filter('**/*.js'); 44 | var cssFilter = $.filter('**/*.css'); 45 | var assets; 46 | 47 | return gulp.src(path.join(conf.paths.tmp, '/serve/*.html')) 48 | .pipe($.inject(partialsInjectFile, partialsInjectOptions)) 49 | .pipe(assets = $.useref.assets()) 50 | .pipe($.rev()) 51 | .pipe(jsFilter) 52 | .pipe($.ngAnnotate()) 53 | .pipe($.uglify({ preserveComments: $.uglifySaveLicense })).on('error', conf.errorHandler('Uglify')) 54 | .pipe(jsFilter.restore()) 55 | .pipe(cssFilter) 56 | .pipe($.replace('../../bower_components/bootstrap-sass-official/assets/fonts/bootstrap/', '../fonts/')) 57 | .pipe($.csso()) 58 | .pipe(cssFilter.restore()) 59 | .pipe(assets.restore()) 60 | .pipe($.useref()) 61 | .pipe($.revReplace()) 62 | .pipe(htmlFilter) 63 | .pipe($.minifyHtml({ 64 | empty: true, 65 | spare: true, 66 | quotes: true, 67 | conditionals: true 68 | })) 69 | .pipe(htmlFilter.restore()) 70 | .pipe(gulp.dest(path.join(conf.paths.dist, '/'))) 71 | .pipe($.size({ title: path.join(conf.paths.dist, '/'), showFiles: true })); 72 | }); 73 | 74 | // Only applies for fonts from bower dependencies 75 | // Custom fonts are handled by the "other" task 76 | gulp.task('fonts', function () { 77 | return gulp.src($.mainBowerFiles()) 78 | .pipe($.filter('**/*.{eot,svg,ttf,woff,woff2}')) 79 | .pipe($.flatten()) 80 | .pipe(gulp.dest(path.join(conf.paths.dist, '/fonts/'))); 81 | }); 82 | 83 | gulp.task('other', function () { 84 | var fileFilter = $.filter(function (file) { 85 | return file.stat.isFile(); 86 | }); 87 | 88 | return gulp.src([ 89 | path.join(conf.paths.src, '/**/*'), 90 | path.join('!' + conf.paths.src, '/**/*.{html,css,js,scss,ts}') 91 | ]) 92 | .pipe(fileFilter) 93 | .pipe(gulp.dest(path.join(conf.paths.dist, '/'))); 94 | }); 95 | 96 | gulp.task('clean', function (done) { 97 | $.del([path.join(conf.paths.dist, '/'), path.join(conf.paths.tmp, '/')], done); 98 | }); 99 | 100 | gulp.task('build', ['html', 'fonts', 'other']); 101 | -------------------------------------------------------------------------------- /gulp/conf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains the variables used in other gulp files 3 | * which defines tasks 4 | * By design, we only put there very generic config values 5 | * which are used in several places to keep good readability 6 | * of the tasks 7 | */ 8 | 9 | var gutil = require('gulp-util'); 10 | 11 | //Use this flag to know if the build should fail, or just error 12 | exports.watching = false; 13 | 14 | /** 15 | * The main paths of your project handle these with care 16 | */ 17 | exports.paths = { 18 | src: 'src', 19 | dist: 'dist', 20 | tmp: '.tmp', 21 | e2e: 'e2e' 22 | }; 23 | 24 | /** 25 | * Wiredep is the lib which inject bower dependencies in your project 26 | * Mainly used to inject script tags in the index.html but also used 27 | * to inject css preprocessor deps and js files in karma 28 | */ 29 | exports.wiredep = { 30 | exclude: [/bootstrap.js$/, /bootstrap-sass-official\/.*\.js/, /bootstrap\.css/], 31 | directory: 'bower_components' 32 | }; 33 | 34 | /** 35 | * Common implementation for an error handler of a Gulp plugin 36 | */ 37 | exports.errorHandler = function(title) { 38 | 'use strict'; 39 | 40 | return function(err) { 41 | gutil.log(gutil.colors.red('[' + title + ']'), err.toString()); 42 | 43 | if(exports.watching) { 44 | this.emit('end'); 45 | } else { 46 | process.exit(1); 47 | } 48 | 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /gulp/e2e-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var gulp = require('gulp'); 5 | var conf = require('./conf'); 6 | 7 | var browserSync = require('browser-sync'); 8 | 9 | var $ = require('gulp-load-plugins')(); 10 | 11 | // Downloads the selenium webdriver 12 | gulp.task('webdriver-update', $.protractor.webdriver_update); 13 | 14 | gulp.task('webdriver-standalone', $.protractor.webdriver_standalone); 15 | 16 | function runProtractor (done) { 17 | var params = process.argv; 18 | var args = params.length > 3 ? [params[3], params[4]] : []; 19 | 20 | gulp.src(path.join(conf.paths.e2e, '/**/*.js')) 21 | .pipe($.protractor.protractor({ 22 | configFile: 'protractor.conf.js', 23 | args: args 24 | })) 25 | .on('error', function (err) { 26 | // Make sure failed tests cause gulp to exit non-zero 27 | throw err; 28 | }) 29 | .on('end', function () { 30 | // Close browser sync server 31 | browserSync.exit(); 32 | done(); 33 | }); 34 | } 35 | 36 | gulp.task('protractor', ['protractor:src']); 37 | gulp.task('protractor:src', ['serve:e2e', 'webdriver-update'], runProtractor); 38 | gulp.task('protractor:dist', ['serve:e2e-dist', 'webdriver-update'], runProtractor); 39 | -------------------------------------------------------------------------------- /gulp/inject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var gulp = require('gulp'); 5 | var conf = require('./conf'); 6 | 7 | var $ = require('gulp-load-plugins')(); 8 | 9 | var wiredep = require('wiredep').stream; 10 | var _ = require('lodash'); 11 | 12 | gulp.task('inject', ['scripts', 'styles'], function () { 13 | var injectStyles = gulp.src([ 14 | path.join(conf.paths.tmp, '/serve/app/**/*.css'), 15 | path.join('!' + conf.paths.tmp, '/serve/app/vendor.css') 16 | ], { read: false }); 17 | 18 | var injectScripts = gulp.src([ 19 | path.join(conf.paths.src, '/app/**/*.module.js'), 20 | path.join(conf.paths.src, '/app/**/*.js'), 21 | path.join(conf.paths.tmp, '/serve/app/**/*.module.js'), 22 | path.join(conf.paths.tmp, '/serve/app/**/*.js'), 23 | path.join('!' + conf.paths.src, '/app/**/*.spec.js'), 24 | path.join('!' + conf.paths.src, '/app/**/*.mock.js') 25 | ], { read: false }); 26 | 27 | var injectOptions = { 28 | ignorePath: [conf.paths.src, path.join(conf.paths.tmp, '/serve')], 29 | addRootSlash: false 30 | }; 31 | 32 | return gulp.src(path.join(conf.paths.src, '/*.html')) 33 | .pipe($.inject(injectStyles, injectOptions)) 34 | .pipe($.inject(injectScripts, injectOptions)) 35 | .pipe(wiredep(_.extend({}, conf.wiredep))) 36 | .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve'))); 37 | }); 38 | -------------------------------------------------------------------------------- /gulp/scripts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var gulp = require('gulp'); 5 | var conf = require('./conf'); 6 | 7 | var browserSync = require('browser-sync'); 8 | 9 | var $ = require('gulp-load-plugins')(); 10 | 11 | //console.log(JSON.stringify($)); 12 | 13 | var tsProject = $.typescript.createProject({ 14 | target: 'es5', 15 | sortOutput: true 16 | }); 17 | 18 | gulp.task('scripts', function () { 19 | //Not a fan of putting these in the root, but it's to get the ace require to work 20 | gulp.src(path.join('bower_components', '/ace-builds/src-noconflict/**/*json*.*')) 21 | .pipe(gulp.dest(path.join(conf.paths.dist, '/'))); 22 | 23 | return gulp.src([ 24 | path.join(conf.paths.src, '/app/**/*.ts'), 25 | path.join('!' + conf.paths.src, '/app/**/*.spec.ts')]) 26 | .pipe($.sourcemaps.init()) 27 | .pipe($.tslint()) 28 | .pipe($.tslint.report('prose', { emitError: false })) 29 | .pipe($.typescript(tsProject)).on('error', conf.errorHandler('TypeScript')) 30 | .pipe($.concat('index.module.js')) 31 | //.pipe($.sourcemaps.write()) 32 | .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve/app'))) 33 | .pipe(browserSync.reload({ stream: true })) 34 | .pipe($.size()) 35 | }); 36 | -------------------------------------------------------------------------------- /gulp/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var gulp = require('gulp'); 5 | var conf = require('./conf'); 6 | 7 | var browserSync = require('browser-sync'); 8 | var browserSyncSpa = require('browser-sync-spa'); 9 | 10 | var util = require('util'); 11 | 12 | var proxyMiddleware = require('http-proxy-middleware'); 13 | 14 | function browserSyncInit(baseDir, browser) { 15 | browser = browser === undefined ? 'default' : browser; 16 | 17 | var routes = null; 18 | if(baseDir === conf.paths.src || (util.isArray(baseDir) && baseDir.indexOf(conf.paths.src) !== -1)) { 19 | routes = { 20 | '/bower_components': 'bower_components' 21 | }; 22 | } 23 | 24 | var server = { 25 | baseDir: baseDir, 26 | routes: routes 27 | }; 28 | 29 | /* 30 | * You can add a proxy to your backend by uncommenting the line bellow. 31 | * You just have to configure a context which will we redirected and the target url. 32 | * Example: $http.get('/users') requests will be automatically proxified. 33 | * 34 | * For more details and option, https://github.com/chimurai/http-proxy-middleware/blob/v0.0.5/README.md 35 | */ 36 | // server.middleware = proxyMiddleware('/users', {target: 'http://jsonplaceholder.typicode.com', proxyHost: 'jsonplaceholder.typicode.com'}); 37 | 38 | browserSync.instance = browserSync.init({ 39 | startPath: '/', 40 | server: server, 41 | browser: browser 42 | }); 43 | } 44 | 45 | browserSync.use(browserSyncSpa({ 46 | selector: '[ng-app]'// Only needed for angular apps 47 | })); 48 | 49 | gulp.task('serve', ['watch'], function () { 50 | browserSyncInit([path.join(conf.paths.tmp, '/serve'), conf.paths.src]); 51 | }); 52 | 53 | gulp.task('serve:dist', ['build'], function () { 54 | browserSyncInit(conf.paths.dist); 55 | }); 56 | 57 | gulp.task('serve:e2e', ['inject'], function () { 58 | browserSyncInit([conf.paths.tmp + '/serve', conf.paths.src], []); 59 | }); 60 | 61 | gulp.task('serve:e2e-dist', ['build'], function () { 62 | browserSyncInit(conf.paths.dist, []); 63 | }); 64 | -------------------------------------------------------------------------------- /gulp/styles.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var gulp = require('gulp'); 5 | var conf = require('./conf'); 6 | 7 | var browserSync = require('browser-sync'); 8 | 9 | var $ = require('gulp-load-plugins')(); 10 | 11 | var wiredep = require('wiredep').stream; 12 | var _ = require('lodash'); 13 | 14 | gulp.task('styles', function () { 15 | var sassOptions = { 16 | style: 'expanded' 17 | }; 18 | 19 | var injectFiles = gulp.src([ 20 | path.join(conf.paths.src, '/app/**/*.scss'), 21 | path.join('!' + conf.paths.src, '/app/index.scss') 22 | ], { read: false }); 23 | 24 | var injectOptions = { 25 | transform: function(filePath) { 26 | filePath = filePath.replace(conf.paths.src + '/app/', ''); 27 | return '@import "' + filePath + '";'; 28 | }, 29 | starttag: '// injector', 30 | endtag: '// endinjector', 31 | addRootSlash: false 32 | }; 33 | 34 | return gulp.src([ 35 | path.join(conf.paths.src, '/app/index.scss') 36 | ]) 37 | 38 | .pipe($.inject(injectFiles, injectOptions)) 39 | .pipe(wiredep(_.extend({}, conf.wiredep))) 40 | .pipe($.sourcemaps.init()) 41 | .pipe($.sass(sassOptions)).on('error', conf.errorHandler('Sass')) 42 | .pipe($.autoprefixer()).on('error', conf.errorHandler('Autoprefixer')) 43 | .pipe($.sourcemaps.write()) 44 | .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve/app/'))) 45 | .pipe(browserSync.reload({ stream: true })); 46 | }); 47 | -------------------------------------------------------------------------------- /gulp/unit-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var gulp = require('gulp'); 5 | var conf = require('./conf'); 6 | 7 | var karma = require('karma'); 8 | 9 | function runTests (singleRun, done) { 10 | karma.server.start({ 11 | configFile: path.join(__dirname, '/../karma.conf.js'), 12 | singleRun: singleRun, 13 | autoWatch: !singleRun 14 | }, function() { 15 | done(); 16 | }); 17 | } 18 | 19 | gulp.task('test', ['scripts'], function(done) { 20 | runTests(true, done); 21 | }); 22 | 23 | gulp.task('test:auto', ['watch'], function(done) { 24 | runTests(false, done); 25 | }); 26 | -------------------------------------------------------------------------------- /gulp/watch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var gulp = require('gulp'); 5 | var conf = require('./conf'); 6 | 7 | var browserSync = require('browser-sync'); 8 | 9 | function isOnlyChange(event) { 10 | return event.type === 'changed'; 11 | } 12 | 13 | gulp.task('setWatchFlag', function() { 14 | //Don't fail on watch errors 15 | conf.watching = true; 16 | }); 17 | 18 | gulp.task('watch', ['setWatchFlag', 'inject'], function () { 19 | gulp.watch([path.join(conf.paths.src, '/*.html'), 'bower.json'], ['inject']); 20 | 21 | gulp.watch([ 22 | path.join(conf.paths.src, '/app/**/*.css'), 23 | path.join(conf.paths.src, '/app/**/*.scss') 24 | ], function(event) { 25 | if(isOnlyChange(event)) { 26 | gulp.start('styles'); 27 | } else { 28 | gulp.start('inject'); 29 | } 30 | }); 31 | 32 | gulp.watch([ 33 | path.join(conf.paths.src, '/app/**/*.js'), 34 | path.join(conf.paths.src, '/app/**/*.ts') 35 | ], function(event) { 36 | if(isOnlyChange(event)) { 37 | gulp.start('scripts'); 38 | } else { 39 | gulp.start('inject'); 40 | } 41 | }); 42 | 43 | gulp.watch(path.join(conf.paths.src, '/app/**/*.html'), function(event) { 44 | browserSync.reload(event.path); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your gulpfile! 3 | * The gulp tasks are splitted in several files in the gulp directory 4 | * because putting all here was really too long 5 | */ 6 | 7 | 'use strict'; 8 | 9 | var gulp = require('gulp'); 10 | var wrench = require('wrench'); 11 | 12 | /** 13 | * This will load all js or coffee files in the gulp directory 14 | * in order to load all gulp tasks 15 | */ 16 | wrench.readdirSyncRecursive('./gulp').filter(function(file) { 17 | return (/\.(js|coffee)$/i).test(file); 18 | }).map(function(file) { 19 | require('./gulp/' + file); 20 | }); 21 | 22 | 23 | /** 24 | * Default task clean temporaries directories and launch the 25 | * main optimization build task 26 | */ 27 | gulp.task('default', ['clean'], function () { 28 | gulp.start('build'); 29 | }); 30 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var conf = require('./gulp/conf'); 5 | 6 | var _ = require('lodash'); 7 | var wiredep = require('wiredep'); 8 | 9 | function listFiles() { 10 | var wiredepOptions = _.extend({}, conf.wiredep, { 11 | dependencies: true, 12 | devDependencies: true 13 | }); 14 | 15 | return wiredep(wiredepOptions).js 16 | .concat([ 17 | path.join(conf.paths.tmp, '/serve/app/index.module.js'), 18 | path.join(conf.paths.src, '/**/*.spec.js'), 19 | path.join(conf.paths.src, '/**/*.mock.js'), 20 | path.join(conf.paths.src, '/**/*.html') 21 | ]); 22 | } 23 | 24 | module.exports = function(config) { 25 | 26 | var configuration = { 27 | files: listFiles(), 28 | 29 | singleRun: true, 30 | 31 | autoWatch: false, 32 | 33 | frameworks: ['jasmine'], 34 | 35 | ngHtml2JsPreprocessor: { 36 | stripPrefix: 'src/', 37 | moduleName: 'ArmViz' 38 | }, 39 | 40 | browsers : ['PhantomJS'], 41 | 42 | plugins : [ 43 | 'karma-phantomjs-launcher', 44 | 'karma-jasmine', 45 | 'karma-ng-html2js-preprocessor' 46 | ], 47 | 48 | preprocessors: { 49 | 'src/**/*.html': ['ng-html2js'] 50 | } 51 | }; 52 | 53 | // This block is needed to execute Chrome on Travis 54 | // If you ever plan to use Chrome and Travis, you can keep it 55 | // If not, you can safely remove it 56 | // https://github.com/karma-runner/karma/issues/1144#issuecomment-53633076 57 | if(configuration.browsers[0] === 'Chrome' && process.env.TRAVIS) { 58 | configuration.customLaunchers = { 59 | 'chrome-travis-ci': { 60 | base: 'Chrome', 61 | flags: ['--no-sandbox'] 62 | } 63 | }; 64 | configuration.browsers = ['chrome-travis-ci']; 65 | } 66 | 67 | config.set(configuration); 68 | }; 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AzureResourceVisualizer", 3 | "version": "0.0.0", 4 | "dependencies": {}, 5 | "scripts": { 6 | "test": "gulp test" 7 | }, 8 | "devDependencies": { 9 | "bower": "^1.5.2", 10 | "browser-sync": "~2.1.4", 11 | "browser-sync-spa": "~1.0.2", 12 | "chalk": "~0.5.1", 13 | "concat-stream": "~1.4.7", 14 | "del": "~1.2.0", 15 | "gulp": "~3.9.0", 16 | "gulp-header": "1.8.2", 17 | "gulp-angular-templatecache": "~1.6.0", 18 | "gulp-autoprefixer": "~2.3.1", 19 | "gulp-concat": "~2.5.2", 20 | "gulp-csso": "~1.0.0", 21 | "gulp-debug": "^2.1.1", 22 | "gulp-filter": "~2.0.2", 23 | "gulp-flatten": "~0.0.4", 24 | "gulp-inject": "~1.3.1", 25 | "gulp-jshint": "~2.0.1", 26 | "gulp-load-plugins": "~0.10.0", 27 | "gulp-minify-html": "~1.0.3", 28 | "gulp-ng-annotate": "~1.0.0", 29 | "gulp-protractor": "~1.0.0", 30 | "gulp-rename": "~1.2.2", 31 | "gulp-replace": "~0.5.3", 32 | "gulp-rev": "~5.0.0", 33 | "gulp-rev-replace": "~0.4.2", 34 | "gulp-sass": "~2.0.1", 35 | "gulp-size": "~1.2.1", 36 | "gulp-sourcemaps": "~1.5.2", 37 | "gulp-tslint": "~5.0.0", 38 | "gulp-typescript": "^2.9.2", 39 | "gulp-uglify": "~1.2.0", 40 | "gulp-useref": "~1.2.0", 41 | "gulp-util": "~3.0.5", 42 | "http-proxy": "~1.8.0", 43 | "http-proxy-middleware": "~0.0.5", 44 | "jshint-stylish": "^2.2.0", 45 | "karma": "~0.12.31", 46 | "karma-jasmine": "~0.3.1", 47 | "karma-ng-html2js-preprocessor": "~0.1.2", 48 | "karma-phantomjs-launcher": "~0.1.4", 49 | "lodash": "~3.9.3", 50 | "main-bower-files": "~2.5.0", 51 | "merge-stream": "~0.1.7", 52 | "protractor": "~1.7.0", 53 | "require-dir": "~0.1.0", 54 | "typings": "^1.1.0", 55 | "tslint": "^3.11.0", 56 | "typescript": "^1.6.0", 57 | "uglify-save-license": "~0.4.1", 58 | "wiredep": "~2.2.0", 59 | "wrench": "~1.5.8" 60 | }, 61 | "engines": { 62 | "node": ">=0.10.0" 63 | }, 64 | "main": "gulpfile.js", 65 | "repository": { 66 | "type": "git", 67 | "url": "https://github.com/ytechie/AzureResourceVisualizer" 68 | }, 69 | "keywords": [ 70 | "azure", 71 | "arm" 72 | ], 73 | "author": "Jason Young ", 74 | "license": "Apache-2.0", 75 | "bugs": { 76 | "url": "https://github.com/ytechie/AzureResourceVisualizer/issues" 77 | }, 78 | "homepage": "https://github.com/ytechie/AzureResourceVisualizer" 79 | } 80 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var paths = require('./.yo-rc.json')['generator-gulp-angular'].props.paths; 4 | 5 | // An example configuration file. 6 | exports.config = { 7 | // The address of a running selenium server. 8 | //seleniumAddress: 'http://localhost:4444/wd/hub', 9 | //seleniumServerJar: deprecated, this should be set on node_modules/protractor/config.json 10 | 11 | // Capabilities to be passed to the webdriver instance. 12 | capabilities: { 13 | 'browserName': 'chrome' 14 | }, 15 | 16 | baseUrl: 'http://localhost:3000', 17 | 18 | // Spec patterns are relative to the current working directly when 19 | // protractor is called. 20 | specs: [paths.e2e + '/**/*.js'], 21 | 22 | // Options to be passed to Jasmine-node. 23 | jasmineNodeOpts: { 24 | showColors: true, 25 | defaultTimeoutInterval: 30000 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/ytechie/AzureResourceVisualizer.svg?branch=master)](https://travis-ci.org/ytechie/AzureResourceVisualizer) 2 | 3 | # Azure Resource Visualizer 4 | 5 | A visual way of visualizing, editing, and saving Azure Resource Manager Templates. 6 | 7 | ![Azure Resource Manager Diagram](arm-diagram.jpg) 8 | 9 | Here is a quick [YouTube screencast](https://www.youtube.com/watch?v=5xP1-IrtNMU) I put together to give you an overview of the project. 10 | 11 | 12 | 13 | ## Future / Project Status 14 | 15 | There is still much to be done. Right now, the resource relationship parser is not perfect, but there is a plan in place to support all possible ARM Template dependencies. Also, the creation experience is currently lacking. 16 | 17 | We're using a [public Trello board](https://trello.com/b/41RiUCGs/azure-resource-visualizer) for managing the features and roadmap. 18 | 19 | ## Installation 20 | 21 | npm install 22 | npm install -g gulp bower typings typescript 23 | bower install 24 | typings install 25 | 26 | ## Running 27 | 28 | Build the website to serve it locally: 29 | 30 | gulp serve 31 | 32 | ### Troubleshooting 33 | 34 | **Problem:** Old Typescript version installed. (use `tsc-v` to check) 35 | 36 | **Solution:** Delete TypeScript from the `C:\Program Files (x86)\Microsoft SDKs\TypeScript` 37 | 38 | ## License 39 | 40 | Available [here](https://github.com/ytechie/AzureResourceVisualizer/blob/master/LICENSE) 41 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd dist 4 | git init 5 | 6 | git config user.name "Travis CI" 7 | git config user.email "jason@ytechie.com" 8 | 9 | git add . 10 | git commit -m "Deploy" 11 | 12 | # We redirect any output to 13 | # /dev/null to hide any sensitive credential data that might otherwise be exposed. 14 | git push --force --quiet "https://${git_user}:${git_password}@${git_target}" master:master > /dev/null 2>&1 15 | -------------------------------------------------------------------------------- /src/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/app/createVisualizerButton/createVisualizerButton.controller.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | module ArmViz { 4 | export class CreateVisualizerButtonController { 5 | private $scope: any; 6 | private $modalInstance: any; 7 | private $http; 8 | 9 | buttonHtml: string; 10 | 11 | /** @ngInject */ 12 | constructor($scope, $modalInstance, $http, loadUrl) { 13 | this.$scope = $scope; 14 | this.$modalInstance = $modalInstance; 15 | this.$http = $http; 16 | 17 | this.buttonHtml = '' + '\n'; 18 | this.buttonHtml += ' ' + '\n'; 19 | this.buttonHtml += ''; 20 | } 21 | 22 | cancel() { 23 | this.$modalInstance.dismiss('cancel'); 24 | } 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/app/createVisualizerButton/createVisualizerButton.html: -------------------------------------------------------------------------------- 1 | 4 | 7 | 10 | -------------------------------------------------------------------------------- /src/app/directives/FileSelectDirective.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | module Directives { 4 | /** @ngInject */ 5 | export function ngFileSelect(): ng.IDirective { 6 | return { 7 | link: function ($scope, el) { 8 | el.bind("change", (e) => { 9 | ($scope).file = ((e.srcElement || e.target)).files[0]; 10 | $scope.$apply(); 11 | }); 12 | } 13 | }; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /src/app/index.module.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | /// 4 | /// 5 | /// 6 | /// 7 | 8 | module ArmViz.Module { 9 | var module = angular.module('ArmViz', [ 10 | 'ngAnimate', 11 | 'ngCookies', 12 | 'ngTouch', 13 | 'ngSanitize', 14 | 'ngResource', 15 | 'ngCookies', 16 | 'ui.router', 17 | 'ui.bootstrap', 18 | 'ui.ace', 19 | 'angular-growl' 20 | ]); 21 | 22 | export function start() { 23 | module 24 | .config(ArmViz.RouterConfig) 25 | 26 | //These names much match apparently 27 | .controller('MainCtrl', ArmViz.MainCtrl) 28 | 29 | .controller('OpenDialogController', OpenDialog.Controller) 30 | .controller('QuickstartLoadDialog', ArmViz.QuickstartLoadDialog) 31 | .controller('TemplateParameterManager', TemplateParameterEditor.Controller) 32 | .controller('OpenDialogController', OpenDialog.Controller) 33 | .controller('ResourceEditorController', ArmViz.ResourceEditorController) 34 | .controller('CreateVisualizerButtonController', ArmViz.CreateVisualizerButtonController) 35 | .controller('PortalUIEditorController', PortalUIEditor.ResourceEditorController) 36 | 37 | .directive('ngFileSelect', Directives.ngFileSelect) 38 | ; //this is intentional :-) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/app/index.route.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | /// 4 | 5 | module ArmViz { 6 | 'use strict'; 7 | 8 | export class RouterConfig { 9 | /** @ngInject */ 10 | constructor($stateProvider: ng.ui.IStateProvider, $urlRouterProvider: ng.ui.IUrlRouterProvider) { 11 | $stateProvider 12 | .state('home', { 13 | url: '/?load&hideChrome', 14 | templateUrl: 'app/main/main.html', 15 | controller: 'MainCtrl', 16 | controllerAs: 'main' 17 | }); 18 | 19 | $urlRouterProvider.otherwise('/'); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/index.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * If you want to override some bootstrap variables, you have to change values here. 3 | * The list of variables are listed here bower_components/bootstrap-sass-official/assets/stylesheets/bootstrap/_variables.scss 4 | */ 5 | $navbar-inverse-link-color: #5AADBB; 6 | $icon-font-path: "../../bower_components/bootstrap-sass-official/assets/fonts/bootstrap/"; 7 | 8 | /** 9 | * Do not remove this comments bellow. It's the markers used by wiredep to inject 10 | * sass dependencies when defined in the bower.json of your dependencies 11 | */ 12 | // bower:scss 13 | // endbower 14 | 15 | .growl-container.growl-fixed.top-right { 16 | top: 60px; 17 | right: 20px; 18 | } 19 | 20 | .growl-title { 21 | font-weight: bold; 22 | padding-bottom: 6px; 23 | } 24 | 25 | .browsehappy { 26 | margin: 0.2em 0; 27 | background: #ccc; 28 | color: #000; 29 | padding: 0.2em 0; 30 | } 31 | 32 | .thumbnail { 33 | height: 200px; 34 | 35 | img.pull-right { 36 | width: 50px; 37 | } 38 | } 39 | 40 | .smiley { 41 | font-size: 30px; 42 | } 43 | 44 | .link-tools .tool-remove { 45 | display: none 46 | } 47 | 48 | .link .marker-arrowheads { 49 | display: none; 50 | } 51 | 52 | /** 53 | * Do not remove this comments bellow. It's the markers used by gulp-inject to inject 54 | * all your sass files automatically 55 | */ 56 | // injector 57 | // endinjector 58 | -------------------------------------------------------------------------------- /src/app/index.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | module ArmViz { 4 | //Get the toolbox to be 100% of the window height... 5 | 6 | $('#sidebar').height($('#sidebar').siblings('#main-content').height()); 7 | 8 | $(window).resize(function () { 9 | $('#sidebar').height($('#sidebar').siblings('#main-content').height()); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /src/app/main/ArmTemplate.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | /// 4 | /// 5 | 6 | describe('ArmTemplate', function () { 7 | it("Should handle resources without depencies", function () { 8 | var at = new ArmViz.ArmTemplate({}); 9 | var deps = at.getDependencies(new ArmViz.Resource()); 10 | 11 | expect(deps).toEqual([]); 12 | }); 13 | 14 | it('should process concat expression', function () { 15 | var ep = new ArmViz.ExpressionParser(); 16 | var exp = ep.parse("[concat('Microsoft.Network/networkInterfaces/', parameters('nicNamePrefix'))]"); 17 | 18 | var at = new ArmViz.ArmTemplate({}); 19 | var dependsOn = at.resolveDependsOnId(exp); 20 | expect(dependsOn.type).toEqual("Microsoft.Network/networkInterfaces"); 21 | expect(dependsOn.name).toEqual("nicNamePrefix") 22 | }); 23 | 24 | it('should process nested parameters in concat', function () { 25 | var ep = new ArmViz.ExpressionParser(); 26 | var exp = ep.parse("resourceId('Microsoft.Web/sites', concat(parameters('endpointName'), 'gateway'))"); 27 | 28 | var at = new ArmViz.ArmTemplate({}); 29 | var dependsOn = at.resolveDependsOnId(exp); 30 | expect(dependsOn.type).toEqual("Microsoft.Web/sites"); 31 | expect(dependsOn.name).toEqual("endpointNamegateway"); 32 | }); 33 | 34 | it('should resolve name with variables expression with properties', function () { 35 | var source = "[variables('nic').prefix.name]"; 36 | var templateData = { 37 | variables: { 38 | nic: { 39 | prefix: { 40 | name: 'nicName' 41 | } 42 | } 43 | } 44 | }; 45 | 46 | var at = new ArmViz.ArmTemplate(templateData); 47 | var name = at.resolveName(source); 48 | 49 | expect(name).toEqual('nicName'); 50 | }); 51 | 52 | it('should resolve dependencies with concat expression with properties', function () { 53 | var source = "[concat('Microsoft.Compute/virtualMachines/', variables('vm').name)]"; 54 | var templateData = { 55 | variables: { 56 | vm: { 57 | name: 'vmName' 58 | } 59 | } 60 | } 61 | 62 | var at = new ArmViz.ArmTemplate(templateData); 63 | var ep = new ArmViz.ExpressionParser(); 64 | var exp = ep.parse(source); 65 | var dependsOn = at.resolveDependsOnId(exp); 66 | 67 | expect(dependsOn.type).toEqual("Microsoft.Compute/virtualMachines"); 68 | expect(dependsOn.name).toEqual('vmName'); 69 | }); 70 | 71 | it('should multi-nested dependency in concat with properties', function () { 72 | var source = "resourceId('Microsoft.Web/sites', concat(variables('endpoint').name, 'Gateway'))"; 73 | var templateData = { 74 | parameters: { }, 75 | variables: { 76 | endpoint: { 77 | name: "[concat(parameters(endpointPrefix), concat('-', endpointName))]" 78 | } 79 | } 80 | }; 81 | 82 | var at = new ArmViz.ArmTemplate(templateData); 83 | var ep = new ArmViz.ExpressionParser(); 84 | var exp = ep.parse(source); 85 | var dependsOn = at.resolveDependsOnId(exp); 86 | 87 | expect(dependsOn.type).toEqual("Microsoft.Web/sites"); 88 | expect(dependsOn.name).toEqual("endpointPrefix-endpointNameGateway"); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /src/app/main/ArmTemplate.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | module ArmViz { 4 | export interface ArmTemplateInterface { 5 | contentVersion: string; 6 | //Note: The parameter elements are keyed by their name 7 | parameters: Parameter[]; 8 | resources: Resource[]; 9 | variables: any; 10 | } 11 | 12 | export class ArmTemplate { 13 | private templateData: ArmTemplateInterface; 14 | private observableResources: KnockoutObservableArray; 15 | private resolvedNames: { [name: string]: string }; 16 | private resolveErrors: any[]; 17 | 18 | constructor(templateData: ArmTemplateInterface) { 19 | this.templateData = templateData; 20 | this.resolvedNames = {}; 21 | this.resolveErrors = []; 22 | } 23 | 24 | static CreateFromJson(json: string) { 25 | var templateData = JSON.parse(json); 26 | 27 | if (!templateData.contentVersion) { 28 | Telemetry.sendEvent('Error', 'TemplateMissingContentVersion'); 29 | throw new Error('Azure Resource Template JSON did not have a contentVersion property'); 30 | } 31 | 32 | if (!templateData.resources) { 33 | templateData.resources = new Array(); 34 | } 35 | 36 | var armTemplate = new ArmTemplate(templateData); 37 | 38 | return armTemplate; 39 | } 40 | 41 | toJson() { 42 | return JSON.stringify(this.templateData, null, 2); 43 | } 44 | 45 | get resources() { 46 | if (this.observableResources) { 47 | return this.observableResources; 48 | } else { 49 | this.observableResources = ko.observableArray(this.templateData.resources); 50 | return this.observableResources; 51 | } 52 | } 53 | 54 | get parameters(): Parameter[] { 55 | return this.templateData.parameters; 56 | } 57 | 58 | get templateDataObj(): ArmTemplateInterface { 59 | return this.templateData; 60 | } 61 | 62 | checkResolveErrors() { 63 | if (this.resolveErrors.length > 0) { 64 | alert('Sorry we are having trouble parsing this template.\nYou may ignore this and continue editing.'); 65 | this.resolveErrors = []; 66 | } 67 | } 68 | 69 | resolveName(name: string): string { 70 | if (this.resolvedNames[name]) { 71 | return this.resolvedNames[name]; 72 | } 73 | 74 | try { 75 | let ep = new ExpressionParser(); 76 | let exp = ep.parse(name); 77 | this.resolvedNames[name] = this.resolveExpression(exp); 78 | } catch (error) { 79 | this.resolveErrors.push(error); 80 | return name; 81 | } 82 | 83 | return this.resolvedNames[name]; 84 | } 85 | 86 | resolveExpression(expression: Expression): string { 87 | let ret = ""; 88 | 89 | if (expression.operator === 'concat') { 90 | if (expression.operands.length < 1) { 91 | // Note: there might only be one operand. The following two expressions are identical: 92 | // [concat('Microsoft.Network/networkInterfaces/Nic0')] 93 | // [concat('Microsoft.Network/networkInterfaces', 'Nic0')] 94 | console.error("Invalid operation. At least one value required for operation: " + expression.operator); 95 | } 96 | 97 | for (let i = 0; i < expression.operands.length; i++) { 98 | if (expression.operands[i] instanceof Expression) { 99 | ret += this.resolveExpression(expression.operands[i]); 100 | } else { 101 | ret += expression.operands[i]; 102 | } 103 | } 104 | } else if (expression.operator === 'resourceId') { 105 | if (expression.operands.length < 1) { 106 | console.error("Invalid operation. At least one value required for operation: " + expression.operator); 107 | } 108 | 109 | for (let i = 0; i < expression.operands.length; i++) { 110 | if (expression.operands[i] instanceof Expression) { 111 | ret += this.resolveExpression(expression.operands[i]); 112 | } else { 113 | // first parameter of resourceId is resource type 114 | if (i === 0) { 115 | // the resource type can optionally end with a '/', make sure we have one if its not present 116 | let operand = expression.operands[i]; 117 | if (operand.substr(1, operand.length - 1) !== "/") { 118 | expression.operands[i] = operand + "/"; 119 | } 120 | } 121 | 122 | ret += expression.operands[i]; 123 | } 124 | } 125 | } else if (expression.operator === 'variables') { 126 | if (expression.operands.length === 0) { 127 | console.error("no variable name specified"); 128 | } 129 | if (expression.operands.length > 1) { 130 | console.error("too many variable names specified"); 131 | } 132 | 133 | let templateVar: Object = this.templateData.variables[expression.operands[0]]; 134 | if (templateVar) { 135 | for (let property of expression.properties) { 136 | templateVar = templateVar[property]; 137 | } 138 | let ep = new ExpressionParser(); 139 | let exp = ep.parse(templateVar); 140 | ret += this.resolveExpression(exp); 141 | } 142 | } else if (expression.operator === 'parameters') { 143 | if (expression.operands.length <= 0) { 144 | console.error("no parameter name specified"); 145 | } 146 | ret += expression.operands[0]; // cheating since we don't have parameters being passed in 147 | } else { // no matching expression operation, just use the value 148 | ret += expression.operands[0]; 149 | } 150 | 151 | return ret; 152 | } 153 | 154 | getDependencies(resource: Resource) { 155 | var dependencies = new Array(); 156 | 157 | if (!resource.dependsOn || resource.dependsOn.length === 0) { 158 | return dependencies; 159 | } 160 | let dependsOn = new Array(); 161 | if (Array.isArray(resource.dependsOn)) { 162 | dependsOn = dependsOn.concat(resource.dependsOn); 163 | } else { 164 | dependsOn.push(resource.dependsOn); 165 | } 166 | 167 | dependsOn.forEach(dependencyName => { 168 | let dependency: DependencyId; 169 | let dependencyFound = false; 170 | 171 | try { 172 | let ep = new ExpressionParser(); 173 | let exp = ep.parse(dependencyName); 174 | dependency = this.resolveDependsOnId(exp); 175 | } catch (error) { 176 | this.resolveErrors.push(error); 177 | } 178 | 179 | this.templateData.resources.forEach(resource => { 180 | if (dependency && this.resourceMatchesDependency(resource, dependency)) { 181 | dependencies.push(resource); 182 | dependencyFound = true; 183 | } 184 | }); 185 | 186 | if (!dependencyFound) { 187 | console.warn("Coundn't find a matching dependency for '" + dependencyName + "'"); 188 | Telemetry.sendEvent('Error', 'ResourceNotFound', dependencyName); 189 | } 190 | }); 191 | 192 | return dependencies; 193 | } 194 | 195 | deleteResource(resource: Resource) { 196 | this.resources.remove(resource); 197 | } 198 | 199 | resolveDependsOnId(expression: Expression): DependencyId { 200 | let resolvedExpression = this.resolveExpression(expression); 201 | 202 | let ret = new DependencyId(); 203 | ret.type = resolvedExpression.substr(0, resolvedExpression.lastIndexOf("/")); 204 | ret.name = resolvedExpression.substr(resolvedExpression.lastIndexOf("/") + 1); 205 | 206 | return ret; 207 | } 208 | 209 | resourceMatchesDependency(resource: Resource, depId: DependencyId): boolean { 210 | if (!resource || !resource.type || !depId || !depId.type) { 211 | console.error('Avoided *undefined* in Resource.resourceMatchesDependency'); 212 | return false; 213 | } 214 | 215 | let typesMatch = resource.type.toUpperCase() === depId.type.toUpperCase(); 216 | let namesMatch = this.resolveName(resource.name).toUpperCase() === depId.name.toUpperCase() 217 | || this.resolveName(resource.name).toUpperCase() === '[' + depId.name.toUpperCase() + ']'; 218 | 219 | return typesMatch && namesMatch; 220 | } 221 | 222 | parseParametersFromTemplate() { 223 | var templateJson = this.toJson(); 224 | // parameters('someParameterName') => extracts 'someParameterName' 225 | var regex = /parameters\(\'([^\']+)\'\)/g; 226 | var match; 227 | while (match = regex.exec(templateJson)) { 228 | this.templateData.parameters[match[1]] = new Parameter(match[1], "string"); 229 | console.log(match[1]); 230 | } 231 | console.log(templateJson); 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/app/main/DependencyId.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | module ArmViz { 4 | export class DependencyId { 5 | type: string; 6 | name: string; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/app/main/Expression.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | describe('Expression', function () { 5 | it('should set source string', function () { 6 | var source = "[concat('foo', 'bar')]"; 7 | var exp = new ArmViz.Expression(source); 8 | 9 | expect(exp.source).toEqual(source); 10 | expect(exp.toString()).toEqual(source); 11 | }); 12 | 13 | it('should convert to string', function () { 14 | var source = "resourceId('foo', 'bar').a.b" 15 | var exp = new ArmViz.Expression(); 16 | 17 | exp.operator = 'resourceId'; 18 | exp.operands.push('foo'); 19 | exp.operands.push('bar'); 20 | exp.properties.push('a'); 21 | exp.properties.push('b'); 22 | 23 | expect(exp.toString()).toEqual(source); 24 | }); 25 | 26 | it('should convert multi-nested expressions to string', function () { 27 | var source = "resourceId('Microsoft.Web/sites', concat(variables('endpoint').name, 'gateway'))"; 28 | var exp = new ArmViz.Expression(); 29 | var exp1 = new ArmViz.Expression(); 30 | var exp2 = new ArmViz.Expression(); 31 | 32 | exp2.operator = 'variables'; 33 | exp2.operands.push('endpoint'); 34 | exp2.properties.push('name'); 35 | 36 | exp1.operator = 'concat'; 37 | exp1.operands.push(exp2); 38 | exp1.operands.push('gateway'); 39 | 40 | exp.operator = 'resourceId'; 41 | exp.operands.push('Microsoft.Web/sites'); 42 | exp.operands.push(exp1); 43 | 44 | expect(exp2.toString()).toEqual("variables('endpoint').name"); 45 | expect(exp1.toString()).toEqual("concat(variables('endpoint').name, 'gateway')"); 46 | expect(exp.toString()).toEqual(source); 47 | }); 48 | 49 | }); 50 | -------------------------------------------------------------------------------- /src/app/main/Expression.ts: -------------------------------------------------------------------------------- 1 | module ArmViz { 2 | export class Expression { 3 | private _source: string; 4 | private _operator: string = ''; 5 | private _operands: Array = []; 6 | private _properties: string[] = []; 7 | 8 | constructor(source?: string) { 9 | this._source = source; 10 | } 11 | 12 | get source() { 13 | return this._source; 14 | } 15 | 16 | get operator() { 17 | return this._operator; 18 | } 19 | 20 | set operator(newOperator: string) { 21 | this._operator = newOperator; 22 | } 23 | 24 | get operands() { 25 | return this._operands; 26 | } 27 | 28 | get properties() { 29 | return this._properties; 30 | } 31 | 32 | toString(): string { 33 | if (this._source) { 34 | return this._source; 35 | } 36 | 37 | let s = this.operator; 38 | 39 | s += '('; 40 | 41 | for (let operand of this.operands) { 42 | s += operand instanceof Expression ? operand.toString() : "'" + operand + "'"; 43 | s += operand !== this.operands[this.operands.length - 1] ? ', ' : ''; 44 | } 45 | 46 | s += ')'; 47 | 48 | for (let property of this.properties) { 49 | s += '.' + property; 50 | } 51 | 52 | return s; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/app/main/ExpressionParser.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | /// 4 | 5 | describe("ExpressionParser", function () { 6 | it('should parse simple variable expression', function () { 7 | var ep = new ArmViz.ExpressionParser(); 8 | var exp = ep.parse("variables('var1')") 9 | 10 | expect(exp.operator).toEqual("variables"); 11 | expect(exp.operands.length).toEqual(1); 12 | expect(exp.operands[0]).toEqual('var1'); 13 | }); 14 | 15 | it('should parse simple variable expression with properties', function () { 16 | var ep = new ArmViz.ExpressionParser(); 17 | var exp = ep.parse("variables('var1').var2.var3.var4") 18 | 19 | expect(exp.operator).toEqual("variables"); 20 | expect(exp.operands.length).toEqual(1); 21 | expect(exp.operands[0]).toEqual('var1'); 22 | expect(exp.properties.length).toEqual(3); 23 | expect(exp.properties[0]).toEqual('var2'); 24 | expect(exp.properties[1]).toEqual('var3'); 25 | expect(exp.properties[2]).toEqual('var4'); 26 | }); 27 | 28 | it('should parse string', function () { 29 | var ep = new ArmViz.ExpressionParser(); 30 | var exp = ep.parse("storageLoop") 31 | 32 | expect(exp.operator).toEqual(""); 33 | expect(exp.operands.length).toEqual(1); 34 | expect(exp.operands[0]).toEqual('storageLoop'); 35 | }); 36 | 37 | it ('should parse string with space', function() { 38 | var ep = new ArmViz.ExpressionParser(); 39 | var exp = ep.parse('virtual network'); 40 | 41 | expect(exp.operator).toEqual(""); 42 | expect(exp.operands.length).toEqual(1); 43 | expect(exp.operands[0]).toEqual('virtual network'); 44 | }); 45 | 46 | it('should parse expression with multiple parameters', function () { 47 | var ep = new ArmViz.ExpressionParser(); 48 | var exp = ep.parse("concat('var1', 'var2')") 49 | 50 | expect(exp.operator).toEqual("concat"); 51 | expect(exp.operands.length).toEqual(2); 52 | expect(exp.operands[0]).toEqual('var1'); 53 | expect(exp.operands[1]).toEqual('var2'); 54 | }); 55 | 56 | it('should parse expression with multiple parameters', function () { 57 | var ep = new ArmViz.ExpressionParser(); 58 | var exp = ep.parse("resourceId('var1', 'var2')") 59 | 60 | expect(exp.operator).toEqual("resourceId"); 61 | expect(exp.operands.length).toEqual(2); 62 | expect(exp.operands[0]).toEqual('var1'); 63 | expect(exp.operands[1]).toEqual('var2'); 64 | }); 65 | 66 | it('should parse expression with multiple parameters with properties', function () { 67 | var ep = new ArmViz.ExpressionParser(); 68 | var exp = ep.parse("resourceId('var1', 'var2').var3.var4") 69 | 70 | expect(exp.operator).toEqual("resourceId"); 71 | expect(exp.operands.length).toEqual(2); 72 | expect(exp.operands[0]).toEqual('var1'); 73 | expect(exp.operands[1]).toEqual('var2'); 74 | expect(exp.properties.length).toEqual(2); 75 | expect(exp.properties[0]).toEqual('var3'); 76 | expect(exp.properties[1]).toEqual('var4'); 77 | }); 78 | 79 | it('should parse nested expressions', function () { 80 | var ep = new ArmViz.ExpressionParser(); 81 | var exp = ep.parse("concat('var1', nest('var2', 'var3'))") 82 | 83 | expect(exp.operator).toEqual("concat"); 84 | expect(exp.operands.length).toEqual(2); 85 | expect(exp.operands[0]).toEqual('var1'); 86 | 87 | var nested = exp.operands[1]; 88 | 89 | expect(nested.operator).toEqual('nest'); 90 | expect(nested.operands.length).toEqual(2); 91 | expect(nested.operands[0]).toEqual('var2'); 92 | expect(nested.operands[1]).toEqual('var3'); 93 | }); 94 | 95 | it('should parse nested expressions with properties', function () { 96 | var ep = new ArmViz.ExpressionParser(); 97 | var exp = ep.parse("concat(nested('var1', var2').var3.var4, 'var5')"); 98 | 99 | expect(exp.operator).toEqual("concat"); 100 | expect(exp.operands.length).toEqual(2); 101 | expect(exp.operands[1]).toEqual('var5'); 102 | 103 | var nested = exp.operands[0]; 104 | expect(nested.operator).toEqual('nested'); 105 | expect(nested.operands.length).toEqual(2); 106 | expect(nested.operands[0]).toEqual('var1'); 107 | expect(nested.operands[1]).toEqual('var2'); 108 | expect(nested.properties.length).toEqual(2); 109 | expect(nested.properties[0]).toEqual('var3'); 110 | expect(nested.properties[1]).toEqual('var4'); 111 | 112 | }); 113 | 114 | it('should parse nested expressions with properties', function () { 115 | var ep = new ArmViz.ExpressionParser(); 116 | var exp = ep.parse("concat('var1', nested('var2', var3').var4.var5)"); 117 | 118 | expect(exp.operator).toEqual("concat"); 119 | expect(exp.operands.length).toEqual(2); 120 | expect(exp.operands[0]).toEqual('var1'); 121 | 122 | var nested = exp.operands[1]; 123 | expect(nested.operator).toEqual('nested'); 124 | expect(nested.operands.length).toEqual(2); 125 | expect(nested.operands[0]).toEqual('var2'); 126 | expect(nested.operands[1]).toEqual('var3'); 127 | expect(nested.properties.length).toEqual(2); 128 | expect(nested.properties[0]).toEqual('var4'); 129 | expect(nested.properties[1]).toEqual('var5'); 130 | 131 | }); 132 | 133 | it('should parse nested expressions with properties', function () { 134 | var ep = new ArmViz.ExpressionParser(); 135 | var exp = ep.parse("concat(nested1('var11', var12').var13.var14, nested2('var21', 'var22').var23.var24)"); 136 | 137 | expect(exp.operator).toEqual("concat"); 138 | expect(exp.operands.length).toEqual(2); 139 | 140 | var nested1 = exp.operands[0]; 141 | expect(nested1.operator).toEqual('nested1'); 142 | expect(nested1.operands.length).toEqual(2); 143 | expect(nested1.operands[0]).toEqual('var11'); 144 | expect(nested1.operands[1]).toEqual('var12'); 145 | expect(nested1.properties.length).toEqual(2); 146 | expect(nested1.properties[0]).toEqual('var13'); 147 | expect(nested1.properties[1]).toEqual('var14'); 148 | 149 | var nested2 = exp.operands[1]; 150 | expect(nested2.operator).toEqual('nested2'); 151 | expect(nested2.operands.length).toEqual(2); 152 | expect(nested2.operands[0]).toEqual('var21'); 153 | expect(nested2.operands[1]).toEqual('var22'); 154 | expect(nested2.properties.length).toEqual(2); 155 | expect(nested2.properties[0]).toEqual('var23'); 156 | expect(nested2.properties[1]).toEqual('var24'); 157 | }); 158 | 159 | it('should strip brackets', function () { 160 | var ep = new ArmViz.ExpressionParser(); 161 | var exp = ep.parse("[foo()]") 162 | 163 | expect(exp.operator).toEqual("foo"); 164 | expect(exp.operands.length).toEqual(0); 165 | }); 166 | 167 | it('should parse multi-nested expressions', function () { 168 | var ep = new ArmViz.ExpressionParser(); 169 | var exp = ep.parse("resourceId('Microsoft.Web/sites', concat(parameters('endpointName'), 'gateway'))"); 170 | 171 | expect(exp.operator).toEqual('resourceId'); 172 | expect(exp.operands.length).toEqual(2); 173 | expect(exp.operands[0]).toEqual('Microsoft.Web/sites'); 174 | expect(exp.operands[1].operator).toEqual('concat'); 175 | expect(exp.operands[1].operands.length).toEqual(2) 176 | expect(exp.operands[1].operands[0].operator).toEqual('parameters'); 177 | expect(exp.operands[1].operands[0].operands[0]).toEqual('endpointName'); 178 | expect(exp.operands[1].operands[1]).toEqual('gateway'); 179 | }); 180 | 181 | it('should parse multi-nested expressions with properties', function () { 182 | var ep = new ArmViz.ExpressionParser(); 183 | var exp = ep.parse( 184 | "variables(resourceId(parameters('endpoint').type, concat('-', parameters('endpoint').name))).default.name"); 185 | 186 | expect(exp.operator).toEqual('variables'); 187 | expect(exp.operands.length).toEqual(1); 188 | expect(exp.properties.length).toEqual(2); 189 | expect(exp.properties[0]).toEqual('default'); 190 | expect(exp.properties[1]).toEqual('name'); 191 | 192 | var resourceId = exp.operands[0]; 193 | expect(resourceId.operands.length).toEqual(2); 194 | 195 | var parameters1 = resourceId.operands[0]; 196 | expect(parameters1.operands.length).toEqual(1); 197 | expect(parameters1.operands[0]).toEqual('endpoint'); 198 | expect(parameters1.properties.length).toEqual(1); 199 | expect(parameters1.properties[0]).toEqual('type'); 200 | 201 | var concat = resourceId.operands[1]; 202 | expect(concat.operands.length).toEqual(2); 203 | expect(concat.operands[0]).toEqual('-'); 204 | 205 | var parameters2 = concat.operands[1]; 206 | expect(parameters2.operands.length).toEqual(1); 207 | expect(parameters2.operands[0]).toEqual('endpoint'); 208 | expect(parameters2.properties.length).toEqual(1); 209 | expect(parameters2.properties[0]).toEqual('name'); 210 | }); 211 | }); 212 | -------------------------------------------------------------------------------- /src/app/main/ExpressionParser.ts: -------------------------------------------------------------------------------- 1 | module ArmViz { 2 | export class ExpressionParser { 3 | parse(source: string): Expression { 4 | let pre = ''; 5 | let buffer = ''; 6 | let isProperty = false; 7 | let exp: Expression = null; 8 | let expStack: Expression[] = []; 9 | 10 | // Trim square brackets 11 | if (source[0] === '[' && source[source.length - 1] === ']') { 12 | source = source.substr(1, source.length - 2); 13 | } 14 | 15 | // An expression could just be a string 16 | if (source.indexOf('(') < 0) { 17 | exp = new Expression(); 18 | exp.operands.push(source); 19 | return exp; 20 | } 21 | 22 | // Parse expression 23 | for (let ch of source) { 24 | switch (ch) { 25 | case "'": 26 | break; 27 | case '(': 28 | if (exp) { 29 | expStack.push(exp); 30 | } 31 | exp = new Expression(); 32 | exp.operator = buffer.trim(); 33 | buffer = ''; 34 | 35 | break; 36 | case ')': 37 | if (buffer.length > 0) { 38 | if (isProperty) { 39 | let operand = exp.operands[exp.operands.length - 1]; 40 | operand.properties.push.apply(operand.properties, buffer.trim().split('.')); 41 | isProperty = false; 42 | } else { 43 | exp.operands.push(buffer.trim()); 44 | } 45 | buffer = ''; 46 | } 47 | 48 | if (expStack.length > 0) { 49 | let parent = expStack.pop(); 50 | parent.operands.push(exp); 51 | exp = parent; 52 | } 53 | 54 | break; 55 | case '.': 56 | if (pre === ')') { 57 | isProperty = true; 58 | } else { 59 | buffer += ch; 60 | } 61 | 62 | break; 63 | case ',': 64 | if (buffer.length > 0) { 65 | if (isProperty) { 66 | let operand = exp.operands[exp.operands.length - 1]; 67 | operand.properties.push.apply(operand.properties, buffer.trim().split('.')); 68 | isProperty = false; 69 | } else { 70 | exp.operands.push(buffer.trim()); 71 | } 72 | buffer = ''; 73 | } 74 | 75 | break; 76 | default: 77 | buffer += ch; 78 | break; 79 | } 80 | 81 | pre = ch; 82 | } 83 | 84 | // Post parsing process 85 | if (buffer.length > 0 && isProperty) { 86 | exp.properties.push.apply(exp.properties, buffer.trim().split('.')); 87 | } 88 | 89 | return exp; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/app/main/Parameter.ts: -------------------------------------------------------------------------------- 1 | module ArmViz { 2 | export class Parameter { 3 | name: string; 4 | type: string; 5 | defaultValue: string; 6 | allowedValues: string[]; 7 | 8 | constructor(name: string, type: string) { 9 | this.name = name; 10 | this.type = type; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/main/Resource.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | /// 4 | /// 5 | 6 | describe("Resource", function () { 7 | it('should match when the id and name match', function () { 8 | var template = new ArmViz.ArmTemplate(null); 9 | 10 | var r = new ArmViz.Resource(null); 11 | r.type = 'type'; 12 | r.name = 'name' 13 | 14 | var dep = new ArmViz.DependencyId(); 15 | dep.type = 'type'; 16 | dep.name = 'name'; 17 | 18 | var result = template.resourceMatchesDependency(r, dep); 19 | 20 | expect(result).toBe(true); 21 | }); 22 | /* 23 | it('should get the correct ID when the name is an expression', function() { 24 | var r = new ArmViz.Resource(null); 25 | r.type = 'type'; 26 | r.name = "[concat(parameters('nicNamePrefix'), copyindex())]"; 27 | 28 | var id = ArmViz.Resource.getResourceId(r); 29 | 30 | expect(id).toEqual("type/parameters('nicNamePrefix')copyindex()"); 31 | }); 32 | 33 | it('should get valid resource id when the name is an expression', function() { 34 | var id = ArmViz.Resource.getResourceId(testResource1); 35 | 36 | expect(id).toEqual("Microsoft.Web/sites/concat(parameters('endpointName'),'gateway')"); 37 | });*/ 38 | }); 39 | 40 | var testResource1 = { 41 | "type": "Microsoft.Web/sites", 42 | "apiVersion": "2015-04-01", 43 | "name": "[concat(parameters('endpointName'),'gateway')]" 44 | } 45 | -------------------------------------------------------------------------------- /src/app/main/Resource.ts: -------------------------------------------------------------------------------- 1 | module ArmViz { 2 | export class Resource { 3 | type: string; 4 | name: string; 5 | apiVersion: string; 6 | location: string; 7 | properties: any; 8 | dependsOn: string | string[]; 9 | 10 | constructor(toolboxItem?: ToolboxResource) { 11 | if (toolboxItem) { 12 | this.type = toolboxItem.resourceType; 13 | this.name = toolboxItem.friendlyName; 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/main/ResourceShape.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | module ArmViz { 4 | export class ResourceShape extends joint.shapes.basic.Generic { 5 | protected _minWidth = 100; 6 | protected _maxWidth = 200; 7 | protected _minHeight = 80; 8 | protected _maxHeight = 120; 9 | protected _defaultWidth = 120; 10 | protected _defaultHeight = 90; 11 | protected _width; 12 | protected _height; 13 | 14 | markup: string = [ 15 | '', 16 | '', 17 | '', 18 | '', 19 | '', 20 | '', 21 | '', 22 | '', 23 | '', 24 | '</g>' 25 | ].join(''); 26 | 27 | sourceTemplate: ArmTemplate; 28 | sourceResource: Resource; 29 | sourceToolboxItem: ToolboxResource; 30 | 31 | constructor( 32 | template: ArmTemplate, 33 | resource: Resource, 34 | toolboxItem: ToolboxResource, 35 | attributes?: any, 36 | options?: any) { 37 | super(attributes, options); 38 | 39 | this.sourceTemplate = template; 40 | this.sourceResource = resource; 41 | this.sourceToolboxItem = toolboxItem ? toolboxItem : new ToolboxResource( 42 | 'Default Resource.png', 43 | this.sourceResource.type.split('/').pop(), 44 | this.sourceTemplate.resolveName(this.sourceResource.name) 45 | ); 46 | 47 | this.initializeContent(); 48 | this.resize(this.defaultWidth, this.defaultHeight); 49 | } 50 | 51 | get minWidth(): number { 52 | return this._minWidth; 53 | } 54 | 55 | get maxWidth(): number { 56 | return this._maxWidth; 57 | } 58 | 59 | get minHeight(): number { 60 | return this._minHeight; 61 | } 62 | 63 | get maxHeight(): number { 64 | return this._maxHeight; 65 | } 66 | 67 | get defaultWidth(): number { 68 | return this._defaultWidth; 69 | } 70 | 71 | get defaultHeight(): number { 72 | return this._defaultHeight; 73 | } 74 | 75 | get width(): number { 76 | return this._width; 77 | } 78 | 79 | set width(value: number) { 80 | if (this.width !== value) { 81 | this.resize(value, this.height); 82 | } 83 | } 84 | 85 | get height(): number { 86 | return this._height; 87 | } 88 | 89 | set height(value: number) { 90 | if (this.height !== value) { 91 | this.resize(this.width, value); 92 | } 93 | } 94 | 95 | resize(width: number, height: number): joint.dia.Element { 96 | width = Math.max(this.minWidth, Math.min(this.maxWidth, width)); 97 | height = Math.max(this.minHeight, Math.min(this.maxHeight, height)); 98 | 99 | super.resize(width, height); 100 | // console.log(width); 101 | // console.log(height); 102 | 103 | this._width = width; 104 | this._height = height; 105 | 106 | // Need to set size explicitly for IE and Edge 107 | this.attributes.attrs['.shape.root-layout'].width = width; 108 | this.attributes.attrs['.shape.root-layout'].height = height; 109 | this.attributes.attrs['.shape.title-bar'].width = width; 110 | 111 | 112 | return this; 113 | } 114 | 115 | 116 | private initializeContent() { 117 | let title = this.sourceToolboxItem.friendlyName; 118 | let name = this.sourceTemplate.resolveName(this.sourceResource.name); 119 | let icon = '/assets/toolbox-icons/' + this.sourceToolboxItem.iconName; 120 | 121 | this.attributes.attrs['.shape.root-layout'] = { 122 | 'follow-scale': true, 123 | 'stroke': '#0079D6', 124 | 'stroke-width': 2, 125 | }; 126 | 127 | this.attributes.attrs['.shape.title-bar'] = { 128 | 'follow-scale': true, 129 | 'fill': '#0079D6', 130 | 'stroke-width': 0, 131 | 'height': 15 132 | }; 133 | 134 | this.attributes.attrs['.shape.title'] = { 135 | 'ref': '.shape.root-layout', 136 | 'ref-x': 4, 137 | 'ref-y': 2, 138 | 'fill': 'white', 139 | 'stroke-width': 0, 140 | 'font-size': 10, 141 | 'font-weight': 'bold', 142 | 'text': title, 143 | 'id': 'shape-title-' + this.cid 144 | }; 145 | 146 | this.attributes.attrs['.shape.icon'] = { 147 | 'ref': '.shape.root-layout', 148 | 'ref-x': .5, 149 | 'ref-y': .5, 150 | 'x-alignment': 'middle', 151 | 'y-alignment': 'middle', 152 | 'width': 48, 153 | 'height': 48, 154 | 'xlink:href': icon 155 | }; 156 | 157 | this.attributes.attrs['.shape.name'] = { 158 | 'ref': '.shape.root-layout', 159 | 'ref-x': 4, 160 | 'ref-y': 75, 161 | 'font-size': 10, 162 | 'fill': 'black', 163 | 'stroke-width': 0, 164 | 'text': name, 165 | 'id': 'shape-name-' + this.cid 166 | }; 167 | 168 | this.attributes.attrs['.shape.tooltip'] = { 169 | 'text': 'Double-click to edit' 170 | }; 171 | } 172 | } 173 | 174 | export class ResourceShapeView extends joint.dia.ElementView { 175 | constructor(options?: any) { 176 | super(options); 177 | } 178 | 179 | render(): Backbone.View<joint.dia.Cell> { 180 | super.render(); 181 | 182 | let title: any = $('#shape-title-' + this.model.cid)[0]; 183 | let name: any = $('#shape-name-' + this.model.cid)[0]; 184 | let titleWidth = title.getBBox().width + 20; 185 | let nameWidth = name.getBBox().width + 20; 186 | let model = <ResourceShape>this.model; 187 | 188 | // Resize according to the length of title and name 189 | if (titleWidth > model.width) { 190 | model.width = titleWidth; 191 | } 192 | if (nameWidth > model.width) { 193 | model.width = nameWidth; 194 | } 195 | 196 | // Necessary for IE and Edge 197 | super.render(); 198 | title = $('#shape-title-' + this.model.cid)[0]; 199 | name = $('#shape-name-' + this.model.cid)[0]; 200 | 201 | // Trim text content if it is too long 202 | if (titleWidth > model.maxWidth) { 203 | title.textContent = name.textContent.substr(0, 37) + '...'; 204 | } 205 | if (nameWidth > model.maxWidth) { 206 | name.textContent = name.textContent.substr(0, 37) + '...'; 207 | } 208 | 209 | return this; 210 | } 211 | } 212 | 213 | class Expression { 214 | operand: Expression[]; 215 | 216 | execute() { 217 | return null; 218 | } 219 | } 220 | 221 | } 222 | -------------------------------------------------------------------------------- /src/app/main/ResourceShapeLink.ts: -------------------------------------------------------------------------------- 1 | /// <reference path="../../../typings/index.d.ts" /> 2 | 3 | module ArmViz { 4 | 'use strict'; 5 | export class ResourceShapeLink extends joint.dia.Link { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/app/main/Telemetry.ts: -------------------------------------------------------------------------------- 1 | /// <reference path="../../../typings/index.d.ts" /> 2 | 3 | module ArmViz { 4 | declare var ga: any; 5 | 6 | export class Telemetry { 7 | static sendEvent(category: string, action: string, label?: string, value?: number) { 8 | ga('send', { 9 | hitType: 'event', 10 | eventCategory: category, 11 | eventAction: action, 12 | eventLabel: label, 13 | eventValue: value 14 | }); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/main/ToolboxITems.ts: -------------------------------------------------------------------------------- 1 | module ArmViz { 2 | export function getToolboxItems() { 3 | var toolboxItems: Array<ToolboxResource> = [ 4 | new ToolboxResource( 5 | "Virtual machine.png", 6 | "Virtual Machine", 7 | 'Microsoft.Compute/virtualMachines', 8 | true), 9 | new ToolboxResource( 10 | "Unidentified feature object.png", 11 | "VM Extension", 12 | 'Microsoft.Compute/virtualMachines/extensions'), 13 | new ToolboxResource( 14 | "Availability Set.png", 15 | "Availability Set", 16 | 'Microsoft.Compute/availabilitySets'), 17 | new ToolboxResource( 18 | "Azure load balancer.png", 19 | "Load Balancer", 20 | 'Microsoft.Network/loadBalancers', 21 | true), 22 | new ToolboxResource( 23 | "Virtual Network.png", 24 | "Network", 25 | 'Microsoft.Network/virtualNetworks', 26 | true), 27 | new ToolboxResource( 28 | "NIC.png", 29 | "NIC", 30 | 'Microsoft.Network/networkInterfaces', 31 | true), 32 | new ToolboxResource( 33 | "Service Endpoint.png", 34 | "Public IP", 35 | 'Microsoft.Network/publicIPAddresses', 36 | true), 37 | new ToolboxResource( 38 | "Unidentified feature object.png", 39 | "NSG", 40 | 'Microsoft.Network/networkSecurityGroups'), 41 | new ToolboxResource( 42 | "Unidentified feature object.png", 43 | "App Gateway", 44 | 'Microsoft.Network/applicationGateways'), 45 | new ToolboxResource( 46 | "Storage (Azure).png", 47 | "Storage Acct", 48 | 'Microsoft.Storage/storageAccounts', 49 | true), 50 | new ToolboxResource( 51 | "Unidentified feature object.png", 52 | "Automation", 53 | 'Microsoft.Automation/automationAccounts'), 54 | new ToolboxResource( 55 | "Logic App.png", 56 | "Workflow", 57 | 'Microsoft.Logic/workflows'), 58 | new ToolboxResource( 59 | "Deployment.png", 60 | "Deployment", 61 | 'Microsoft.Resources/deployments'), 62 | new ToolboxResource( 63 | "Web App (was Websites).png", 64 | "Server Farm", 65 | 'Microsoft.Web/serverfarms'), 66 | new ToolboxResource( 67 | "Cloud Service.png", 68 | "Hosting Env", 69 | 'Microsoft.Web/hostingEnvironments'), 70 | new ToolboxResource( 71 | "Key Vault.png", 72 | "Key Vault", 73 | 'Microsoft.KeyVault/vaults'), 74 | new ToolboxResource( 75 | "Azure SQL Database.png", 76 | "SQL Server", 77 | 'Microsoft.Sql/servers'), 78 | new ToolboxResource( 79 | "Web App (was Websites).png", 80 | "Web App", 81 | 'Microsoft.Web/sites'), 82 | new ToolboxResource( 83 | "Web App (was Websites).png", 84 | "Server Farm", 85 | 'Microsoft.Web/serverFarms'), 86 | new ToolboxResource( 87 | "Autoscaling.png", 88 | "Auto Scale", 89 | 'microsoft.insights/autoscalesettings'), 90 | new ToolboxResource( 91 | "Azure alert.png", 92 | "Alert Rules", 93 | 'microsoft.insights/alertrules'), 94 | new ToolboxResource( 95 | "Operational Insights.png", 96 | "Insights", 97 | 'microsoft.insights/components'), 98 | new ToolboxResource( 99 | "Web App (was Websites).png", 100 | "Web Site", 101 | 'Microsoft.Web/Sites'), 102 | new ToolboxResource( 103 | "API App.png", 104 | "API App", 105 | 'Microsoft.AppService/apiApps'), 106 | new ToolboxResource( 107 | "Unidentified feature object.png", 108 | "App Gateway", 109 | 'Microsoft.AppService/gateways'), 110 | new ToolboxResource( 111 | "Availability Set.png", 112 | "VM Scale Set", 113 | 'Microsoft.Compute/virtualMachineScaleSets'), 114 | new ToolboxResource( 115 | "cdn.png", 116 | "CDN Profile", 117 | 'Microsoft.Cdn/Profiles', 118 | true), 119 | new ToolboxResource( 120 | "cdn.png", 121 | "CDN Endpoint", 122 | 'Microsoft.Cdn/Profiles/Endpoints', 123 | true) 124 | ]; 125 | 126 | return toolboxItems; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/app/main/ToolboxResource.ts: -------------------------------------------------------------------------------- 1 | module ArmViz { 2 | export class ToolboxResource { 3 | iconName: string; 4 | friendlyName: string; 5 | resourceType: string; 6 | private hasDefaultJson: boolean; //Indicates if there is a file with the default JSON 7 | 8 | //This is loaded dynamically at run-time 9 | defaultJson: string; 10 | 11 | constructor(iconName: string, friendlyName: string, resourceName: string, hasDefaultJson?: boolean) { 12 | this.iconName = iconName; 13 | this.friendlyName = friendlyName; 14 | this.resourceType = resourceName; 15 | 16 | this.hasDefaultJson = !!hasDefaultJson; 17 | } 18 | 19 | getDefaultJsonFileName() { 20 | if (!this.hasDefaultJson) { 21 | return null; 22 | } 23 | return this.resourceType.replace(new RegExp("/", 'g'), ".") + '.json'; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/main/graph.ts: -------------------------------------------------------------------------------- 1 | /// <reference path="../../../typings/index.d.ts" /> 2 | 3 | module ArmViz { 4 | export class Graph { 5 | private graph: joint.dia.Graph; 6 | private paper: joint.dia.Paper; 7 | 8 | private template: ArmTemplate; 9 | private toolboxItems: ToolboxResource[]; 10 | private resourceShapes: ResourceShape[] = new Array<ResourceShape>(); 11 | private resourceShapeLinks: ResourceShapeLink[] = new Array<ResourceShapeLink>(); 12 | 13 | resourceSelected: (resource: Resource, modal: boolean) => void; 14 | 15 | constructor(toolboxItems: ToolboxResource[]) { 16 | this.toolboxItems = toolboxItems; 17 | 18 | this.initJointJs(); 19 | this.initializeClickPopup(); 20 | } 21 | 22 | applyTemplate(template: ArmTemplate) { 23 | this.reset(); 24 | 25 | this.template = template; 26 | 27 | this.createNodes(); 28 | this.createLinks(); 29 | this.displayNodesAndLinks(); 30 | this.graph.clear(); 31 | this.autoSetShapePositions(); 32 | this.displayNodesAndLinks(); 33 | 34 | this.watchModel(); 35 | 36 | template.checkResolveErrors(); 37 | } 38 | 39 | private watchModel() { 40 | let self = this; 41 | this.template.resources.subscribe<KnockoutArrayChange<Resource>[]>((changes) => { 42 | changes.forEach((change) => { 43 | if (change.status === 'added') { 44 | let shape = self.addResource(change.value); 45 | self.drawShape(shape); 46 | } else if (change.status === 'deleted') { 47 | //Remove from the array too? 48 | self.removeResourceShape(change.value); 49 | } 50 | }); 51 | }, null, "arrayChange"); 52 | } 53 | 54 | private reset() { 55 | this.resourceShapes = new Array<ResourceShape>(); 56 | this.resourceShapeLinks = new Array<ResourceShapeLink>(); 57 | 58 | this.graph.clear(); 59 | } 60 | 61 | private initJointJs() { 62 | this.graph = new joint.dia.Graph(); 63 | this.paper = new joint.dia.Paper(<any>{ 64 | el: $('#paper'), 65 | gridSize: 1, 66 | model: this.graph, 67 | height: '100%', 68 | width: '100%', 69 | elementView: ResourceShapeView 70 | }); 71 | } 72 | 73 | private createNodes() { 74 | ko.utils.arrayForEach(this.template.resources(), resource => { 75 | this.addResource(resource); 76 | }); 77 | } 78 | 79 | private addResource(resource: Resource) { 80 | var toolboxItem: ToolboxResource = this.getToolboxItemForResource(resource); 81 | 82 | var shape = new ResourceShape(this.template, resource, toolboxItem); 83 | shape.position(60, 60); 84 | 85 | this.resourceShapes.push(shape); 86 | 87 | return shape; 88 | } 89 | 90 | private displayNodesAndLinks() { 91 | this.resourceShapes.forEach(shape => { 92 | this.drawShape(shape); 93 | }); 94 | 95 | this.refreshLinks(); 96 | 97 | var bbox = (<any>this.paper).getContentBBox(); 98 | (<any>this.paper).fitToContent(bbox.width, bbox.height + 400); 99 | } 100 | 101 | public refreshLinks() { 102 | //Remove existing links 103 | this.resourceShapeLinks.forEach(shapeLink => { 104 | shapeLink.remove(); 105 | }); 106 | this.resourceShapeLinks = new Array<ResourceShapeLink>(); 107 | 108 | //Create new links from the model 109 | this.createLinks(); 110 | 111 | //Draw the links 112 | this.resourceShapeLinks.forEach(shapeLink => { 113 | this.drawShapeLink(shapeLink); 114 | }); 115 | } 116 | 117 | private createLinks() { 118 | var self = this; 119 | 120 | ko.utils.arrayForEach(this.template.resources(), resource => { 121 | var dependencies = self.template.getDependencies(resource); 122 | 123 | dependencies.forEach(dep => { 124 | var sourceNode = self.getShapeForResource(resource); 125 | var destNode = self.getShapeForResource(dep); 126 | 127 | var l = new joint.dia.Link({ 128 | source: { id: sourceNode.id }, 129 | target: { id: destNode.id }, 130 | attrs: { 131 | '.connection': { 'stroke-width': 1.5, stroke: '#34495E' }, 132 | '.marker-target': { fill: '#34495E', stroke: '#34495E', d: 'M 6 0 L 0 3 L 6 6 z' } 133 | } 134 | }); 135 | 136 | this.resourceShapeLinks.push(l); 137 | }); 138 | }); 139 | } 140 | 141 | private drawShape(shape: ResourceShape) { 142 | this.graph.addCell(shape); 143 | } 144 | 145 | private drawShapeLink(link: ResourceShapeLink) { 146 | this.graph.addCell(link); 147 | } 148 | 149 | private removeResourceShape(resource: Resource) { 150 | let shape = this.getShapeForResource(resource); 151 | shape.remove(); 152 | } 153 | 154 | private getToolboxItemForResource(resource: Resource): ToolboxResource { 155 | var foundItem: ToolboxResource = null; 156 | 157 | this.toolboxItems.forEach(toolboxItem => { 158 | if (toolboxItem.resourceType.toUpperCase() === resource.type.toUpperCase()) { 159 | foundItem = toolboxItem; 160 | } 161 | }); 162 | 163 | return foundItem; 164 | } 165 | 166 | private getShapeForResource(resource: Resource) { 167 | var retShape: ResourceShape; 168 | 169 | this.resourceShapes.forEach(shape => { 170 | if (shape.sourceResource === resource) { 171 | retShape = shape; 172 | } 173 | }); 174 | 175 | return retShape; 176 | } 177 | 178 | private initializeClickPopup() { 179 | var self = this; 180 | this.paper.on('cell:pointerdown', (evt, x, y) => { 181 | var shape: ResourceShape = evt.model; 182 | self.displayResource(shape.sourceResource, false); 183 | }); 184 | 185 | this.paper.on('cell:pointerdblclick', (cellView: any, evt: any, x: any, y: any) => { 186 | var shape: ResourceShape = cellView.model; 187 | self.displayResource(shape.sourceResource, true); 188 | }); 189 | } 190 | 191 | private displayResource(resource: Resource, modal: boolean) { 192 | if (this.resourceSelected) { 193 | this.resourceSelected(resource, modal); 194 | } 195 | } 196 | 197 | private autoSetShapePositions() { 198 | var self = this; 199 | //https://github.com/cpettitt/dagre/wiki#configuring-the-layout 200 | 201 | var g = new dagre.graphlib.Graph(); 202 | 203 | // Set an object for the graph label 204 | g.setGraph({}); 205 | 206 | // Default to assigning a new object as a label for each new edge. 207 | g.setDefaultEdgeLabel(() => { return {}; }); 208 | 209 | this.resourceShapes.forEach(shape => { 210 | g.setNode(shape.id, { width: shape.attributes.size.width, height: shape.attributes.size.height }); 211 | }); 212 | 213 | this.resourceShapeLinks.forEach(shapeLink => { 214 | g.setEdge(shapeLink.attributes.source.id, shapeLink.attributes.target.id); 215 | }); 216 | 217 | 218 | dagre.layout(g); 219 | 220 | g.nodes().forEach((node) => { 221 | var shape = _.findWhere(self.resourceShapes, { id: node }); 222 | 223 | shape.attributes.position.x = g.node(node).x; 224 | shape.attributes.position.y = g.node(node).y + 50; 225 | }); 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/app/main/main.controller.ts: -------------------------------------------------------------------------------- 1 | /// <reference path="../../../typings/index.d.ts" /> 2 | 3 | module ArmViz { 4 | 5 | export class MainCtrl { 6 | private $scope: any; 7 | private $modal: any; 8 | private $http: any; //ng.IHttpProvider causes errors 9 | private $window: any; 10 | private $cookies: any; 11 | private $timeout: any; 12 | private growl: angular.growl.IGrowlService; 13 | private template: ArmTemplate; 14 | private graph: Graph; 15 | private loadUrl: string; //Url from the address bar 16 | private loadedUrl: string; 17 | 18 | toolboxItems: ToolboxResource[]; 19 | hideChrome = false; 20 | 21 | /** @ngInject */ 22 | constructor($scope, $stateParams, $http, $window, $modal, $cookies, $timeout, growl: angular.growl.IGrowlService) { 23 | this.$scope = $scope; 24 | this.$modal = $modal; 25 | this.$http = $http; 26 | this.$window = $window; 27 | this.$cookies = $cookies; 28 | this.$timeout = $timeout; 29 | this.growl = growl; 30 | 31 | var toolboxItems = getToolboxItems(); 32 | this.toolboxItems = toolboxItems; 33 | 34 | let templateData = <ArmTemplateInterface>arm; 35 | this.template = new ArmTemplate(templateData); 36 | 37 | this.graph = new Graph(toolboxItems); 38 | 39 | this.hideChrome = !!$stateParams.hideChrome; 40 | 41 | if ($stateParams.load) { 42 | this.loadUrl = $stateParams.load; 43 | this.loadedUrl = this.loadUrl; 44 | 45 | $scope.loadUrl = this.loadUrl; 46 | 47 | $http.get(this.loadUrl) 48 | .success((data: any, status, headers, config) => { 49 | this.template = new ArmTemplate(<ArmTemplateInterface>data); 50 | this.graph.applyTemplate(this.template); 51 | Telemetry.sendEvent('Template', 'LoadFromUrl', this.loadUrl); 52 | }).error((data, status, headers, config) => { 53 | alert('Error loading your template from GitHub. Please go back and try again.'); 54 | Telemetry.sendEvent('Error', 'LoadTemplateFromUrlFailed', this.loadUrl + ': ' + status + ': ' + data); 55 | }); 56 | } else { 57 | this.graph.applyTemplate(this.template); 58 | } 59 | 60 | this.graph.resourceSelected = (resource: Resource, modal: boolean) => { 61 | if (modal) { 62 | var modalInstance = $modal.open({ 63 | templateUrl: '/app/resourceEditorDialog/ResourceEditor.html', 64 | controller: 'ResourceEditorController', 65 | controllerAs: 'main', 66 | size: 'lg', 67 | 68 | //These items get passed to the child controller 69 | resolve: { 70 | arm: () => { 71 | return this.template; 72 | }, 73 | resource: () => { 74 | return resource; 75 | } 76 | } 77 | }); 78 | modalInstance.result.then((resultResource: any) => { 79 | if (resultResource && resultResource.deleteFlag) { 80 | this.template.deleteResource(<Resource>resultResource); 81 | } 82 | 83 | this.graph.refreshLinks(); 84 | }); 85 | } else { 86 | $scope.selectedResource = JSON.stringify(resource, null, 2); 87 | $scope.$apply(); 88 | } 89 | }; 90 | 91 | this.showFeedbackPopup(); 92 | } 93 | 94 | downloadArmTemplate() { 95 | let json = this.template.toJson(); 96 | 97 | this.downloadJsonInBrowser(json, 'armTemplate.json'); 98 | Telemetry.sendEvent('Template', 'Download'); 99 | } 100 | 101 | openExistingTemplate() { 102 | var modalInstance = this.$modal.open({ 103 | templateUrl: '/app/openExistingTemplateDialog/OpenDialog.html', 104 | controller: 'OpenDialogController', 105 | controllerAs: 'main' 106 | }); 107 | 108 | modalInstance.result.then((newTemplate: ArmTemplate) => { 109 | this.template = newTemplate; 110 | this.graph.applyTemplate(newTemplate); 111 | Telemetry.sendEvent('Template', 'OpenExisting'); 112 | }); 113 | } 114 | 115 | loadArmQuickstartTemplate() { 116 | var modalInstance = this.$modal.open({ 117 | templateUrl: '/app/quickstartLoadDialog/QuickstartLoadDialog.html', 118 | controller: 'QuickstartLoadDialog', 119 | controllerAs: 'main' 120 | }); 121 | 122 | modalInstance.result.then((result: DialogResult) => { 123 | this.template = result.armTemplate; 124 | this.loadedUrl = result.templateInfo.templateLink; 125 | this.graph.applyTemplate(result.armTemplate); 126 | Telemetry.sendEvent('Template', 'LoadArmQuickstart', result.templateInfo.name); 127 | }); 128 | } 129 | 130 | openTemplateProperties() { 131 | //Documentation: http://angular-ui.github.io/bootstrap/#/modal 132 | this.$modal.open({ 133 | templateUrl: '/app/templateParameterEditor/TemplateProperties.html', 134 | controller: 'TemplateParameterManager', 135 | controllerAs: 'main', 136 | size: 'lg', 137 | 138 | //These items get passed to the child controller 139 | resolve: { 140 | armTemplate: () => { 141 | return this.template; 142 | } 143 | } 144 | }); 145 | } 146 | 147 | createVisualizeButton() { 148 | this.$modal.open({ 149 | templateUrl: '/app/createVisualizerButton/createVisualizerButton.html', 150 | controller: 'CreateVisualizerButtonController', 151 | controllerAs: 'main', 152 | size: 'lg', 153 | 154 | //These items get passed to the child controller 155 | resolve: { 156 | loadUrl: () => { 157 | return this.loadedUrl; 158 | } 159 | } 160 | }); 161 | } 162 | 163 | openPortalUIEditor() { 164 | this.$modal.open({ 165 | templateUrl: '/app/portalUIEditor/PortalUIEditorDialog.html', 166 | controller: 'PortalUIEditorController', 167 | controllerAs: 'main', 168 | size: 'lg' 169 | }); 170 | } 171 | 172 | deployToAzure() { 173 | let url = 'http://armportaluiredirector.azurewebsites.net/?json=POST'; 174 | 175 | this.$http.post(url, this.template.templateDataObj).then((response) => { 176 | let cacheUrl = response.data; 177 | let portalUiUrl = 'https://portal.azure.com/#create/Microsoft.Template/uri/' + cacheUrl; 178 | 179 | this.$window.open(portalUiUrl); 180 | Telemetry.sendEvent('Template', 'DeployToAzure'); 181 | }, (response) => { 182 | console.error('Not sure what to do: ' + response); 183 | }); 184 | } 185 | 186 | toolboxItemClick(toolboxItem: ToolboxResource) { 187 | let jsonFileName = toolboxItem.getDefaultJsonFileName(); 188 | let resource: Resource; 189 | 190 | if (jsonFileName) { 191 | if (toolboxItem.defaultJson) { 192 | //Used the cached JSON and avoid a server trip 193 | resource = <Resource>JSON.parse(toolboxItem.defaultJson); //$http returns JSON as an object 194 | this.template.resources.push(resource); 195 | } else { 196 | //This is the first time getting this resource type 197 | this.$http.get('/assets/toolbox-data/' + jsonFileName) 198 | .success((data: any, status, headers, config) => { 199 | resource = <Resource>data; //$http returns JSON as an object 200 | toolboxItem.defaultJson = JSON.stringify(data); 201 | this.template.resources.push(resource); 202 | }).error((data, status, headers, config) => { 203 | //Fall back to using a primitive resource default JSON 204 | resource = new Resource(toolboxItem); 205 | this.template.resources.push(resource); 206 | Telemetry.sendEvent('Error', 'RequestForToolboxJSONFailed', jsonFileName + ': ' + status + ': ' + data); 207 | }); 208 | } 209 | } else { 210 | //No default JSON, use something really basic 211 | resource = new Resource(toolboxItem); 212 | this.template.resources.push(resource); 213 | } 214 | 215 | this.template.parseParametersFromTemplate(); 216 | Telemetry.sendEvent('Toolbox', 'AddResource', toolboxItem.resourceType); 217 | } 218 | 219 | private downloadJsonInBrowser(json: string, fileName: string) { 220 | //Uses this file saver: https://github.com/Teleborder/FileSaver.js 221 | var blob = new Blob([json], { type: "text/plain;charset=utf-8" }); 222 | (<any>window).saveAs(blob, fileName); 223 | } 224 | 225 | private showFeedbackPopup() { 226 | let userCount = this.$cookies.get('userCount'); 227 | 228 | if (!userCount) { 229 | this.$cookies.put('userCount', 1); 230 | } else { 231 | if (userCount < 3) { 232 | userCount++; 233 | this.$cookies.put('userCount', userCount); 234 | } 235 | 236 | if (userCount === 2) { 237 | this.$timeout(() => { 238 | let feedbackNotify = ` 239 | Do you like this tool? Help us improve ArmViz by taking our 2 minutes 240 | <a href="http://www.instant.ly/s/DDMwi/" target="_blank">survey</a>`; 241 | this.growl.info(feedbackNotify); 242 | }, 10000); 243 | } 244 | } 245 | } 246 | } 247 | 248 | //Avoid compiler errors 249 | interface HTMLAnchorElement { 250 | download: string; 251 | } 252 | 253 | interface Window { 254 | URL: { 255 | createObjectURL(x); 256 | }; 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/app/main/main.html: -------------------------------------------------------------------------------- 1 | <nav class="navbar navbar-inverse navbar-fixed-top" ng-hide="main.hideChrome"> 2 | <div class="container-fluid"> 3 | <div class="navbar-header"> 4 | <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" 5 | aria-controls="navbar"> 6 | <span class="sr-only">Toggle navigation</span> 7 | <span class="icon-bar"></span> 8 | <span class="icon-bar"></span> 9 | <span class="icon-bar"></span> 10 | </button> 11 | <a class="navbar-brand" href="#">Azure Resource Manager Template Visualizer (ArmViz)</a> 12 | </div> 13 | 14 | <ul class="nav navbar-nav"> 15 | <li><a href="https://github.com/ytechie/AzureResourceVisualizer" target="_blank">About</a></li> 16 | <li><a href="https://github.com/Azure/azure-quickstart-templates" target="_blank">QuickStart Templates</a></li> 17 | </ul> 18 | <ul class="nav navbar-nav navbar-right"> 19 | <li><a href="http://www.instant.ly/s/DDMwi" class="smiley" title="Provide Feedback">☻</a></li> 20 | </ul> 21 | </div> 22 | </nav> 23 | 24 | <div class="container-fluid"> 25 | <div class="row"> 26 | <div class="sidebar toolbox" ng-hide="main.hideChrome"> 27 | 28 | <button class="btn btn-default" ng-click="main.openExistingTemplate()" style="width: 250px; margin-bottom: 3px;"> 29 | Open Existing Template 30 | </button> <br /> 31 | 32 | <button class="btn btn-default" ng-click="main.loadArmQuickstartTemplate()" style="width: 250px; margin-bottom: 3px;"> 33 | Load ARM Quickstart Template 34 | </button> <br ng-hide="!!main.loadUrl" /> 35 | 36 | <button class="btn btn-default" ng-click="main.openTemplateProperties()" style="width: 250px; margin-bottom: 3px;"> 37 | Edit Parameter Definitions 38 | </button><br /> 39 | 40 | <button class="btn btn-default" ng-click="main.downloadArmTemplate()" style="width: 250px; margin-bottom: 3px;"> 41 | Download ARM Template 42 | </button><br /> 43 | 44 | <span ng-hide="!main.loadedUrl"> 45 | <button class="btn btn-default" ng-click="main.createVisualizeButton()" style="width: 250px; margin-bottom: 3px;"> 46 | Create Visualizer Button 47 | </button><br /> 48 | </span> 49 | 50 | <button class="btn btn-default" ng-click="main.deployToAzure()" style="width: 250px; margin-bottom: 3px;"> 51 | Deploy To Azure 52 | </button><br /> 53 | 54 | <button class="btn btn-default" ng-click="main.openPortalUIEditor()" style="width: 250px; margin-bottom: 3px;"> 55 | Portal UI Editor 56 | </button><br /> 57 | </br /> 58 | 59 | <h4>Toolbox</h4> 60 | <a ng-repeat="toolboxItem in main.toolboxItems" class="itemContainer" ng-click="main.toolboxItemClick(toolboxItem)"> 61 | <img class="icon" src="/assets/toolbox-icons/{{toolboxItem.iconName}}" /> 62 | <span class="label">{{toolboxItem.friendlyName}}</span> 63 | </a> 64 | </div> 65 | <div class="col-md-offset-4 col-md-8 main"> 66 | <div id="paper" class="paper" style="height: 1000px; width: 800px;" /> 67 | </div> 68 | </div> 69 | </div> 70 | 71 | <div growl></div> 72 | -------------------------------------------------------------------------------- /src/app/main/sampleARM.ts: -------------------------------------------------------------------------------- 1 | module ArmViz { 2 | export var arm: any = { 3 | "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json", 4 | "contentVersion": "1.0.0.0", 5 | "parameters": { 6 | "storageAccountName": { 7 | "type": "string", 8 | "metadata": { 9 | "description": "Name of storage account" 10 | } 11 | }, 12 | "adminUsername": { 13 | "type": "string", 14 | "metadata": { 15 | "description": "Admin username" 16 | } 17 | }, 18 | "adminPassword": { 19 | "type": "securestring", 20 | "metadata": { 21 | "description": "Admin password" 22 | } 23 | }, 24 | "dnsNameforLBIP": { 25 | "type": "string", 26 | "metadata": { 27 | "description": "DNS for Load Balancer IP" 28 | } 29 | }, 30 | "vmSize": { 31 | "type": "string", 32 | "defaultValue": "Standard_D2", 33 | "metadata": { 34 | "description": "Size of the VM" 35 | } 36 | } 37 | }, 38 | "variables": { 39 | "storageAccountType": "Standard_LRS", 40 | "addressPrefix": "10.0.0.0/16", 41 | "subnetName": "Subnet-1", 42 | "subnetPrefix": "10.0.0.0/24", 43 | "publicIPAddressType": "Dynamic", 44 | "nic1NamePrefix": "nic1", 45 | "nic2NamePrefix": "nic2", 46 | "imagePublisher": "MicrosoftWindowsServer", 47 | "imageOffer": "WindowsServer", 48 | "imageSKU": "2012-R2-Datacenter", 49 | "vnetName": "myVNET", 50 | "publicIPAddressName": "myPublicIP", 51 | "lbName": "myLB", 52 | "vmNamePrefix": "myVM", 53 | "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('vnetName'))]", 54 | "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", 55 | "publicIPAddressID": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]", 56 | "lbID": "[resourceId('Microsoft.Network/loadBalancers',variables('lbName'))]", 57 | "frontEndIPConfigID": "[concat(variables('lbID'),'/frontendIPConfigurations/LoadBalancerFrontEnd')]", 58 | "lbPoolID": "[concat(variables('lbID'),'/backendAddressPools/BackendPool1')]" 59 | }, 60 | "resources": [ 61 | { 62 | "type": "Microsoft.Storage/storageAccounts", 63 | "name": "[parameters('storageAccountName')]", 64 | "apiVersion": "2015-05-01-preview", 65 | "location": "[resourceGroup().location]", 66 | "properties": { 67 | "accountType": "[variables('storageAccountType')]" 68 | } 69 | }, 70 | { 71 | "apiVersion": "2015-05-01-preview", 72 | "type": "Microsoft.Network/publicIPAddresses", 73 | "name": "[variables('publicIPAddressName')]", 74 | "location": "[resourceGroup().location]", 75 | "properties": { 76 | "publicIPAllocationMethod": "[variables('publicIPAddressType')]", 77 | "dnsSettings": { 78 | "domainNameLabel": "[parameters('dnsNameforLBIP')]" 79 | } 80 | } 81 | }, 82 | { 83 | "apiVersion": "2015-05-01-preview", 84 | "type": "Microsoft.Network/virtualNetworks", 85 | "name": "[variables('vnetName')]", 86 | "location": "[resourceGroup().location]", 87 | "properties": { 88 | "addressSpace": { 89 | "addressPrefixes": [ 90 | "[variables('addressPrefix')]" 91 | ] 92 | }, 93 | "subnets": [ 94 | { 95 | "name": "[variables('subnetName')]", 96 | "properties": { 97 | "addressPrefix": "[variables('subnetPrefix')]" 98 | } 99 | } 100 | ] 101 | } 102 | }, 103 | { 104 | "apiVersion": "2015-05-01-preview", 105 | "type": "Microsoft.Network/networkInterfaces", 106 | "name": "[variables('nic1NamePrefix')]", 107 | "location": "[resourceGroup().location]", 108 | "dependsOn": [ 109 | "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]", 110 | "[concat('Microsoft.Network/loadBalancers/', variables('lbName'))]" 111 | ], 112 | "properties": { 113 | "ipConfigurations": [ 114 | { 115 | "name": "ipconfig1", 116 | "properties": { 117 | "privateIPAllocationMethod": "Dynamic", 118 | "subnet": { 119 | "id": "[variables('subnetRef')]" 120 | }, 121 | "loadBalancerBackendAddressPools": [ 122 | { 123 | "id": "[concat(variables('lbID'), '/backendAddressPools/BackendPool1')]" 124 | } 125 | ], 126 | "loadBalancerInboundNatRules": [ 127 | { 128 | "id": "[concat(variables('lbID'),'/inboundNatRules/RDP-VM0')]" 129 | } 130 | ] 131 | } 132 | } 133 | ] 134 | } 135 | }, 136 | { 137 | "apiVersion": "2015-05-01-preview", 138 | "type": "Microsoft.Network/networkInterfaces", 139 | "name": "[variables('nic2NamePrefix')]", 140 | "location": "[resourceGroup().location]", 141 | "dependsOn": [ 142 | "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]" 143 | ], 144 | "properties": { 145 | "ipConfigurations": [ 146 | { 147 | "name": "ipconfig1", 148 | "properties": { 149 | "privateIPAllocationMethod": "Dynamic", 150 | "subnet": { 151 | "id": "[variables('subnetRef')]" 152 | } 153 | } 154 | } 155 | ] 156 | } 157 | }, 158 | { 159 | "apiVersion": "2015-05-01-preview", 160 | "name": "[variables('lbName')]", 161 | "type": "Microsoft.Network/loadBalancers", 162 | "location": "[resourceGroup().location]", 163 | "dependsOn": [ 164 | "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]" 165 | ], 166 | "properties": { 167 | "frontendIPConfigurations": [ 168 | { 169 | "name": "LoadBalancerFrontEnd", 170 | "properties": { 171 | "publicIPAddress": { 172 | "id": "[variables('publicIPAddressID')]" 173 | } 174 | } 175 | } 176 | ], 177 | "backendAddressPools": [ 178 | { 179 | "name": "BackendPool1" 180 | } 181 | ], 182 | "inboundNatRules": [ 183 | { 184 | "name": "RDP-VM0", 185 | "properties": { 186 | "frontendIPConfiguration": { 187 | "id": "[variables('frontEndIPConfigID')]" 188 | }, 189 | "protocol": "tcp", 190 | "frontendPort": 50001, 191 | "backendPort": 3389, 192 | "enableFloatingIP": false 193 | } 194 | } 195 | ] 196 | } 197 | }, 198 | { 199 | "apiVersion": "2015-06-15", 200 | "type": "Microsoft.Compute/virtualMachines", 201 | "name": "[variables('vmNamePrefix')]", 202 | "location": "[resourceGroup().location]", 203 | "dependsOn": [ 204 | "[concat('Microsoft.Storage/storageAccounts/', parameters('storageAccountName'))]", 205 | "[concat('Microsoft.Network/networkInterfaces/', variables('nic1NamePrefix'))]", 206 | "[concat('Microsoft.Network/networkInterfaces/', variables('nic2NamePrefix'))]" 207 | ], 208 | "properties": { 209 | "hardwareProfile": { 210 | "vmSize": "[parameters('vmSize')]" 211 | }, 212 | "osProfile": { 213 | "computername": "[variables('vmNamePrefix')]", 214 | "adminUsername": "[parameters('adminUsername')]", 215 | "adminPassword": "[parameters('adminPassword')]" 216 | }, 217 | "storageProfile": { 218 | "imageReference": { 219 | "publisher": "[variables('imagePublisher')]", 220 | "offer": "[variables('imageOffer')]", 221 | "sku": "[variables('imageSKU')]", 222 | "version": "latest" 223 | }, 224 | "osDisk": { 225 | "name": "osdisk", 226 | "vhd": { 227 | "uri": "[concat('http://',parameters('storageAccountName'),'.blob.core.windows.net/vhds/','osdisk', '.vhd')]" 228 | }, 229 | "caching": "ReadWrite", 230 | "createOption": "FromImage" 231 | } 232 | }, 233 | "networkProfile": { 234 | "networkInterfaces": [ 235 | { 236 | "properties": { 237 | "primary": true 238 | }, 239 | "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nic1NamePrefix'))]" 240 | }, 241 | { 242 | "properties": { 243 | "primary": false 244 | }, 245 | "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nic2NamePrefix'))]" 246 | } 247 | ] 248 | }, 249 | "diagnosticsProfile": { 250 | "bootDiagnostics": { 251 | "enabled": "true", 252 | "storageUri": "[concat('http://',parameters('StorageAccountName'),'.blob.core.windows.net')]" 253 | } 254 | } 255 | } 256 | } 257 | ] 258 | }; 259 | } 260 | -------------------------------------------------------------------------------- /src/app/main/toolbox.scss: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | position: fixed; 3 | top: 51px; 4 | bottom: 0; 5 | left: 0; 6 | z-index: 1000; 7 | display: block; 8 | padding: 20px; 9 | overflow-x: hidden; 10 | overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ 11 | background-color: #f5f5f5; 12 | border-right: 1px solid #eee; 13 | width: 370px; 14 | 15 | #nodeProperties { 16 | height: 400px; 17 | width: 100%; 18 | } 19 | } 20 | 21 | .toolbox { 22 | .itemContainer { 23 | display: inline-block; 24 | white-space: normal; 25 | border-width: 1px; 26 | text-align: left; 27 | cursor: pointer; 28 | width: 150px; 29 | margin: 3px; 30 | text-decoration: none; 31 | 32 | .icon { 33 | height: 30px; 34 | width: 30px; 35 | margin: auto; 36 | } 37 | 38 | .label { 39 | padding-left: 10px; 40 | padding-right: 10px; 41 | color: black; 42 | } 43 | } 44 | 45 | border-right: 1px solid black; 46 | } -------------------------------------------------------------------------------- /src/app/openExistingTemplateDialog/OpenDialog.controller.ts: -------------------------------------------------------------------------------- 1 | /// <reference path="../../../typings/index.d.ts" /> 2 | 3 | module OpenDialog { 4 | export class Controller { 5 | private $scope: any; 6 | private $modalInstance: any; 7 | private $http; 8 | 9 | /** @ngInject */ 10 | constructor($scope, $modalInstance, $http) { 11 | this.$scope = $scope; 12 | this.$modalInstance = $modalInstance; 13 | this.$http = $http; 14 | 15 | this.$scope.blah = 'foo'; 16 | } 17 | 18 | cancel() { 19 | this.$modalInstance.dismiss('cancel'); 20 | } 21 | 22 | //this is using the FileSelect directive 23 | 24 | open() { 25 | var fileReader = new FileReader(); 26 | fileReader.onload = (e) => { 27 | var json = <string>(<any>e.target).result; 28 | let template = ArmViz.ArmTemplate.CreateFromJson(json); 29 | 30 | this.$modalInstance.close(template); 31 | }; 32 | fileReader.readAsText(this.$scope.file); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/app/openExistingTemplateDialog/OpenDialog.html: -------------------------------------------------------------------------------- 1 | <div class="modal-header"> 2 | <h3 class="modal-title">Open Template</h3> 3 | </div> 4 | <div class="modal-body"> 5 | <input type="file" ng-file-select="onFileSelect($files)" /> 6 | </div> 7 | <div class="modal-footer"> 8 | <button class="btn btn-primary" ng-click="main.open()" ng-disabled="!file">Open</button> 9 | <button class="btn btn-warning" ng-click="main.cancel()">Cancel</button> 10 | </div> 11 | -------------------------------------------------------------------------------- /src/app/portalUIEditor/PortalUIEditorDialog.controller.ts: -------------------------------------------------------------------------------- 1 | /// <reference path="../main/Resource.ts" /> 2 | /// <reference path="../../../typings/index.d.ts" /> 3 | 4 | module PortalUIEditor { 5 | export class ResourceEditorController { 6 | private $modalInstance: any; 7 | private $http: any; 8 | private $window: any; 9 | 10 | json: string; 11 | validationResult: string; 12 | 13 | /** @ngInject */ 14 | constructor($modalInstance: any, $http: any, $window: any) { 15 | this.$modalInstance = $modalInstance; 16 | this.$http = $http; 17 | this.$window = $window; 18 | 19 | ArmViz.Telemetry.sendEvent('PortalUIEditor', 'Open'); 20 | } 21 | 22 | validate() { 23 | try { 24 | JSON.parse(this.json); 25 | this.validationResult = "Valid JSON!"; 26 | ArmViz.Telemetry.sendEvent('PortalUIEditor', 'Validate', 'Passed'); 27 | } catch (err) { 28 | this.validationResult = "Invalid JSON: " + err.toString(); 29 | ArmViz.Telemetry.sendEvent('PortalUIEditor', 'Validate', 'Failed: ' + err.toString()); 30 | } 31 | } 32 | 33 | close() { 34 | this.$modalInstance.dismiss('cancel'); 35 | }; 36 | 37 | preview() { 38 | console.log('preview!'); 39 | var obj: any; 40 | try { 41 | obj = JSON.parse(this.json); 42 | } catch (err) { 43 | this.validationResult = "Invalid JSON: " + err.toString(); 44 | ArmViz.Telemetry.sendEvent('PortalUIEditor', 'Preview', 'Failed: ' + err.toString()); 45 | 46 | return null; 47 | } 48 | 49 | let url = 'http://armportaluiredirector.azurewebsites.net/?json=POST'; 50 | 51 | this.$http.post(url, obj).then((response) => { 52 | //console.log('Got response: ' + response); 53 | let cacheUrl = response.data; 54 | 55 | let portalUiUrl = 'https://portal.azure.com/#blade/Microsoft_Azure_Compute/CreateMultiVmWizardBlade/internal_bladeCallId/anything/internal_bladeCallerParams/{"initialData":{},"providerConfig":{"createUiDefinition":"{jsonUrl}"}}'; 56 | portalUiUrl = portalUiUrl.replace('{jsonUrl}', cacheUrl); 57 | 58 | this.$window.open(portalUiUrl); 59 | }, (response) => { 60 | console.error('Not sure what to do: ' + response); 61 | }); 62 | } 63 | 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/app/portalUIEditor/PortalUIEditorDialog.html: -------------------------------------------------------------------------------- 1 | <div class="modal-header"> 2 | <h3 class="modal-title">Portal UI Editor</h3> 3 | </div> 4 | <div class="modal-body"> 5 | <div ui-ace="{ mode: 'json' }" ng-model="main.json" style="height: 400px; width: 100%; max-height: 400px; resize: none;"> 6 | </div> 7 | 8 | <b>{{main.validationResult}}</b> 9 | </div> 10 | <div class="modal-footer"> 11 | <button class="btn btn-info" ng-click="main.validate()">Validate</button> 12 | <button class="btn btn-primary" ng-click="main.preview()">Preview</button> 13 | <button class="btn btn-warning" ng-click="main.close()">Close</button> 14 | </div> 15 | -------------------------------------------------------------------------------- /src/app/quickstartLoadDialog/DialogResult.ts: -------------------------------------------------------------------------------- 1 | /// <reference path="GithubTemplateReader.ts" /> 2 | /// <reference path="../../../typings/index.d.ts" /> 3 | 4 | module ArmViz { 5 | export class DialogResult { 6 | armTemplate: ArmTemplate; 7 | templateInfo: TemplateCategory; 8 | 9 | constructor(armTemplate: ArmTemplate, templateInfo: TemplateCategory) { 10 | this.armTemplate = armTemplate; 11 | this.templateInfo = templateInfo; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/quickstartLoadDialog/GithubTemplateReader.ts: -------------------------------------------------------------------------------- 1 | /// <reference path="../main/ArmTemplate.ts" /> 2 | 3 | /// <reference path="../../../typings/index.d.ts" /> 4 | 5 | ///repos/:owner/:repo/contents/:path 6 | 7 | module ArmViz { 8 | export class GithubTemplateReader { 9 | private GithubApiRoot: string = 'https://api.github.com/'; 10 | private GithubTemplateRoot: string = this.GithubApiRoot + 'repos/Azure/azure-quickstart-templates/contents/'; 11 | 12 | public getTemplateCategories($http: angular.IHttpProvider, callback: (categories: TemplateCategory[]) => void) { 13 | var reqUrl = this.GithubTemplateRoot; 14 | 15 | (<any>$http).get(reqUrl) 16 | .success((data: any[], status, headers, config) => { 17 | var categories = new Array<TemplateCategory>(); 18 | 19 | data.forEach(item => { 20 | if (item.type === 'dir' && item.name !== '1-CONTRIBUTION-GUIDE' && item.name !== '.github') { 21 | var newCategory = new TemplateCategory(); 22 | newCategory.name = item.name; 23 | newCategory.url = item.url; 24 | newCategory.html_url = item.html_url; 25 | 26 | categories.push(newCategory); 27 | } 28 | }); 29 | 30 | callback(categories); 31 | }) 32 | .error((data, status, headers, config) => { 33 | Telemetry.sendEvent('Error', 'RequestTemplateFromGitHubFailed', reqUrl + ': ' + status + ': ' + data); 34 | throw new Error('Error in GitHub template reader getting data from GitHub ' + data); 35 | }); 36 | } 37 | 38 | public getTemplateMetadata($http: angular.IHttpProvider, categoryData: TemplateCategory, callback: (metadata: TemplateMetadataInterface) => void) { 39 | (<any>$http).get(this.GithubTemplateRoot + categoryData.name + '/' + 'metadata.json') 40 | .success((data: any, status, headers, config) => { 41 | if (data.encoding !== "base64") { 42 | Telemetry.sendEvent('Error', 'GitHubTemplateMetadataNotBase64Encoded', categoryData.name); 43 | throw new Error("Github template reader was expecting base64 encoded file"); 44 | } 45 | 46 | var fileContents = atob(data.content); 47 | var metadata = <TemplateMetadataInterface>JSON.parse(fileContents); 48 | callback(metadata); 49 | }); 50 | } 51 | 52 | public getTemplate($http: angular.IHttpProvider, categoryData: TemplateCategory, 53 | callback: (armTemplate: ArmTemplate, parseError: string) => void) { 54 | 55 | let apiTemplateLink = this.GithubTemplateRoot + categoryData.name + '/' + 'azuredeploy.json'; 56 | categoryData.templateLink = 'https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/' + categoryData.name + '/' + 'azuredeploy.json'; 57 | 58 | (<any>$http).get(apiTemplateLink) 59 | .success((data: any, status, headers, config) => { 60 | if (data.encoding !== "base64") { 61 | Telemetry.sendEvent('Error', 'GitHubTemplateNotBase64Encoded', categoryData.name); 62 | throw new Error("Github template reader was expecting base64 encoded file"); 63 | } 64 | 65 | var base64 = data.content; 66 | var byteOrderMark = "77u/"; 67 | if (base64.substring(0, byteOrderMark.length) === byteOrderMark) { 68 | base64 = base64.substring(byteOrderMark.length, base64.length); 69 | } 70 | 71 | var fileContents = atob(base64); 72 | 73 | var armTemplate: ArmTemplate = null; 74 | var parseError: string; 75 | try { 76 | armTemplate = ArmTemplate.CreateFromJson(fileContents); 77 | } catch (err) { 78 | parseError = err.toString(); 79 | } 80 | callback(armTemplate, parseError); 81 | }); 82 | } 83 | } 84 | 85 | export class TemplateCategory { 86 | name: string; 87 | url: string; 88 | html_url: string; 89 | templateLink: string; 90 | } 91 | 92 | export interface TemplateMetadataInterface { 93 | itemDisplayName: string; 94 | description: string; 95 | summary: string; 96 | githubUsername: string; 97 | dateUpdated: string; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/app/quickstartLoadDialog/QuickstartLoadDialog.controller.ts: -------------------------------------------------------------------------------- 1 | /// <reference path="GithubTemplateReader.ts" /> 2 | /// <reference path="../../../typings/index.d.ts" /> 3 | 4 | module ArmViz { 5 | export class QuickstartLoadDialog { 6 | private $scope: any; 7 | private $modalInstance: any; 8 | private $http: any; 9 | 10 | private github: GithubTemplateReader; 11 | 12 | categories: TemplateCategory[]; 13 | templateMetadata: TemplateMetadataInterface; 14 | selectedCategory: TemplateCategory; 15 | 16 | /** @ngInject */ 17 | constructor($scope, $modalInstance, $http) { 18 | this.$scope = $scope; 19 | this.$modalInstance = $modalInstance; 20 | this.$http = $http; 21 | 22 | this.github = new GithubTemplateReader(); 23 | 24 | this.github.getTemplateCategories($http, (c) => this.categoriesReceived(c)); 25 | } 26 | 27 | categoriesReceived(categories: TemplateCategory[]): void { 28 | this.categories = categories; 29 | } 30 | 31 | categorySelected() { 32 | var category = this.selectedCategory; 33 | 34 | this.github.getTemplateMetadata(this.$http, category, metadata => { 35 | this.templateMetadata = metadata; 36 | }); 37 | } 38 | 39 | cancel() { 40 | this.$modalInstance.dismiss('cancel'); 41 | }; 42 | 43 | open() { 44 | var category = this.selectedCategory; 45 | 46 | this.github.getTemplate(this.$http, category, (armTemplate, parseError) => { 47 | if (parseError) { 48 | alert('Error parsing template: ' + parseError); 49 | Telemetry.sendEvent('Error', 'ParseQuickstartTemplateFailed', category + ': ' + parseError); 50 | return; 51 | } 52 | this.$modalInstance.close(new DialogResult(armTemplate, category)); 53 | }); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/app/quickstartLoadDialog/QuickstartLoadDialog.html: -------------------------------------------------------------------------------- 1 | <div class="modal-header"> 2 | <h3 class="modal-title">Quickstart Template Loader</h3> 3 | </div> 4 | <div class="modal-body"> 5 | <select ng-model="main.selectedCategory" ng-options="category.name for category in main.categories" ng-change="main.categorySelected()"> 6 | </select> 7 | 8 | <div ng-show="main.templateMetadata"> 9 | <br /> 10 | <b>Name:</b> {{ main.templateMetadata.itemDisplayName }}<br /> 11 | <b>Description:</b> {{ main.templateMetadata.description }}<br /> 12 | <b>Summary:</b> {{ main.templateMetadata.summary }}<br /> 13 | <b>GitHub Username:</b> {{ main.templateMetadata.githubUsername }}<br /> 14 | <b>Date Updated:</b> {{ main.templateMetadata.dateUpdated }}<br /> 15 | </div> 16 | 17 | </div> 18 | <div class="modal-footer"> 19 | <button class="btn btn-primary" ng-click="main.open()">Open</button> 20 | <button class="btn btn-warning" ng-click="main.cancel()">Cancel</button> 21 | </div> 22 | -------------------------------------------------------------------------------- /src/app/resourceEditorDialog/ResourceEditor.controller.ts: -------------------------------------------------------------------------------- 1 | /// <reference path="../main/Resource.ts" /> 2 | /// <reference path="../../../typings/index.d.ts" /> 3 | 4 | module ArmViz { 5 | export class ResourceEditorController { 6 | private $modalInstance: any; 7 | 8 | private arm: ArmTemplate; 9 | private resource: Resource; 10 | 11 | resourceJson: string; 12 | validationResult: string; 13 | 14 | /** @ngInject */ 15 | constructor($modalInstance: any, arm: ArmTemplate, resource: Resource) { 16 | this.$modalInstance = $modalInstance; 17 | 18 | this.arm = arm; 19 | this.resource = resource; 20 | this.resourceJson = JSON.stringify(this.resource, null, 2); 21 | 22 | Telemetry.sendEvent('ResourceEditor', 'Open', this.resource.type); 23 | } 24 | 25 | validate() { 26 | try { 27 | JSON.parse(this.resourceJson); 28 | this.validationResult = "Valid JSON!"; 29 | Telemetry.sendEvent('ResourceEditor', 'Validate', 'Passed'); 30 | } catch (err) { 31 | this.validationResult = "Invalid JSON: " + err.toString(); 32 | Telemetry.sendEvent('ResourceEditor', 'Validate', 'Failed: ' + this.resource.type + ": " + err.toString()); 33 | } 34 | } 35 | 36 | delete() { 37 | if (!confirm("Are you sure you want to delete this resource?")) { 38 | return; 39 | } 40 | 41 | let resource = this.resource; 42 | (<any>resource).deleteFlag = true; 43 | 44 | this.$modalInstance.close(resource); 45 | 46 | Telemetry.sendEvent('ResourceEditor', 'Delete', this.resource.type); 47 | } 48 | 49 | save() { 50 | var newResource: Resource; 51 | 52 | try { 53 | newResource = JSON.parse(this.resourceJson); 54 | } catch (err) { 55 | alert('Invalid JSON: ' + err.toString()); 56 | Telemetry.sendEvent('ResourceEditor', 'SaveFailed-InvalidJSON', this.resource.type + ": " + err.toString()); 57 | return; 58 | } 59 | 60 | //Update the EXISTING resource without destroying it 61 | for (var key in this.resource) { 62 | if (this.resource.hasOwnProperty(key)) { 63 | delete this.resource[key]; 64 | } 65 | } 66 | 67 | $.extend(this.resource, newResource); 68 | 69 | this.$modalInstance.close(this.resource); 70 | 71 | Telemetry.sendEvent('ResourceEditor', 'Save', this.resource.type); 72 | }; 73 | 74 | cancel() { 75 | this.$modalInstance.dismiss('cancel'); 76 | }; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/app/resourceEditorDialog/ResourceEditor.html: -------------------------------------------------------------------------------- 1 | <div class="modal-header"> 2 | <h3 class="modal-title">Resource Properties</h3> 3 | </div> 4 | <div class="modal-body"> 5 | <div ui-ace="{ mode: 'json' }" ng-model="main.resourceJson" style="height: 400px; width: 100%; max-height: 400px; resize: none;"> 6 | </div> 7 | 8 | <b>{{main.validationResult}}</b> 9 | </div> 10 | <div class="modal-footer"> 11 | <button class="btn btn-danger" ng-click="main.delete()">Delete</button> 12 | <button class="btn btn-info" ng-click="main.validate()">Validate</button> 13 | <button class="btn btn-primary" ng-click="main.save()">Save</button> 14 | <button class="btn btn-warning" ng-click="main.cancel()">Cancel</button> 15 | </div> 16 | -------------------------------------------------------------------------------- /src/app/templateParameterEditor/ParameterModelItem.ts: -------------------------------------------------------------------------------- 1 | module TemplateParameterEditor { 2 | export class ParameterModelItem { 3 | name:string; 4 | type:string; 5 | defaultValue:string; 6 | allowedValues:string[]; 7 | value: string; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/app/templateParameterEditor/ParameterValueManager.ts: -------------------------------------------------------------------------------- 1 | module TemplateParameterEditor { 2 | export function generateJSON(parameterModelItems: ParameterModelItem[]) { 3 | var template = new DeploymentParameters(); 4 | template.$schema = "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentParameters.json"; 5 | template.contentVersion = "1.0.0.0"; 6 | template.parameters = { p: Parameter }; 7 | parameterModelItems.forEach(param => { 8 | template.parameters[param.name] = new Parameter(param.value); 9 | }); 10 | 11 | var json = JSON.stringify(template, null, 2); 12 | 13 | return json; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/templateParameterEditor/ParameterValuesTemplate.ts: -------------------------------------------------------------------------------- 1 | module TemplateParameterEditor { 2 | 3 | export class DeploymentParameters implements ParameterValuesTemplateInterface { 4 | $schema: string; 5 | contentVersion: string; 6 | parameters: { p: typeof Parameter }; 7 | // todo: modify constructor with default values for properties 8 | constructor() { 9 | // this.$schema = "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentParameters.json#"; 10 | // this.contentVersion = "1.0.0.0"; 11 | // this.parameters = {p: Parameter}; 12 | } 13 | } 14 | 15 | export class Parameter implements ParameterInterface { 16 | value: any; 17 | //metadata: MetadataInterface; 18 | constructor(val: string) { 19 | this.value = val; 20 | //this.metadata = md; 21 | } 22 | } 23 | 24 | // export class Metadata implements MetadataInterface{ 25 | // type: string; 26 | // description: string; 27 | // constructor(type: string, desc?: string){ 28 | // this.type = type; 29 | // this.description = desc; 30 | // } 31 | // } 32 | 33 | export interface ParameterValuesTemplateInterface { 34 | $schema: string; 35 | contentVersion: string; 36 | parameters: { p: typeof Parameter }; 37 | } 38 | 39 | 40 | export interface ParameterInterface { 41 | value: any; // need to change this later for $ref in schema 42 | // metadata?: MetadataInterface; 43 | } 44 | 45 | // export interface MetadataInterface { 46 | // type: string; 47 | // description?: string; 48 | // } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/app/templateParameterEditor/TemplateParameterManager.ts: -------------------------------------------------------------------------------- 1 | /// <reference path="../main/ArmTemplate.ts" /> 2 | /// <reference path="../main/Parameter.ts" /> 3 | 4 | module TemplateParameterEditor { 5 | export class TemplateParameterManager { 6 | armTemplate: ArmViz.ArmTemplate; 7 | 8 | constructor(armTemplate: ArmViz.ArmTemplate) { 9 | this.armTemplate = armTemplate; 10 | } 11 | 12 | public getParameterNames(): string[] { 13 | return Object.keys(this.armTemplate.parameters); 14 | } 15 | 16 | public getParameterData(propertyName: string): ArmViz.Parameter { 17 | return this.armTemplate.parameters[propertyName]; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/templateParameterEditor/TemplateProperties.html: -------------------------------------------------------------------------------- 1 | <div class="modal-header"> 2 | <h3 class="modal-title">Parameter Editor</h3> 3 | </div> 4 | <div class="modal-body"> 5 | <table> 6 | <thead> 7 | <td>Name</td> 8 | <td>Type</td> 9 | <td>Default Value</td> 10 | <td>Allowed Values</td> 11 | <td>Deployment Value</td> 12 | <td></td> 13 | </thead> 14 | <tbody> 15 | <tr ng-repeat="parameter in main.parameterModel"> 16 | <td><input type="text" ng-model="parameter.name" /></td> 17 | <td><input type="text" ng-model="parameter.type" style="width: 120px;" /></td> 18 | <td><input type="text" ng-model="parameter.defaultValue" /></td> 19 | <td><input type="text" ng-model="parameter.allowedValues" /></td> 20 | <td><input type="text" ng-model="parameter.value" /></td> 21 | <td><button ng-click="main.deleteParameter(parameter)">Delete</button></td> 22 | </tr> 23 | </tbody> 24 | </table> 25 | <button ng-click="main.addParameter()">Add</button> 26 | </div> 27 | <div class="modal-footer"> 28 | <button class="btn btn-primary" ng-click="main.save()">Save</button> 29 | <button class="btn btn-warning" ng-click="main.cancel()">Cancel</button> 30 | <button class="btn btn-info" ng-click="main.genParamValues()">Download Parameter Values JSON</button> 31 | </div> 32 | -------------------------------------------------------------------------------- /src/app/templateParameterEditor/templateParameterEditor.controller.ts: -------------------------------------------------------------------------------- 1 | /// <reference path="../../../typings/index.d.ts" /> 2 | 3 | module TemplateParameterEditor { 4 | export class Controller { 5 | private $modalInstance: any; 6 | private armTemplate: ArmViz.ArmTemplate; 7 | private templateParameterManager: TemplateParameterManager; 8 | 9 | parameterModel: Array<ParameterModelItem>; 10 | 11 | /** @ngInject */ 12 | constructor($modalInstance: any, armTemplate: ArmViz.ArmTemplate) { 13 | this.$modalInstance = $modalInstance; 14 | this.armTemplate = armTemplate; 15 | 16 | let templateParameterManager = new TemplateParameterManager(armTemplate); 17 | 18 | this.parameterModel = new Array<ParameterModelItem>(); 19 | 20 | let parameterNames = templateParameterManager.getParameterNames(); 21 | parameterNames.forEach(parameterName => { 22 | let parameterData = templateParameterManager.getParameterData(parameterName); 23 | this.parameterModel.push({ 24 | name: parameterName, 25 | type: parameterData.type, 26 | defaultValue: parameterData.defaultValue, 27 | allowedValues: parameterData.allowedValues, 28 | value: '' // get from values JSON, but we will need to create the UI to load a params json file first 29 | }); 30 | }); 31 | 32 | this.templateParameterManager = templateParameterManager; 33 | 34 | ArmViz.Telemetry.sendEvent('ParametersEditor', 'Open'); 35 | } 36 | 37 | save() { 38 | this.synchronizeModelToTemplate(this.parameterModel, this.templateParameterManager); 39 | 40 | this.$modalInstance.close(); 41 | ArmViz.Telemetry.sendEvent('ParametersEditor', 'Save'); 42 | } 43 | 44 | cancel() { 45 | this.$modalInstance.dismiss('cancel'); 46 | } 47 | 48 | addParameter() { 49 | var newModelItem = new ParameterModelItem(); 50 | this.parameterModel.push(newModelItem); 51 | ArmViz.Telemetry.sendEvent('ParametersEditor', 'AddParameter'); 52 | } 53 | 54 | deleteParameter(parameter) { 55 | var index = this.parameterModel.indexOf(parameter); 56 | this.parameterModel.splice(index, 1); 57 | ArmViz.Telemetry.sendEvent('ParametersEditor', 'DeleteParameter'); 58 | } 59 | 60 | genParamValues() { 61 | var paramValuesJSON = TemplateParameterEditor.generateJSON(this.parameterModel); 62 | this.downloadJsonInBrowser(paramValuesJSON, "parameters.json"); 63 | } 64 | 65 | private downloadJsonInBrowser(json: string, fileName: string) { 66 | //Uses this file saver: https://github.com/Teleborder/FileSaver.js 67 | var blob = new Blob([json], { type: "text/plain;charset=utf-8" }); 68 | (<any>window).saveAs(blob, fileName); 69 | } 70 | 71 | private synchronizeModelToTemplate(modelParameters: ParameterModelItem[], 72 | templateParameterManager: TemplateParameterManager) { 73 | 74 | var templateParameters = templateParameterManager.armTemplate.parameters; 75 | 76 | modelParameters.forEach(modelItem => { 77 | var foundParameter = templateParameters[modelItem.name]; 78 | 79 | if (foundParameter) { 80 | //Item is in both src and dest 81 | foundParameter.type = modelItem.type; 82 | foundParameter.defaultValue = modelItem.defaultValue; 83 | foundParameter.allowedValues = modelItem.allowedValues; 84 | } else { 85 | //Item is only in src 86 | var newParameter = new ArmViz.Parameter(modelItem.name, modelItem.type); 87 | newParameter.defaultValue = modelItem.defaultValue; 88 | newParameter.allowedValues = modelItem.allowedValues; 89 | 90 | templateParameterManager.armTemplate.parameters[modelItem.name] = newParameter; 91 | } 92 | }); 93 | 94 | //Handle item only in dest 95 | var templateParameterNames = templateParameterManager.getParameterNames(); 96 | templateParameterNames.forEach(templateParameterName => { 97 | var match = _.find(modelParameters, val => { 98 | return val.name === templateParameterName; 99 | }); 100 | 101 | if (!match) { 102 | delete templateParameterManager.armTemplate.parameters[templateParameterName]; 103 | } 104 | }); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/app/vendor.scss: -------------------------------------------------------------------------------- 1 | $icon-font-path: "../../bower_components/bootstrap-sass-official/assets/fonts/bootstrap/"; 2 | 3 | /* Do not remove this comments bellow. It's the markers used by wiredep to inject 4 | sass dependencies when defined in the bower.json of your dependencies */ 5 | // bower:scss 6 | // endbower 7 | -------------------------------------------------------------------------------- /src/assets/images/angular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/images/angular.png -------------------------------------------------------------------------------- /src/assets/images/bootstrap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/images/bootstrap.png -------------------------------------------------------------------------------- /src/assets/images/browsersync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/images/browsersync.png -------------------------------------------------------------------------------- /src/assets/images/gulp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/images/gulp.png -------------------------------------------------------------------------------- /src/assets/images/jasmine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/images/jasmine.png -------------------------------------------------------------------------------- /src/assets/images/karma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/images/karma.png -------------------------------------------------------------------------------- /src/assets/images/node-sass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/images/node-sass.png -------------------------------------------------------------------------------- /src/assets/images/protractor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/images/protractor.png -------------------------------------------------------------------------------- /src/assets/images/ui-bootstrap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/images/ui-bootstrap.png -------------------------------------------------------------------------------- /src/assets/images/yeoman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/images/yeoman.png -------------------------------------------------------------------------------- /src/assets/toolbox-data/Microsoft.Cdn.Profiles.Endpoints.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "2015-06-01", 3 | "location": "[variables('location')]", 4 | "name": "[concat(parameters('profileName'),'/',parameters('endpointName'))]", 5 | "type": "Microsoft.Cdn/profiles/endpoints", 6 | "properties": { 7 | "originHostHeader": "[replace(replace(reference(resourceId('Microsoft.Storage/storageAccounts', parameters('newStorageAccountName')),'2015-06-15' ).primaryEndpoints.blob,'https://',''),'/','')]", 8 | "isHttpAllowed": true, 9 | "isHttpsAllowed": true, 10 | "queryStringCachingBehavior": "IgnoreQueryString", 11 | "contentTypesToCompress": [ 12 | "text/plain", 13 | "text/html", 14 | "text/css", 15 | "application/x-javascript", 16 | "text/javascript" 17 | ], 18 | "isCompressionEnabled": "True", 19 | "origins": [ 20 | { 21 | "name": "origin1", 22 | "properties": { 23 | "hostName": "[replace(replace(reference(resourceId('Microsoft.Storage/storageAccounts', parameters('newStorageAccountName')),'2015-06-15' ).primaryEndpoints.blob,'https://',''),'/','')]" 24 | } 25 | } 26 | ] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/assets/toolbox-data/Microsoft.Cdn.Profiles.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "2015-06-01", 3 | "name": "[parameters('profileName')]", 4 | "type": "Microsoft.Cdn/profiles", 5 | "location": "[variables('location')]", 6 | "properties": { 7 | "sku": { 8 | "name": "Standard" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/assets/toolbox-data/Microsoft.Compute.virtualMachines.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "2015-06-15", 3 | "type": "Microsoft.Compute/virtualMachines", 4 | "name": "[variables('vmName')]", 5 | "location": "[variables('location')]", 6 | "properties": { 7 | "hardwareProfile": { 8 | "vmSize": "[variables('vmSize')]" 9 | }, 10 | "osProfile": { 11 | "computerName": "[variables('vmName')]", 12 | "adminUsername": "[parameters('adminUsername')]", 13 | "adminPassword": "[parameters('adminPassword')]" 14 | }, 15 | "storageProfile": { 16 | "imageReference": { 17 | "publisher": "[variables('imagePublisher')]", 18 | "offer": "[variables('imageOffer')]", 19 | "sku": "[parameters('windowsOSVersion')]", 20 | "version": "latest" 21 | }, 22 | "osDisk": { 23 | "name": "osdisk", 24 | "vhd": { 25 | "uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/',variables('OSDiskName'),'.vhd')]" 26 | }, 27 | "caching": "ReadWrite", 28 | "createOption": "FromImage" 29 | } 30 | }, 31 | "networkProfile": { 32 | "networkInterfaces": [ 33 | { 34 | "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]" 35 | } 36 | ] 37 | }, 38 | "diagnosticsProfile": { 39 | "bootDiagnostics": { 40 | "enabled": "true", 41 | "storageUri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net')]" 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/assets/toolbox-data/Microsoft.Network.loadBalancers.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "2015-05-01-preview", 3 | "name": "[variables('lbName')]", 4 | "type": "Microsoft.Network/loadBalancers", 5 | "location": "[resourceGroup().location]", 6 | "properties": { 7 | "frontendIPConfigurations": [ 8 | { 9 | "name": "LoadBalancerFrontEnd", 10 | "properties": { 11 | "publicIPAddress": { 12 | "id": "[variables('publicIPAddressID')]" 13 | } 14 | } 15 | } 16 | ], 17 | "backendAddressPools": [ 18 | { 19 | "name": "BackendPool1" 20 | } 21 | ], 22 | "inboundNatRules": [ 23 | { 24 | "name": "RDP-VM0", 25 | "properties": { 26 | "frontendIPConfiguration": { 27 | "id": "[variables('frontEndIPConfigID')]" 28 | }, 29 | "protocol": "tcp", 30 | "frontendPort": 50001, 31 | "backendPort": 3389, 32 | "enableFloatingIP": false 33 | } 34 | } 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/assets/toolbox-data/Microsoft.Network.networkInterfaces.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "2015-05-01-preview", 3 | "type": "Microsoft.Network/networkInterfaces", 4 | "name": "[variables('nicName')]", 5 | "location": "[variables('location')]", 6 | "properties": { 7 | "ipConfigurations": [ 8 | { 9 | "name": "ipconfig1", 10 | "properties": { 11 | "privateIPAllocationMethod": "Dynamic", 12 | "publicIPAddress": { 13 | "id": "[resourceId('Microsoft.Network/publicIPAddresses',variables('publicIPAddressName'))]" 14 | }, 15 | "subnet": { 16 | "id": "[variables('subnetRef')]" 17 | } 18 | } 19 | } 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/assets/toolbox-data/Microsoft.Network.publicIPAddresses.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "2015-05-01-preview", 3 | "type": "Microsoft.Network/publicIPAddresses", 4 | "name": "[variables('publicIPAddressName')]", 5 | "location": "[variables('location')]", 6 | "properties": { 7 | "publicIPAllocationMethod": "[variables('publicIPAddressType')]", 8 | "dnsSettings": { 9 | "domainNameLabel": "[parameters('dnsNameForPublicIP')]" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/assets/toolbox-data/Microsoft.Network.virtualNetworks.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "2015-05-01-preview", 3 | "type": "Microsoft.Network/virtualNetworks", 4 | "name": "[variables('virtualNetworkName')]", 5 | "location": "[variables('location')]", 6 | "properties": { 7 | "addressSpace": { 8 | "addressPrefixes": [ 9 | "[variables('addressPrefix')]" 10 | ] 11 | }, 12 | "subnets": [ 13 | { 14 | "name": "[variables('subnetName')]", 15 | "properties": { 16 | "addressPrefix": "[variables('subnetPrefix')]" 17 | } 18 | } 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/assets/toolbox-data/Microsoft.Storage.storageAccounts.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Microsoft.Storage/storageAccounts", 3 | "name": "[parameters('newStorageAccountName')]", 4 | "apiVersion": "2015-05-01-preview", 5 | "location": "[variables('location')]", 6 | "properties": { 7 | "accountType": "[variables('storageAccountType')]" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/assets/toolbox-icons/API App.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/API App.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/API Management.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/API Management.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Access Control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Access Control.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/App Service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/App Service.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Application Insights.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Application Insights.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Autoscaling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Autoscaling.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Availability Set.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Availability Set.PNG -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Azure (automatic) load balancer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Azure (automatic) load balancer.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Azure Active Directory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Azure Active Directory.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Azure Batch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Azure Batch.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Azure Cache including Redis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Azure Cache including Redis.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Azure Certificate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Azure Certificate.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Azure DNS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Azure DNS.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Azure Files Service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Azure Files Service.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Azure Marketplace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Azure Marketplace.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Azure Rights Management (RMS).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Azure Rights Management (RMS).png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Azure SDK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Azure SDK.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Azure SQL Database.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Azure SQL Database.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Azure Search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Azure Search.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Azure alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Azure alert.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Azure automation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Azure automation.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Azure load balancer (feature).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Azure load balancer (feature).png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Azure load balancer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Azure load balancer.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Azure subscription.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Azure subscription.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Backup Service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Backup Service.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Bitbucket code source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Bitbucket code source.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/BizTalk Services.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/BizTalk Services.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Cloud Service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Cloud Service.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Cloud, Office 365.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Cloud, Office 365.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/CodePlex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/CodePlex.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Content Delivery Network (CDN).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Content Delivery Network (CDN).png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Data Factory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Data Factory.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Default Resource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Default Resource.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Deployment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Deployment.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/DocumentDB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/DocumentDB.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Dropbox code source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Dropbox code source.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Event Hubs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Event Hubs.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/ExpressRoute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/ExpressRoute.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Git repository.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Git repository.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/GitHub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/GitHub.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/HDInsight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/HDInsight.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Health monitoring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Health monitoring.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Healthy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Healthy.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Hybrid Connections (BizTalk).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Hybrid Connections (BizTalk).png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Hybrid connection manager for BizTalk Hybrid Connection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Hybrid connection manager for BizTalk Hybrid Connection.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/IoT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/IoT.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Key Vault.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Key Vault.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Logic App.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Logic App.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Machine Learning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Machine Learning.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Media Services.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Media Services.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Microsoft Azure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Microsoft Azure.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Microsoft account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Microsoft account.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Mobile App (was Mobile Services).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Mobile App (was Mobile Services).png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Mobile Engagement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Mobile Engagement.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Multi-Factor Authentication.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Multi-Factor Authentication.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/MySQL database.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/MySQL database.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/NIC.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/NIC.PNG -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Notification Hubs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Notification Hubs.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Notification topic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Notification topic.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/OS image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/OS image.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Office 365 subscription.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Office 365 subscription.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Office 365.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Office 365.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Operational Insights.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Operational Insights.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/RemoteApp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/RemoteApp.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Resource group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Resource group.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/SQL Data Sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/SQL Data Sync.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/SQL Database (generic).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/SQL Database (generic).png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Scheduler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Scheduler.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Service Bus Queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Service Bus Queue.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Service Bus Relay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Service Bus Relay.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Service Bus Topic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Service Bus Topic.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Service Bus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Service Bus.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Service Endpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Service Endpoint.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Service Fabric.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Service Fabric.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Service package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Service package.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Site Recovery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Site Recovery.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Startup task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Startup task.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/StorSimple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/StorSimple.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Storage (Azure).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Storage (Azure).png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Storage blob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Storage blob.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Storage queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Storage queue.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Storage table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Storage table.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Stream Analytics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Stream Analytics.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Traffic Manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Traffic Manager.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Unidentified feature object.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Unidentified feature object.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/VHD data disk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/VHD data disk.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/VHD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/VHD.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/VM symbol only.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/VM symbol only.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/VPN Gateway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/VPN Gateway.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Virtual Network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Virtual Network.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Virtual machine container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Virtual machine container.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Virtual machine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Virtual machine.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Visual Studio Online.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Visual Studio Online.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Web App (was Websites).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Web App (was Websites).png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Web role.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Web role.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Web roles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Web roles.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/WebJobs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/WebJobs.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Work account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Work account.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Worker role.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Worker role.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/Worker roles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/Worker roles.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/cdn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/cdn.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/rdp remoting file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/rdp remoting file.png -------------------------------------------------------------------------------- /src/assets/toolbox-icons/rpd Remoting file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/assets/toolbox-icons/rpd Remoting file.png -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | <!doctype html> 2 | <html ng-app="ArmViz"> 3 | 4 | <head> 5 | <meta charset="utf-8"> 6 | <title>Azure Resource Visualizer 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 |
32 | 33 | 34 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "noImplicitAny": false, 7 | "removeComments": true, 8 | "noLib": false, 9 | "sourceMap": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/visualizebutton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ytechie/AzureResourceVisualizer/430f501cd7026574deaa5b03654d3c45eb3d7546/src/visualizebutton.png -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "ban": [true, 4 | ["_", "extend"], 5 | ["_", "isNull"], 6 | ["_", "isDefined"] 7 | ], 8 | "class-name": true, 9 | "comment-format": [false, 10 | "check-space", 11 | "check-lowercase" 12 | ], 13 | "curly": true, 14 | "eofline": false, 15 | "forin": true, 16 | "indent": [true, 2], 17 | "interface-name": false, 18 | "jsdoc-format": true, 19 | "label-position": true, 20 | "label-undefined": true, 21 | "max-line-length": [false, 140], 22 | "member-ordering": [false, 23 | "public-before-private", 24 | "static-before-instance", 25 | "variables-before-functions" 26 | ], 27 | "no-arg": true, 28 | "no-bitwise": true, 29 | "no-console": [true, 30 | "debug", 31 | "info", 32 | "time", 33 | "timeEnd", 34 | "trace" 35 | ], 36 | "no-construct": true, 37 | "no-constructor-vars": true, 38 | "no-debugger": true, 39 | "no-duplicate-key": true, 40 | "no-duplicate-variable": true, 41 | "no-empty": true, 42 | "no-eval": true, 43 | "no-string-literal": true, 44 | "no-switch-case-fall-through": true, 45 | "no-trailing-whitespace": false, 46 | "no-unused-expression": true, 47 | "no-unused-variable": true, 48 | "no-unreachable": true, 49 | "no-use-before-declare": true, 50 | "no-var-requires": true, 51 | "one-line": [true, 52 | "check-open-brace", 53 | "check-catch", 54 | "check-else", 55 | "check-whitespace" 56 | ], 57 | "quotemark": [false, "single"], 58 | "radix": true, 59 | "semicolon": true, 60 | "triple-equals": [true, "allow-null-check"], 61 | "typedef": [false, 62 | "callSignature", 63 | "indexSignature", 64 | "parameter", 65 | "propertySignature", 66 | "variableDeclarator" 67 | ], 68 | "typedef-whitespace": [true, 69 | ["callSignature", "noSpace"], 70 | ["catchClause", "noSpace"], 71 | ["indexSignature", "space"] 72 | ], 73 | "use-strict": [false, 74 | "check-module", 75 | "check-function" 76 | ], 77 | "variable-name": false, 78 | "whitespace": [false, 79 | "check-branch", 80 | "check-decl", 81 | "check-operator", 82 | "check-separator", 83 | "check-type" 84 | ] 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AzureResourceVisualizer", 3 | "globalDependencies": { 4 | "ace": "registry:dt/ace#0.0.0+20160317120654", 5 | "angular": "registry:dt/angular#1.5.0+20160613151008", 6 | "angular-growl-v2": "registry:dt/angular-growl-v2#0.7.5+20160317120654", 7 | "angular-ui-router": "registry:dt/angular-ui-router#1.1.5+20160521151413", 8 | "backbone": "registry:dt/backbone#1.0.0+20160316155526", 9 | "backbone-global": "registry:dt/backbone-global#1.0.0+20160509145756", 10 | "dagre": "registry:dt/dagre#0.7.0+20160505164908", 11 | "google.analytics/ga": "registry:dt/google.analytics/ga#0.0.0+20160317120654", 12 | "gulp": "registry:dt/gulp#3.8.0+20160316155526", 13 | "jasmine": "registry:dt/jasmine#2.2.0+20160505161446", 14 | "jointjs": "registry:dt/jointjs#0.9.3+20160323123346", 15 | "jquery": "registry:dt/jquery#1.10.0+20160417213236", 16 | "knockout": "registry:dt/knockout#0.0.0+20160428162840", 17 | "lodash": "registry:dt/lodash#3.10.0+20160330154726", 18 | "node": "registry:dt/node#6.0.0+20160613154055", 19 | "orchestrator": "registry:dt/orchestrator#0.0.0+20160316155526", 20 | "q": "registry:dt/q#0.0.0+20160613154756" 21 | } 22 | } 23 | --------------------------------------------------------------------------------