Added Big Data Cluster Viewlet to ADS (#6204)
@@ -1 +0,0 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32"><defs><style>.cls-1{fill:none;}.cls-2{clip-path:url(#clip-path);}.cls-3{clip-path:url(#clip-path-2);}.cls-4{clip-path:url(#clip-path-3);}.cls-5{clip-path:url(#clip-path-4);}.cls-6{clip-path:url(#clip-path-5);}.cls-7{clip-path:url(#clip-path-6);}.cls-8{clip-path:url(#clip-path-7);}.cls-9{clip-path:url(#clip-path-8);}.cls-10{fill:#68217a;}</style><clipPath id="clip-path"><rect class="cls-1" x="39.15" y="47.45" width="15.81" height="13.48"/></clipPath><clipPath id="clip-path-2"><rect class="cls-1" x="-754.07" y="415.73" width="15.81" height="13.48"/></clipPath><clipPath id="clip-path-3"><rect class="cls-1" x="-753.07" y="415.73" width="15.81" height="13.48"/></clipPath><clipPath id="clip-path-4"><rect class="cls-1" x="-750.43" y="422" width="10.73" height="0.43"/></clipPath><clipPath id="clip-path-5"><rect class="cls-1" x="-750.13" y="420.77" width="10.14" height="0.43"/></clipPath><clipPath id="clip-path-6"><rect class="cls-1" x="-749.72" y="419.53" width="9.31" height="0.43"/></clipPath><clipPath id="clip-path-7"><rect class="cls-1" x="-749.32" y="418.29" width="8.5" height="0.43"/></clipPath><clipPath id="clip-path-8"><rect class="cls-1" x="-748.89" y="417.05" width="7.65" height="0.43"/></clipPath></defs><title>AKS</title><path class="cls-10" d="M14.66,10.18,9.79,12V4.16l4.86,1.73Z"/><path class="cls-10" d="M5.31,5.5v5.18L9,12.1V4.16Zm.38,4.8-.19-.06V6l.19-.06.19-.06.26-.06v4.67L6,10.37A1.45,1.45,0,0,1,5.7,10.3Zm1,.26L6.4,10.5V5.7l.26-.06.26-.06.26-.06v5.25l-.26-.06Zm1.15.38-.32-.13V5.38l.32-.06.32-.13.38-.13v6.08L8.13,11C8.13,11.07,7.81,10.94,7.81,10.94Z"/><path class="cls-10" d="M26.11,10.18,21.25,12V4.16l4.86,1.73Z"/><path class="cls-10" d="M16.77,5.5v5.18l3.65,1.41V4.16Zm.38,4.8L17,10.24V6l.19-.06.19-.06.26-.06v4.67l-.19-.06A1.45,1.45,0,0,1,17.15,10.3Zm1,.26-.26-.06V5.7l.26-.06.26-.06.26-.06v5.25l-.26-.06Zm1.15.38-.32-.13V5.38l.32-.06.32-.13L20,5.06v6.08L19.58,11C19.58,11.07,19.26,10.94,19.26,10.94Z"/><path class="cls-10" d="M14.66,25.92,9.79,27.78V19.9l4.86,1.73Z"/><path class="cls-10" d="M5.31,21.25v5.18L9,27.84V19.9ZM5.7,26,5.5,26V21.7l.19-.06.19-.06.19-.06v4.67l-.19-.06Zm1,.26-.26-.06v-4.8l.26-.06.26-.06.26-.06V26.5l-.26-.06C6.91,26.37,6.66,26.3,6.66,26.3Zm1.15.38-.32-.13V21.12l.32-.06.32-.13.38-.13v6.08l-.38-.13C8.13,26.82,7.81,26.69,7.81,26.69Z"/><path class="cls-10" d="M26.11,25.92l-4.86,1.86V19.9l4.86,1.73Z"/><path class="cls-10" d="M16.77,21.25v5.18l3.65,1.41V19.9Zm.38,4.8L17,26V21.7l.19-.06.19-.06.19-.06v4.67l-.19-.06Zm1,.26-.26-.06v-4.8l.26-.06.26-.06.26-.06V26.5l-.26-.06C18.37,26.37,18.11,26.3,18.11,26.3Zm1.15.38-.32-.13V21.12l.32-.06.32-.13L20,20.8v6.08l-.38-.13C19.58,26.82,19.26,26.69,19.26,26.69Z"/><path class="cls-10" d="M20.74,18,15.87,19.9V12l4.86,1.73Z"/><path class="cls-10" d="M11.39,13.31V18.5L15,19.9V12Zm.38,4.8L11.58,18V13.76l.19-.06.19-.06.19-.06v4.67L12,18.18C12,18.18,11.78,18.11,11.78,18.11Zm1,.32-.26-.06v-4.8l.26-.06.26-.06.26-.06v5.25L13,18.56C13,18.5,12.74,18.43,12.74,18.43Zm1.15.38-.32-.13V13.25l.32-.06.32-.13.38-.13V19l-.38-.13Z"/><path class="cls-10" d="M9.34,18,4.48,19.9V12l4.86,1.73Z"/><path class="cls-10" d="M0,13.31V18.5L3.65,19.9V12Zm.38,4.8L.19,18V13.76l.19-.06H.64l.19-.06V18.3l-.19-.13Zm1,.32-.26-.06v-4.8l.26-.13.26-.06.26-.06v5.25L1.6,18.5Zm1.15.38-.32-.13V13.25l.32-.06.32-.13.38-.13V19l-.38-.13A2.77,2.77,0,0,0,2.5,18.82Z"/><path class="cls-10" d="M32,18,27.14,19.9V12L32,13.76Z"/><path class="cls-10" d="M22.66,13.31V18.5L26.3,19.9V12Zm.38,4.8L22.85,18V13.76L23,13.7l.19-.06.19-.06v4.67l-.19-.06C23.3,18.18,23,18.11,23,18.11Zm1,.32-.26-.06v-4.8L24,13.5l.26-.06.26-.06v5.25l-.26-.06C24.26,18.5,24,18.43,24,18.43Zm1.15.38-.32-.13V13.25l.32-.06.32-.13.38-.13V19l-.38-.13Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 3.7 KiB |
@@ -1 +0,0 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32"><defs><style>.cls-1{fill:none;}.cls-2{clip-path:url(#clip-path);}.cls-3{clip-path:url(#clip-path-2);}.cls-4{clip-path:url(#clip-path-3);}.cls-5{clip-path:url(#clip-path-4);}.cls-6{clip-path:url(#clip-path-5);}.cls-7{clip-path:url(#clip-path-6);}.cls-8{clip-path:url(#clip-path-7);}.cls-9{clip-path:url(#clip-path-8);}.cls-10{fill:#326ce5;}.cls-11{fill:#fff;stroke:#fff;stroke-width:0.25px;}</style><clipPath id="clip-path"><rect class="cls-1" x="39.15" y="85.81" width="15.81" height="13.48"/></clipPath><clipPath id="clip-path-2"><rect class="cls-1" x="-754.07" y="454.09" width="15.81" height="13.48"/></clipPath><clipPath id="clip-path-3"><rect class="cls-1" x="-753.07" y="454.09" width="15.81" height="13.48"/></clipPath><clipPath id="clip-path-4"><rect class="cls-1" x="-750.43" y="460.36" width="10.73" height="0.43"/></clipPath><clipPath id="clip-path-5"><rect class="cls-1" x="-750.13" y="459.12" width="10.14" height="0.43"/></clipPath><clipPath id="clip-path-6"><rect class="cls-1" x="-749.72" y="457.88" width="9.31" height="0.43"/></clipPath><clipPath id="clip-path-7"><rect class="cls-1" x="-749.32" y="456.64" width="8.5" height="0.43"/></clipPath><clipPath id="clip-path-8"><rect class="cls-1" x="-748.89" y="455.4" width="7.65" height="0.43"/></clipPath></defs><title>Kubernetes</title><g id="layer1"><g id="g3052"><path id="path3055" class="cls-10" d="M15.89,0a2.09,2.09,0,0,0-.82.21L3.95,5.69A2.17,2.17,0,0,0,2.8,7.17L.05,19.47a2.21,2.21,0,0,0,.29,1.67l.12.17,7.7,9.87A2.11,2.11,0,0,0,9.83,32H22.17a2.11,2.11,0,0,0,1.66-.82l7.7-9.87a2.21,2.21,0,0,0,.41-1.84L29.2,7.17A2.17,2.17,0,0,0,28,5.69L16.92.22A2.09,2.09,0,0,0,15.89,0Z"/><path id="path3059" class="cls-11" d="M16,4.19a.72.72,0,0,0-.67.76V5c0,.06,0,.13,0,.18a5.89,5.89,0,0,0,.09.65A6.6,6.6,0,0,1,15.5,7a.75.75,0,0,1-.22.35l0,.29a8.46,8.46,0,0,0-5.55,2.75l-.24-.18a.51.51,0,0,1-.4,0,6.29,6.29,0,0,1-.9-.84,5.65,5.65,0,0,0-.44-.48l-.15-.12a.78.78,0,0,0-.46-.18.63.63,0,0,0-.53.24.74.74,0,0,0,.16,1h0l.14.11a5.51,5.51,0,0,0,.55.33,6.22,6.22,0,0,1,1,.72.78.78,0,0,1,.13.4l.21.2a9.1,9.1,0,0,0-1.36,6.19l-.28.08a.94.94,0,0,1-.29.3,6,6,0,0,1-1.19.2,5.41,5.41,0,0,0-.64.05l-.18,0h0a.73.73,0,0,0-.56.84.7.7,0,0,0,.86.5h0l.17,0a5.46,5.46,0,0,0,.6-.24,6.08,6.08,0,0,1,1.16-.35.72.72,0,0,1,.38.14l.29-.05a8.88,8.88,0,0,0,3.84,4.94l-.12.3a.7.7,0,0,1,.06.39,6.77,6.77,0,0,1-.6,1.12,5.76,5.76,0,0,0-.36.55l-.09.18a.74.74,0,0,0,.28,1,.7.7,0,0,0,.92-.39h0l.08-.17a5.85,5.85,0,0,0,.19-.63,4.88,4.88,0,0,1,.52-1.23.53.53,0,0,1,.29-.14l.15-.28a8.36,8.36,0,0,0,5,.37,8.45,8.45,0,0,0,1.14-.35l.14.26a.52.52,0,0,1,.34.21A6.52,6.52,0,0,1,20,26.54a5.87,5.87,0,0,0,.19.63l.08.18a.7.7,0,0,0,.92.39.74.74,0,0,0,.28-1l-.09-.18a5.74,5.74,0,0,0-.36-.55,6.47,6.47,0,0,1-.59-1.09.55.55,0,0,1,.05-.4,2.4,2.4,0,0,1-.11-.28,8.89,8.89,0,0,0,3.84-5l.28.05a.52.52,0,0,1,.37-.14,6.08,6.08,0,0,1,1.16.35,5.47,5.47,0,0,0,.6.24l.17,0h0a.7.7,0,0,0,.86-.5.73.73,0,0,0-.56-.84l-.19,0a5.41,5.41,0,0,0-.64-.05,6,6,0,0,1-1.19-.2.76.76,0,0,1-.29-.3l-.27-.08a9.13,9.13,0,0,0-.14-3.2,9,9,0,0,0-1.25-3l.24-.22a.55.55,0,0,1,.13-.39,6.22,6.22,0,0,1,1-.72,5.53,5.53,0,0,0,.55-.33l.15-.12a.74.74,0,0,0,.16-1,.69.69,0,0,0-1-.06l-.15.12a5.67,5.67,0,0,0-.44.48,6.31,6.31,0,0,1-.9.84.72.72,0,0,1-.4,0l-.25.19A8.63,8.63,0,0,0,16.73,7.7c0-.09,0-.26,0-.31A.54.54,0,0,1,16.5,7a6.62,6.62,0,0,1,.08-1.24,5.89,5.89,0,0,0,.09-.65c0-.06,0-.14,0-.2A.72.72,0,0,0,16,4.19Zm-.83,5.32-.2,3.6h0a.6.6,0,0,1-.59.58.57.57,0,0,1-.35-.12h0l-2.86-2.09a6.79,6.79,0,0,1,4-2Zm1.67,0a6.84,6.84,0,0,1,4,2L18,13.58h0a.58.58,0,0,1-.81-.11.61.61,0,0,1-.13-.35h0Zm-6.72,3.33,2.61,2.41h0a.62.62,0,0,1,.07.83.59.59,0,0,1-.3.21h0l-3.35,1A7.24,7.24,0,0,1,10.11,12.84Zm11.75,0A7.28,7.28,0,0,1,22.72,15a7.36,7.36,0,0,1,.15,2.3l-3.37-1h0a.61.61,0,0,1-.23-1h0l2.6-2.4Zm-6.4,2.6h1.07l.67.86L17,17.36l-1,.48-1-.48-.24-1.07Zm3.43,2.94H19l3.47.6a7,7,0,0,1-2.78,3.59l-1.35-3.35h0a.61.61,0,0,1,.28-.78.57.57,0,0,1,.22-.06Zm-5.82,0a.59.59,0,0,1,.56.47.62.62,0,0,1,0,.37h0l-1.33,3.32A7.07,7.07,0,0,1,9.52,19l3.44-.6h.12ZM16,19.84a.57.57,0,0,1,.27.06.59.59,0,0,1,.26.26h0l1.69,3.16a6.86,6.86,0,0,1-.68.2,6.69,6.69,0,0,1-3.76-.2l1.69-3.15h0A.59.59,0,0,1,16,19.84Z"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -1,94 +1,111 @@
|
||||
{
|
||||
"name": "big-data-cluster",
|
||||
"displayName": "SQL Server big data cluster",
|
||||
"description": "SQL Server big data cluster",
|
||||
"version": "0.0.1",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
|
||||
"icon": "images/sqlserver.png",
|
||||
"aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412",
|
||||
"engines": {
|
||||
"vscode": "*",
|
||||
"azdata": ">=1.4.0"
|
||||
},
|
||||
"activationEvents": [
|
||||
"*"
|
||||
],
|
||||
"main": "./out/main",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Microsoft/azuredatastudio.git"
|
||||
},
|
||||
"extensionDependencies": [
|
||||
"Microsoft.mssql"
|
||||
],
|
||||
"contributes": {
|
||||
"configuration": {
|
||||
"type": "object",
|
||||
"title": "Kubernetes configuration",
|
||||
"properties": {
|
||||
"mssql-bdc": {
|
||||
"type": "object",
|
||||
"description": "Kubernetes configuration",
|
||||
"properties": {
|
||||
"mssql-bdc.kubectl-path": {
|
||||
"type": "string",
|
||||
"description": "File path to a kubectl binary."
|
||||
},
|
||||
"mssql-bdc.kubectl-path.windows": {
|
||||
"type": "string",
|
||||
"description": "File path to a kubectl binary."
|
||||
},
|
||||
"mssql-bdc.kubectl-path.mac": {
|
||||
"type": "string",
|
||||
"description": "File path to a kubectl binary."
|
||||
},
|
||||
"mssql-bdc.kubectl-path.linux": {
|
||||
"type": "string",
|
||||
"description": "File path to a kubectl binary."
|
||||
},
|
||||
"mssql-bdc.kubeconfig": {
|
||||
"type": "string",
|
||||
"description": "File path to the kubeconfig file."
|
||||
},
|
||||
"mssql-bdc.knownKubeconfigs": {
|
||||
"type": "array",
|
||||
"description": "File paths to kubeconfig files from which you can select."
|
||||
},
|
||||
"mssql-bdc.outputFormat": {
|
||||
"enum": [
|
||||
"json",
|
||||
"yaml"
|
||||
],
|
||||
"type": "string",
|
||||
"description": "Output format for Kubernetes specs. One of 'json' or 'yaml' (default)."
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"mssql-bdc.namespace": "",
|
||||
"mssql-bdc.kubectl-path": "",
|
||||
"mssql-bdc.kubeconfig": "",
|
||||
"mssql-bdc.knownKubeconfigs": []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"command": "mssql.cluster.create",
|
||||
"title": "Create SQL Server big data cluster",
|
||||
"category": "SQL Server"
|
||||
}
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-nls": "^3.2.1",
|
||||
"download": "^6.2.5",
|
||||
"shelljs": "^0.8.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha-junit-reporter": "^1.17.0",
|
||||
"mocha-multi-reporters": "^1.1.7"
|
||||
}
|
||||
"name": "big-data-cluster",
|
||||
"displayName": "%text.sqlServerBigDataClusters%",
|
||||
"description": "%description%",
|
||||
"version": "0.0.1",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
|
||||
"icon": "images/sqlserver.png",
|
||||
"engines": {
|
||||
"vscode": "*",
|
||||
"azdata": "*"
|
||||
},
|
||||
"activationEvents": [
|
||||
"*"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Microsoft/azuredatastudio.git"
|
||||
},
|
||||
"main": "./out/extension",
|
||||
"contributes": {
|
||||
"dataExplorer": {
|
||||
"sqlBigDataCluster": [
|
||||
{
|
||||
"id": "sqlBigDataCluster",
|
||||
"name": "%text.sqlServerBigDataClusters%"
|
||||
}
|
||||
]
|
||||
},
|
||||
"menus": {
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "bigDataClusters.command.addController",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.deleteController",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.refreshController",
|
||||
"when": "false"
|
||||
}
|
||||
],
|
||||
"view/title": [
|
||||
{
|
||||
"command": "bigDataClusters.command.addController",
|
||||
"when": "view == sqlBigDataCluster",
|
||||
"group": "navigation"
|
||||
}
|
||||
],
|
||||
"view/item/context": [
|
||||
{
|
||||
"command": "bigDataClusters.command.deleteController",
|
||||
"when": "viewItem == bigDataClusters.itemType.controllerNode",
|
||||
"group": "navigation@1"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.refreshController",
|
||||
"when": "viewItem == bigDataClusters.itemType.controllerNode",
|
||||
"group": "navigation@1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"configuration": {
|
||||
"type": "object",
|
||||
"title": "%text.sqlServerBigDataClusters%",
|
||||
"properties": {
|
||||
"clusterControllers.controllers": {
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"command": "bigDataClusters.command.addController",
|
||||
"title": "%command.addController.title%",
|
||||
"icon": {
|
||||
"light": "resources/light/add.svg",
|
||||
"dark": "resources/dark/add_inverse.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.deleteController",
|
||||
"title": "%command.deleteController.title%",
|
||||
"when": "viewItem == bigDataClusters.itemType.controllerNode"
|
||||
},
|
||||
{
|
||||
"command": "bigDataClusters.command.refreshController",
|
||||
"title": "%command.refreshController.title%",
|
||||
"icon": {
|
||||
"light": "resources/light/refresh.svg",
|
||||
"dark": "resources/dark/refresh_inverse.svg"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"request": "^2.88.0",
|
||||
"vscode-nls": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^8.0.24",
|
||||
"mocha": "^5.2.0",
|
||||
"should": "^13.2.1",
|
||||
"typemoq": "^2.1.0",
|
||||
"vscode": "^1.1.26"
|
||||
}
|
||||
}
|
||||
|
||||
7
extensions/big-data-cluster/package.nls.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"description": "Support for managing SQL Server Big Data Clusters",
|
||||
"text.sqlServerBigDataClusters": "SQL Server Big Data Clusters",
|
||||
"command.addController.title": "Connect to Controller",
|
||||
"command.deleteController.title" : "Delete",
|
||||
"command.refreshController.title" : "Refresh"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>add</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14,6v4H10v4H6V10H2V6H6V2h4V6Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M13,7V9H9v4H7V9H3V7H7V3H9V7Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 486 B |
@@ -0,0 +1 @@
|
||||
<svg id="Icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><defs><style>.cls-1{opacity:0;}.cls-2{fill:#f6f6f6;}.cls-3{fill:#424242;}.cls-4{fill:#f0eff1;}</style></defs><title>centralmanagement_server_16x</title><g id="canvas" class="cls-1"><rect class="cls-2" width="16" height="16"/></g><path id="outline" class="cls-2" d="M16,0H7V1H3V5H0V16H8V13h8ZM6,4H7V5H6Z"/><g id="iconBG"><path class="cls-3" d="M8,1V12h7V1Zm6,10H13V10h1Zm0-6H9V4h5Zm0-2H9V2h5Z"/><path class="cls-3" d="M1,6v9H7V6Zm5,8H5V13H6Zm0-4H2V9H6ZM6,8H2V7H6Z"/><rect class="cls-3" x="6" y="2" width="1" height="1"/><rect class="cls-3" x="4" y="4" width="1" height="1"/><rect class="cls-3" x="4" y="2" width="1" height="1"/></g><g id="iconFG"><path class="cls-4" d="M14,3H9V2h5Zm0,1H9V5h5Zm0,6H13v1h1Z"/><path class="cls-4" d="M6,8H2V7H6ZM6,9H2v1H6Zm0,4H5v1H6Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 869 B |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#231f20;}.cls-2{fill:#fff;}</style></defs><title>folder_inverse_16x16</title><polygon class="cls-1" points="13.59 2.34 13.58 2.35 13.58 2.33 13.59 2.34"/><text></text><path class="cls-2" d="M16,14.13H0v-12a1,1,0,0,1,.08-.39,1,1,0,0,1,.53-.53A1,1,0,0,1,1,1.13H4.75a2.16,2.16,0,0,1,.61.07,2.26,2.26,0,0,1,.45.18,2.14,2.14,0,0,1,.36.24l.32.24a1.8,1.8,0,0,0,.34.18,1.12,1.12,0,0,0,.43.07H15a1,1,0,0,1,.39.08,1,1,0,0,1,.53.53,1,1,0,0,1,.08.39ZM1,2.13v1H4.75a1.36,1.36,0,0,0,.33,0A1,1,0,0,0,5.34,3l.23-.16.25-.21-.25-.21-.23-.16a1,1,0,0,0-.26-.1,1.36,1.36,0,0,0-.33,0Zm14,11v-10H7.25a1.12,1.12,0,0,0-.43.07,1.8,1.8,0,0,0-.34.18l-.32.24a2.14,2.14,0,0,1-.36.24,2.26,2.26,0,0,1-.45.18,2.16,2.16,0,0,1-.61.07H1v9Z"/></svg>
|
||||
|
After Width: | Height: | Size: 830 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M13.451 5.609l-.579-.939-1.068.812-.076.094c-.335.415-.927 1.341-1.124 2.876l-.021.165.033.163.071.345c0 1.654-1.346 3-3 3-.795 0-1.545-.311-2.107-.868-.563-.567-.873-1.317-.873-2.111 0-1.431 1.007-2.632 2.351-2.929v2.926s2.528-2.087 2.984-2.461h.012l3.061-2.582-4.919-4.1h-1.137v2.404c-3.429.318-6.121 3.211-6.121 6.721 0 1.809.707 3.508 1.986 4.782 1.277 1.282 2.976 1.988 4.784 1.988 3.722 0 6.75-3.028 6.75-6.75 0-1.245-.349-2.468-1.007-3.536z" fill="#2D2D30"/><path d="M12.6 6.134l-.094.071c-.269.333-.746 1.096-.91 2.375.057.277.092.495.092.545 0 2.206-1.794 4-4 4-1.098 0-2.093-.445-2.817-1.164-.718-.724-1.163-1.718-1.163-2.815 0-2.206 1.794-4 4-4l.351.025v1.85s1.626-1.342 1.631-1.339l1.869-1.577-3.5-2.917v2.218l-.371-.03c-3.176 0-5.75 2.574-5.75 5.75 0 1.593.648 3.034 1.695 4.076 1.042 1.046 2.482 1.694 4.076 1.694 3.176 0 5.75-2.574 5.75-5.75-.001-1.106-.318-2.135-.859-3.012z" fill="#C5C5C5"/></svg>
|
||||
|
After Width: | Height: | Size: 986 B |
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
viewBox="0 0 16 16"
|
||||
data-name="Layer 1"
|
||||
id="Layer_1">
|
||||
<metadata
|
||||
id="metadata17">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>sql_bigdata_cluster</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs4">
|
||||
<style
|
||||
id="style2">.cls-1{fill:#212121;}.cls-2{fill:#231f20;}</style>
|
||||
</defs>
|
||||
<title
|
||||
id="title6">sql_bigdata_cluster</title>
|
||||
<path
|
||||
style="fill:#ffffff;stroke-width:1.00282443"
|
||||
id="path8"
|
||||
d="M 7.995,0 C 5.605,0 1.575,0.45254557 1.465,2.1319925 V 13.737272 C 1.465,15.517285 5.575,16 7.995,16 c 2.42,0 6.54,-0.482715 6.54,-2.262728 V 2.1319925 C 14.435,0.45254557 10.405,0 7.995,0 Z m 5.45,13.737272 c -0.14,0.392206 -2.18,1.166562 -5.45,1.166562 -3.27,0 -5.32,-0.784412 -5.43,-1.166562 V 3.5097423 a 14.67,14.752986 0 0 0 5.43,0.8749214 14.71,14.793212 0 0 0 5.45,-0.8749214 z m 0,-11.5549967 c -0.17,0.3922062 -2.19,1.1062225 -5.45,1.1062225 -3.26,0 -5.2,-0.6939032 -5.43,-1.0861094 0.23,-0.4022627 2.22,-1.1062225 5.43,-1.1062225 3.21,0 5.27,0.7240729 5.45,1.0659963 v 0 z"
|
||||
class="cls-1" />
|
||||
<polygon
|
||||
style="fill:#ffffff"
|
||||
transform="translate(0.075)"
|
||||
id="polygon10"
|
||||
points="13.57,2.35 13.58,2.36 13.57,2.37 "
|
||||
class="cls-2" />
|
||||
<path
|
||||
style="fill:#ffffff"
|
||||
id="path12"
|
||||
d="m 9.6501562,5.2372858 c -0.1362374,0 -0.2728654,0.026375 -0.4003906,0.082031 -0.123585,0.050567 -0.2358691,0.1260731 -0.3300781,0.2207031 -0.094256,0.096634 -0.1724299,0.2082024 -0.2304688,0.3300781 -0.062701,0.1283175 -0.099426,0.2676857 -0.109375,0.4101562 -0.00186,0.1267925 0.022265,0.2517914 0.070312,0.3691407 0.045212,0.1164344 0.1088696,0.2248797 0.1894531,0.3203125 L 8.2107031,7.9384577 C 8.011051,7.8519995 7.7980699,7.8002026 7.5798437,7.7997858 7.2852043,7.7997877 7.0158159,7.8890317 6.7790625,8.0283014 L 6.3435156,7.4677545 C 6.4851678,7.2819801 6.5620085,7.0548883 6.5622656,6.8212702 6.5623837,6.2311827 6.0839937,5.7527927 5.4939062,5.7529108 4.9038187,5.7527927 4.4254288,6.2311827 4.4255469,6.8212702 4.4254288,7.4113576 4.9038188,7.8897476 5.4939062,7.8896295 5.646983,7.8892233 5.7981841,7.8559185 5.9372656,7.7919733 l 0.4628906,0.5351562 c -0.2593431,0.2844532 -0.4218723,0.6589599 -0.421875,1.0742188 1.1e-6,0.1550931 0.029186,0.301527 0.070312,0.4433594 L 5.2692969,10.19041 C 5.0668671,9.9352433 4.7590727,9.7863779 4.4333593,9.7861139 3.8432718,9.7859958 3.3648819,10.264386 3.365,10.854473 c -1.179e-4,0.590087 0.478272,1.068477 1.0683593,1.068359 0.5900874,1.18e-4 1.0684773,-0.478272 1.0683594,-1.068359 -2.425e-4,-0.05958 -0.00547,-0.119029 -0.015625,-0.177734 l 0.7675782,-0.376953 c 0.2881162,0.42403 0.7748778,0.703124 1.3261718,0.703124 0.087028,-9e-5 0.1739047,-0.0073 0.2597656,-0.02148 l 0.2011719,0.597656 c -0.2806104,0.199117 -0.4474678,0.523359 -0.4472656,0.869137 -8.57e-5,0.586839 0.4721644,1.062587 1.0546875,1.0625 0.5825231,8.7e-5 1.054773,-0.475661 1.054687,-1.0625 8.6e-5,-0.586839 -0.4721639,-1.062587 -1.054687,-1.0625 -0.043779,5.16e-4 -0.087483,0.0038 -0.1308594,0.0098 L 8.3220312,10.819317 C 8.6909643,10.625493 8.9698168,10.295494 9.099375,9.8993953 l 0.5449219,0.089844 h 0.00195 c 0.05025,0.5310507 0.4958731,0.9369327 1.0292971,0.9374997 0.571737,8.6e-5 1.035243,-0.46342 1.035156,-1.0351567 C 11.710786,9.3198482 11.247281,8.8563402 10.675544,8.8564264 10.264465,8.85697 9.8926723,9.100743 9.7282783,9.4775202 L 9.1814062,9.3798639 C 9.1740509,8.9410593 8.9869509,8.524497 8.6638281,8.2275202 L 9.3103125,7.2607233 c 0.1095989,0.036162 0.2244742,0.051906 0.3398437,0.048828 0.1376991,0.0043 0.2729851,-0.023148 0.3984378,-0.080078 0.126162,-0.045588 0.239468,-0.119827 0.330078,-0.21875 0.09823,-0.093286 0.176943,-0.2056351 0.230469,-0.3300781 0.05137,-0.1271794 0.07858,-0.2632358 0.08008,-0.4003907 -4.88e-4,-0.140498 -0.02772,-0.2797842 -0.08008,-0.4101562 C 10.551096,5.7482226 10.472932,5.6366542 10.378672,5.5400202 10.284463,5.44539 10.172179,5.369883 10.048594,5.3193171 9.9210683,5.2636605 9.7863933,5.2372858 9.6501562,5.2372858 Z m -0.00195,0.4746094 C 9.9659223,5.7112473 10.223947,5.9683972 10.224378,6.2861139 10.225028,6.6045936 9.9666863,6.8629356 9.6482062,6.8622858 9.3304864,6.8618548 9.0733369,6.6038302 9.0739843,6.2861139 9.0744163,5.9691601 9.3312493,5.7123255 9.6482031,5.7118952 Z m -4.1543,0.4941406 C 5.8337444,6.2059063 6.1092701,6.481432 6.1091406,6.8212702 6.1092701,7.1611084 5.8337444,7.4366342 5.4939062,7.4365045 5.1540681,7.436634 4.8785424,7.1611083 4.8786719,6.8212702 4.8785424,6.481432 5.154068,6.2059063 5.4939062,6.2060358 Z M 7.5817969,8.3700983 A 1.0403689,1.0403689 0 0 1 8.6228125,9.4111139 1.0403689,1.0403689 0 0 1 7.5817969,10.450176 1.0403689,1.0403689 0 0 1 6.5427343,9.4111139 1.0403689,1.0403689 0 0 1 7.5817969,8.3700983 Z m 3.0585941,0.9277344 h 0.002 c 0.01432,-5.13e-4 0.02865,-5.13e-4 0.04297,0 0.331066,2.151e-4 0.599395,0.2685422 0.59961,0.5996096 -2.16e-4,0.3310657 -0.268544,0.5993937 -0.59961,0.5996087 -0.331828,8.64e-4 -0.601347,-0.26778 -0.601562,-0.5996087 -7.66e-4,-0.3150021 0.242463,-0.5768467 0.556641,-0.5996096 z M 4.4216406,10.260723 c 0.3398381,-1.3e-4 0.6153637,0.275396 0.6152344,0.615234 1.299e-4,0.339838 -0.2753959,0.615365 -0.6152344,0.615235 -0.3398385,1.3e-4 -0.6153643,-0.275397 -0.6152344,-0.615235 -1.293e-4,-0.339838 0.2753963,-0.615364 0.6152344,-0.615234 z m 4.2382813,1.589844 c 0.3452152,-8.4e-5 0.6250885,0.272792 0.625,0.609375 8.81e-5,0.336583 -0.2797848,0.609459 -0.625,0.609375 -0.3452157,8.4e-5 -0.6250889,-0.272792 -0.625,-0.609375 -8.86e-5,-0.336583 0.2797844,-0.609459 0.625,-0.609375 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.9 KiB |
1
extensions/big-data-cluster/resources/light/add.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>add</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14,6v4H10v4H6V10H2V6H6V2h4V6Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M13,7V9H9v4H7V9H3V7H7V3H9V7Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 486 B |
@@ -0,0 +1 @@
|
||||
<svg id="Icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><defs><style>.cls-1{opacity:0;}.cls-2{fill:#f6f6f6;}.cls-3{fill:#424242;}.cls-4{fill:#f0eff1;}</style></defs><title>centralmanagement_server_16x</title><g id="canvas" class="cls-1"><rect class="cls-2" width="16" height="16"/></g><path id="outline" class="cls-2" d="M16,0H7V1H3V5H0V16H8V13h8ZM6,4H7V5H6Z"/><g id="iconBG"><path class="cls-3" d="M8,1V12h7V1Zm6,10H13V10h1Zm0-6H9V4h5Zm0-2H9V2h5Z"/><path class="cls-3" d="M1,6v9H7V6Zm5,8H5V13H6Zm0-4H2V9H6ZM6,8H2V7H6Z"/><rect class="cls-3" x="6" y="2" width="1" height="1"/><rect class="cls-3" x="4" y="4" width="1" height="1"/><rect class="cls-3" x="4" y="2" width="1" height="1"/></g><g id="iconFG"><path class="cls-4" d="M14,3H9V2h5Zm0,1H9V5h5Zm0,6H13v1h1Z"/><path class="cls-4" d="M6,8H2V7H6ZM6,9H2v1H6Zm0,4H5v1H6Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 869 B |
1
extensions/big-data-cluster/resources/light/folder.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{fill:#F6F6F6;} .icon-vs-fg{fill:#F0EFF1;} .icon-folder{fill:#DCB67A;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 2.5v10c0 .827-.673 1.5-1.5 1.5h-11.996c-.827 0-1.5-.673-1.5-1.5v-8c0-.827.673-1.5 1.5-1.5h2.886l1-2h8.11c.827 0 1.5.673 1.5 1.5z" id="outline"/><path class="icon-folder" d="M14.5 2h-7.492l-1 2h-3.504c-.277 0-.5.224-.5.5v8c0 .276.223.5.5.5h11.996c.275 0 .5-.224.5-.5v-10c0-.276-.225-.5-.5-.5zm-.496 2h-6.496l.5-1h5.996v1z" id="iconBg"/><path class="icon-vs-fg" d="M14 3v1h-6.5l.5-1h6z" id="iconFg"/></svg>
|
||||
|
After Width: | Height: | Size: 740 B |
1
extensions/big-data-cluster/resources/light/refresh.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M13.451 5.609l-.579-.939-1.068.812-.076.094c-.335.415-.927 1.341-1.124 2.876l-.021.165.033.163.071.345c0 1.654-1.346 3-3 3-.795 0-1.545-.311-2.107-.868-.563-.567-.873-1.317-.873-2.111 0-1.431 1.007-2.632 2.351-2.929v2.926s2.528-2.087 2.984-2.461h.012l3.061-2.582-4.919-4.1h-1.137v2.404c-3.429.318-6.121 3.211-6.121 6.721 0 1.809.707 3.508 1.986 4.782 1.277 1.282 2.976 1.988 4.784 1.988 3.722 0 6.75-3.028 6.75-6.75 0-1.245-.349-2.468-1.007-3.536z" fill="#F6F6F6"/><path d="M12.6 6.134l-.094.071c-.269.333-.746 1.096-.91 2.375.057.277.092.495.092.545 0 2.206-1.794 4-4 4-1.098 0-2.093-.445-2.817-1.164-.718-.724-1.163-1.718-1.163-2.815 0-2.206 1.794-4 4-4l.351.025v1.85s1.626-1.342 1.631-1.339l1.869-1.577-3.5-2.917v2.218l-.371-.03c-3.176 0-5.75 2.574-5.75 5.75 0 1.593.648 3.034 1.695 4.076 1.042 1.046 2.482 1.694 4.076 1.694 3.176 0 5.75-2.574 5.75-5.75-.001-1.106-.318-2.135-.859-3.012z" fill="#424242"/></svg>
|
||||
|
After Width: | Height: | Size: 986 B |
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
viewBox="0 0 16 16"
|
||||
data-name="Layer 1"
|
||||
id="Layer_1">
|
||||
<metadata
|
||||
id="metadata17">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>sql_bigdata_cluster</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs4">
|
||||
<style
|
||||
id="style2">.cls-1{fill:#212121;}.cls-2{fill:#231f20;}</style>
|
||||
</defs>
|
||||
<title
|
||||
id="title6">sql_bigdata_cluster</title>
|
||||
<path
|
||||
style="fill:#212121;stroke-width:1.00282443"
|
||||
id="path8"
|
||||
d="M 7.995,0 C 5.605,0 1.575,0.45254557 1.465,2.1319925 V 13.737272 C 1.465,15.517285 5.575,16 7.995,16 c 2.42,0 6.54,-0.482715 6.54,-2.262728 V 2.1319925 C 14.435,0.45254557 10.405,0 7.995,0 Z m 5.45,13.737272 c -0.14,0.392206 -2.18,1.166562 -5.45,1.166562 -3.27,0 -5.32,-0.784412 -5.43,-1.166562 V 3.5097423 a 14.67,14.752986 0 0 0 5.43,0.8749214 14.71,14.793212 0 0 0 5.45,-0.8749214 z m 0,-11.5549967 c -0.17,0.3922062 -2.19,1.1062225 -5.45,1.1062225 -3.26,0 -5.2,-0.6939032 -5.43,-1.0861094 0.23,-0.4022627 2.22,-1.1062225 5.43,-1.1062225 3.21,0 5.27,0.7240729 5.45,1.0659963 v 0 z"
|
||||
class="cls-1" />
|
||||
<polygon
|
||||
style="fill:#231f20"
|
||||
transform="translate(0.075)"
|
||||
id="polygon10"
|
||||
points="13.57,2.35 13.58,2.36 13.57,2.37 "
|
||||
class="cls-2" />
|
||||
<path
|
||||
id="path12"
|
||||
d="m 9.6501562,5.2372858 c -0.1362374,0 -0.2728654,0.026375 -0.4003906,0.082031 -0.123585,0.050567 -0.2358691,0.1260731 -0.3300781,0.2207031 -0.094256,0.096634 -0.1724299,0.2082024 -0.2304688,0.3300781 -0.062701,0.1283175 -0.099426,0.2676857 -0.109375,0.4101562 -0.00186,0.1267925 0.022265,0.2517914 0.070312,0.3691407 0.045212,0.1164344 0.1088696,0.2248797 0.1894531,0.3203125 L 8.2107031,7.9384577 C 8.011051,7.8519995 7.7980699,7.8002026 7.5798437,7.7997858 7.2852043,7.7997877 7.0158159,7.8890317 6.7790625,8.0283014 L 6.3435156,7.4677545 C 6.4851678,7.2819801 6.5620085,7.0548883 6.5622656,6.8212702 6.5623837,6.2311827 6.0839937,5.7527927 5.4939062,5.7529108 4.9038187,5.7527927 4.4254288,6.2311827 4.4255469,6.8212702 4.4254288,7.4113576 4.9038188,7.8897476 5.4939062,7.8896295 5.646983,7.8892233 5.7981841,7.8559185 5.9372656,7.7919733 l 0.4628906,0.5351562 c -0.2593431,0.2844532 -0.4218723,0.6589599 -0.421875,1.0742188 1.1e-6,0.1550931 0.029186,0.301527 0.070312,0.4433594 L 5.2692969,10.19041 C 5.0668671,9.9352433 4.7590727,9.7863779 4.4333593,9.7861139 3.8432718,9.7859958 3.3648819,10.264386 3.365,10.854473 c -1.179e-4,0.590087 0.478272,1.068477 1.0683593,1.068359 0.5900874,1.18e-4 1.0684773,-0.478272 1.0683594,-1.068359 -2.425e-4,-0.05958 -0.00547,-0.119029 -0.015625,-0.177734 l 0.7675782,-0.376953 c 0.2881162,0.42403 0.7748778,0.703124 1.3261718,0.703124 0.087028,-9e-5 0.1739047,-0.0073 0.2597656,-0.02148 l 0.2011719,0.597656 c -0.2806104,0.199117 -0.4474678,0.523359 -0.4472656,0.869137 -8.57e-5,0.586839 0.4721644,1.062587 1.0546875,1.0625 0.5825231,8.7e-5 1.054773,-0.475661 1.054687,-1.0625 8.6e-5,-0.586839 -0.4721639,-1.062587 -1.054687,-1.0625 -0.043779,5.16e-4 -0.087483,0.0038 -0.1308594,0.0098 L 8.3220312,10.819317 C 8.6909643,10.625493 8.9698168,10.295494 9.099375,9.8993953 l 0.5449219,0.089844 h 0.00195 c 0.05025,0.5310507 0.4958731,0.9369327 1.0292971,0.9374997 0.571737,8.6e-5 1.035243,-0.46342 1.035156,-1.0351567 C 11.710786,9.3198482 11.247281,8.8563402 10.675544,8.8564264 10.264465,8.85697 9.8926723,9.100743 9.7282783,9.4775202 L 9.1814062,9.3798639 C 9.1740509,8.9410593 8.9869509,8.524497 8.6638281,8.2275202 L 9.3103125,7.2607233 c 0.1095989,0.036162 0.2244742,0.051906 0.3398437,0.048828 0.1376991,0.0043 0.2729851,-0.023148 0.3984378,-0.080078 0.126162,-0.045588 0.239468,-0.119827 0.330078,-0.21875 0.09823,-0.093286 0.176943,-0.2056351 0.230469,-0.3300781 0.05137,-0.1271794 0.07858,-0.2632358 0.08008,-0.4003907 -4.88e-4,-0.140498 -0.02772,-0.2797842 -0.08008,-0.4101562 C 10.551096,5.7482226 10.472932,5.6366542 10.378672,5.5400202 10.284463,5.44539 10.172179,5.369883 10.048594,5.3193171 9.9210683,5.2636605 9.7863933,5.2372858 9.6501562,5.2372858 Z m -0.00195,0.4746094 C 9.9659223,5.7112473 10.223947,5.9683972 10.224378,6.2861139 10.225028,6.6045936 9.9666863,6.8629356 9.6482062,6.8622858 9.3304864,6.8618548 9.0733369,6.6038302 9.0739843,6.2861139 9.0744163,5.9691601 9.3312493,5.7123255 9.6482031,5.7118952 Z m -4.1543,0.4941406 C 5.8337444,6.2059063 6.1092701,6.481432 6.1091406,6.8212702 6.1092701,7.1611084 5.8337444,7.4366342 5.4939062,7.4365045 5.1540681,7.436634 4.8785424,7.1611083 4.8786719,6.8212702 4.8785424,6.481432 5.154068,6.2059063 5.4939062,6.2060358 Z M 7.5817969,8.3700983 A 1.0403689,1.0403689 0 0 1 8.6228125,9.4111139 1.0403689,1.0403689 0 0 1 7.5817969,10.450176 1.0403689,1.0403689 0 0 1 6.5427343,9.4111139 1.0403689,1.0403689 0 0 1 7.5817969,8.3700983 Z m 3.0585941,0.9277344 h 0.002 c 0.01432,-5.13e-4 0.02865,-5.13e-4 0.04297,0 0.331066,2.151e-4 0.599395,0.2685422 0.59961,0.5996096 -2.16e-4,0.3310657 -0.268544,0.5993937 -0.59961,0.5996087 -0.331828,8.64e-4 -0.601347,-0.26778 -0.601562,-0.5996087 -7.66e-4,-0.3150021 0.242463,-0.5768467 0.556641,-0.5996096 z M 4.4216406,10.260723 c 0.3398381,-1.3e-4 0.6153637,0.275396 0.6152344,0.615234 1.299e-4,0.339838 -0.2753959,0.615365 -0.6152344,0.615235 -0.3398385,1.3e-4 -0.6153643,-0.275397 -0.6152344,-0.615235 -1.293e-4,-0.339838 0.2753963,-0.615364 0.6152344,-0.615234 z m 4.2382813,1.589844 c 0.3452152,-8.4e-5 0.6250885,0.272792 0.625,0.609375 8.81e-5,0.336583 -0.2797848,0.609459 -0.625,0.609375 -0.3452157,8.4e-5 -0.6250889,-0.272792 -0.625,-0.609375 -8.86e-5,-0.336583 0.2797844,-0.609459 0.625,-0.609375 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.9 KiB |
41
extensions/big-data-cluster/src/bigDataCluster/constants.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export enum BdcItemType {
|
||||
controllerRoot = 'bigDataClusters.itemType.controllerRootNode',
|
||||
controller = 'bigDataClusters.itemType.controllerNode',
|
||||
folder = 'bigDataClusters.itemType.folderNode',
|
||||
sqlMaster = 'bigDataClusters.itemType.sqlMasterNode',
|
||||
EndPoint = 'bigDataClusters.itemType.endPointNode',
|
||||
addController = 'bigDataClusters.itemType.addControllerNode'
|
||||
}
|
||||
|
||||
export class IconPath {
|
||||
private static extensionContext: vscode.ExtensionContext;
|
||||
|
||||
public static controllerNode: { dark: string, light: string };
|
||||
public static folderNode: { dark: string, light: string };
|
||||
public static sqlMasterNode: { dark: string, light: string };
|
||||
|
||||
public static setExtensionContext(extensionContext: vscode.ExtensionContext) {
|
||||
IconPath.extensionContext = extensionContext;
|
||||
IconPath.controllerNode = {
|
||||
dark: IconPath.extensionContext.asAbsolutePath('resources/dark/bigDataCluster_controller.svg'),
|
||||
light: IconPath.extensionContext.asAbsolutePath('resources/light/bigDataCluster_controller.svg')
|
||||
};
|
||||
IconPath.folderNode = {
|
||||
dark: IconPath.extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
|
||||
light: IconPath.extensionContext.asAbsolutePath('resources/light/folder.svg')
|
||||
};
|
||||
IconPath.sqlMasterNode = {
|
||||
dark: IconPath.extensionContext.asAbsolutePath('resources/dark/sql_bigdata_cluster_inverse.svg'),
|
||||
light: IconPath.extensionContext.asAbsolutePath('resources/light/sql_bigdata_cluster.svg')
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EndpointRouterApi } from './apiGenerated';
|
||||
|
||||
export async function getEndPoints(
|
||||
url: string, username: string, password: string, ignoreSslVerification?: boolean
|
||||
): Promise<IEndPointsResponse> {
|
||||
|
||||
if (!url || !username || !password) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
url = adjustUrl(url);
|
||||
let endPointApi = new EndpointRouterApi(username, password, url);
|
||||
endPointApi.ignoreSslVerification = !!ignoreSslVerification;
|
||||
|
||||
let controllerResponse: IEndPointsResponse = undefined;
|
||||
let controllerError: IControllerError = undefined;
|
||||
let request = <IEndPointsRequest>{
|
||||
url: url,
|
||||
username: username,
|
||||
password: password,
|
||||
method: 'endPointsGet'
|
||||
};
|
||||
|
||||
try {
|
||||
let result = await endPointApi.endpointsGet();
|
||||
controllerResponse = <IEndPointsResponse>{
|
||||
response: result.response as IHttpResponse,
|
||||
endPoints: result.body as IEndPoint[],
|
||||
request
|
||||
};
|
||||
return controllerResponse;
|
||||
} catch (error) {
|
||||
if ('response' in error) {
|
||||
let err: IEndPointsResponse = error as IEndPointsResponse;
|
||||
let errCode = `${err.response.statusCode || ''}`;
|
||||
let errMessage = err.response.statusMessage;
|
||||
let errUrl = err.response.url;
|
||||
controllerError = <IControllerError>{
|
||||
address: errUrl,
|
||||
code: errCode,
|
||||
errno: errCode,
|
||||
message: errMessage,
|
||||
name: undefined
|
||||
};
|
||||
} else {
|
||||
controllerError = error as IControllerError;
|
||||
}
|
||||
throw Object.assign(controllerError, { request }) as IControllerError;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixes missing protocol and wrong character for port entered by user
|
||||
*/
|
||||
function adjustUrl(url: string): string {
|
||||
if (!url) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
url = url.trim().replace(/ /g, '').replace(/,(\d+)$/, ':$1');
|
||||
if (!url.includes('://')) {
|
||||
url = `https://${url}`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
export interface IEndPointsRequest {
|
||||
url: string;
|
||||
username: string;
|
||||
password?: string;
|
||||
method?: string;
|
||||
}
|
||||
|
||||
export interface IEndPointsResponse {
|
||||
request?: IEndPointsRequest;
|
||||
response: IHttpResponse;
|
||||
endPoints: IEndPoint[];
|
||||
}
|
||||
|
||||
export interface IHttpResponse {
|
||||
method?: string;
|
||||
url?: string;
|
||||
statusCode?: number;
|
||||
statusMessage?: string;
|
||||
}
|
||||
|
||||
export interface IEndPoint {
|
||||
name?: string;
|
||||
description?: string;
|
||||
endpoint?: string;
|
||||
ip?: string;
|
||||
port?: number;
|
||||
}
|
||||
|
||||
export interface IControllerError extends Error {
|
||||
code?: string;
|
||||
errno?: string;
|
||||
message: string;
|
||||
request?: any;
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { IEndPoint, IControllerError, getEndPoints } from '../controller/clusterControllerApi';
|
||||
import { ControllerTreeDataProvider } from '../tree/controllerTreeDataProvider';
|
||||
import { TreeNode } from '../tree/treeNode';
|
||||
import { showErrorMessage } from '../utils';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class AddControllerDialogModel {
|
||||
constructor(
|
||||
public treeDataProvider: ControllerTreeDataProvider,
|
||||
public node?: TreeNode,
|
||||
public prefilledUrl?: string,
|
||||
public prefilledUsername?: string,
|
||||
public prefilledPassword?: string,
|
||||
public prefilledRememberPassword?: boolean
|
||||
) {
|
||||
this.prefilledUrl = prefilledUrl || (node && node['url']);
|
||||
this.prefilledUsername = prefilledUsername || (node && node['username']);
|
||||
this.prefilledPassword = prefilledPassword || (node && node['password']);
|
||||
this.prefilledRememberPassword = prefilledRememberPassword || (node && node['rememberPassword']);
|
||||
}
|
||||
|
||||
public async onComplete(url: string, username: string, password: string, rememberPassword: boolean): Promise<void> {
|
||||
let response = await getEndPoints(url, username, password, true);
|
||||
if (response && response.request) {
|
||||
let masterInstance: IEndPoint = undefined;
|
||||
if (response.endPoints) {
|
||||
masterInstance = response.endPoints.find(e => e.name && e.name === 'sql-server-master');
|
||||
}
|
||||
this.treeDataProvider.addController(response.request.url, response.request.username,
|
||||
response.request.password, rememberPassword, masterInstance);
|
||||
await this.treeDataProvider.saveControllers();
|
||||
}
|
||||
}
|
||||
|
||||
public async onError(error: IControllerError): Promise<void> {
|
||||
// implement
|
||||
}
|
||||
|
||||
public async onCancel(): Promise<void> {
|
||||
if (this.node) {
|
||||
this.node.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class AddControllerDialog {
|
||||
|
||||
private dialog: azdata.window.Dialog;
|
||||
private uiModelBuilder: azdata.ModelBuilder;
|
||||
|
||||
private urlInputBox: azdata.InputBoxComponent;
|
||||
private usernameInputBox: azdata.InputBoxComponent;
|
||||
private passwordInputBox: azdata.InputBoxComponent;
|
||||
private rememberPwCheckBox: azdata.CheckBoxComponent;
|
||||
|
||||
constructor(private model: AddControllerDialogModel) {
|
||||
}
|
||||
|
||||
public showDialog(): void {
|
||||
this.createDialog();
|
||||
azdata.window.openDialog(this.dialog);
|
||||
}
|
||||
|
||||
private createDialog(): void {
|
||||
this.dialog = azdata.window.createModelViewDialog(localize('textAddNewController', 'Add New Controller'));
|
||||
this.dialog.registerContent(async view => {
|
||||
this.uiModelBuilder = view.modelBuilder;
|
||||
|
||||
this.urlInputBox = this.uiModelBuilder.inputBox()
|
||||
.withProperties<azdata.InputBoxProperties>({
|
||||
placeHolder: localize('textUrlLower', 'url'),
|
||||
value: this.model.prefilledUrl
|
||||
}).component();
|
||||
this.usernameInputBox = this.uiModelBuilder.inputBox()
|
||||
.withProperties<azdata.InputBoxProperties>({
|
||||
placeHolder: localize('textUsernameLower', 'username'),
|
||||
value: this.model.prefilledUsername
|
||||
}).component();
|
||||
this.passwordInputBox = this.uiModelBuilder.inputBox()
|
||||
.withProperties<azdata.InputBoxProperties>({
|
||||
placeHolder: localize('textPasswordLower', 'password'),
|
||||
inputType: 'password',
|
||||
value: this.model.prefilledPassword
|
||||
})
|
||||
.component();
|
||||
this.rememberPwCheckBox = this.uiModelBuilder.checkBox()
|
||||
.withProperties<azdata.CheckBoxProperties>({
|
||||
label: localize('textRememberPassword', 'Remember Password'),
|
||||
checked: this.model.prefilledRememberPassword
|
||||
}).component();
|
||||
|
||||
let formModel = this.uiModelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
components: [{
|
||||
component: this.urlInputBox,
|
||||
title: localize('textUrlCapital', 'URL'),
|
||||
required: true
|
||||
}, {
|
||||
component: this.usernameInputBox,
|
||||
title: localize('textUsernameCapital', 'Username'),
|
||||
required: true
|
||||
}, {
|
||||
component: this.passwordInputBox,
|
||||
title: localize('textPasswordCapital', 'Password'),
|
||||
required: true
|
||||
}, {
|
||||
component: this.rememberPwCheckBox,
|
||||
title: ''
|
||||
}
|
||||
],
|
||||
title: ''
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
});
|
||||
|
||||
this.dialog.registerCloseValidator(async () => await this.validate());
|
||||
this.dialog.cancelButton.onClick(async () => await this.cancel());
|
||||
this.dialog.okButton.label = localize('textAdd', 'Add');
|
||||
this.dialog.cancelButton.label = localize('textCancel', 'Cancel');
|
||||
}
|
||||
|
||||
private async validate(): Promise<boolean> {
|
||||
let url = this.urlInputBox && this.urlInputBox.value;
|
||||
let username = this.usernameInputBox && this.usernameInputBox.value;
|
||||
let password = this.passwordInputBox && this.passwordInputBox.value;
|
||||
let rememberPassword = this.passwordInputBox && !!this.rememberPwCheckBox.checked;
|
||||
|
||||
try {
|
||||
await this.model.onComplete(url, username, password, rememberPassword);
|
||||
return true;
|
||||
} catch (error) {
|
||||
showErrorMessage(error);
|
||||
if (this.model && this.model.onError) {
|
||||
await this.model.onError(error as IControllerError);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async cancel(): Promise<void> {
|
||||
if (this.model && this.model.onCancel) {
|
||||
await this.model.onCancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { TreeNode } from './treeNode';
|
||||
import { BdcItemType } from '../constants';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class AddControllerNode extends TreeNode {
|
||||
private readonly nodeType: string;
|
||||
|
||||
constructor() {
|
||||
super(localize('textBigDataClusterControllerWithDots', 'Add Big Data Cluster Controller...'));
|
||||
this.nodeType = BdcItemType.addController;
|
||||
}
|
||||
|
||||
public async getChildren(): Promise<TreeNode[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
public getTreeItem(): vscode.TreeItem {
|
||||
let item = new vscode.TreeItem(this.label, vscode.TreeItemCollapsibleState.None);
|
||||
item.command = {
|
||||
title: localize('textConnectToController', 'Connect to Controller'),
|
||||
command: 'bigDataClusters.command.addController',
|
||||
arguments: [this]
|
||||
};
|
||||
item.contextValue = this.nodeType;
|
||||
return item;
|
||||
}
|
||||
|
||||
public getNodeInfo(): azdata.NodeInfo {
|
||||
return {
|
||||
label: this.label,
|
||||
isLeaf: this.isLeaf,
|
||||
errorMessage: undefined,
|
||||
metadata: undefined,
|
||||
nodePath: this.nodePath,
|
||||
nodeStatus: undefined,
|
||||
nodeType: this.nodeType,
|
||||
iconType: this.nodeType,
|
||||
nodeSubType: undefined
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { TreeNode } from './treeNode';
|
||||
|
||||
export interface IControllerTreeChangeHandler {
|
||||
notifyNodeChanged(node?: TreeNode): void;
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import { TreeNode } from './treeNode';
|
||||
import { IControllerTreeChangeHandler } from './controllerTreeChangeHandler';
|
||||
import { AddControllerNode } from './addControllerTreeNode';
|
||||
import { ControllerRootNode, ControllerNode } from './controllerTreeNode';
|
||||
import { IEndPoint } from '../controller/clusterControllerApi';
|
||||
import { showErrorMessage } from '../utils';
|
||||
|
||||
const ConfigNamespace = 'clusterControllers';
|
||||
const CredentialNamespace = 'clusterControllerCredentials';
|
||||
|
||||
export class ControllerTreeDataProvider implements vscode.TreeDataProvider<TreeNode>, IControllerTreeChangeHandler {
|
||||
|
||||
private _onDidChangeTreeData: vscode.EventEmitter<TreeNode> = new vscode.EventEmitter<TreeNode>();
|
||||
public readonly onDidChangeTreeData: vscode.Event<TreeNode> = this._onDidChangeTreeData.event;
|
||||
private root: ControllerRootNode;
|
||||
private credentialProvider: azdata.CredentialProvider;
|
||||
|
||||
constructor() {
|
||||
this.root = new ControllerRootNode(this);
|
||||
this.loadSavedControllers();
|
||||
}
|
||||
|
||||
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
|
||||
if (element) {
|
||||
return element.getChildren();
|
||||
}
|
||||
|
||||
if (this.root.hasChildren) {
|
||||
return this.root.getChildren();
|
||||
} else {
|
||||
return [new AddControllerNode()];
|
||||
}
|
||||
}
|
||||
|
||||
public getTreeItem(element: TreeNode): vscode.TreeItem | Thenable<vscode.TreeItem> {
|
||||
return element.getTreeItem();
|
||||
}
|
||||
|
||||
public addController(
|
||||
url: string,
|
||||
username: string,
|
||||
password: string,
|
||||
rememberPassword: boolean,
|
||||
masterInstance?: IEndPoint
|
||||
): void {
|
||||
this.root.addControllerNode(url, username, password, rememberPassword, masterInstance);
|
||||
this.notifyNodeChanged();
|
||||
}
|
||||
|
||||
public deleteController(url: string, username: string): ControllerNode {
|
||||
let deleted = this.root.deleteControllerNode(url, username);
|
||||
if (deleted) {
|
||||
this.notifyNodeChanged();
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
public notifyNodeChanged(node?: TreeNode): void {
|
||||
this._onDidChangeTreeData.fire(node);
|
||||
}
|
||||
|
||||
public async loadSavedControllers(): Promise<void> {
|
||||
let config = vscode.workspace.getConfiguration(ConfigNamespace);
|
||||
if (config && config.controllers) {
|
||||
let controllers = config.controllers;
|
||||
this.root.clearChildren();
|
||||
for (let c of controllers) {
|
||||
let password = undefined;
|
||||
if (c.rememberPassword) {
|
||||
password = await this.getPassword(c.url, c.username);
|
||||
}
|
||||
this.root.addChild(new ControllerNode(
|
||||
c.url, c.username, password, c.rememberPassword,
|
||||
undefined, this.root, this, undefined
|
||||
));
|
||||
}
|
||||
this.notifyNodeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public async saveControllers(): Promise<void> {
|
||||
let controllers = this.root.children.map(e => {
|
||||
let controller = e as ControllerNode;
|
||||
return {
|
||||
url: controller.url,
|
||||
username: controller.username,
|
||||
password: controller.password,
|
||||
rememberPassword: !!controller.rememberPassword
|
||||
};
|
||||
});
|
||||
|
||||
let controllersWithoutPassword = controllers.map(e => {
|
||||
return {
|
||||
url: e.url,
|
||||
username: e.username,
|
||||
rememberPassword: e.rememberPassword
|
||||
};
|
||||
});
|
||||
|
||||
try {
|
||||
await vscode.workspace.getConfiguration(ConfigNamespace).update('controllers', controllersWithoutPassword, true);
|
||||
} catch (error) {
|
||||
showErrorMessage(error);
|
||||
}
|
||||
|
||||
for (let e of controllers) {
|
||||
if (e.rememberPassword) {
|
||||
await this.savePassword(e.url, e.username, e.password);
|
||||
} else {
|
||||
await this.deletePassword(e.url, e.username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async savePassword(url: string, username: string, password: string): Promise<boolean> {
|
||||
let provider = await this.getCredentialProvider();
|
||||
let id = this.createId(url, username);
|
||||
let result = await provider.saveCredential(id, password);
|
||||
return result;
|
||||
}
|
||||
|
||||
private async deletePassword(url: string, username: string): Promise<boolean> {
|
||||
let provider = await this.getCredentialProvider();
|
||||
let id = this.createId(url, username);
|
||||
let result = await provider.deleteCredential(id);
|
||||
return result;
|
||||
}
|
||||
|
||||
private async getPassword(url: string, username: string): Promise<string> {
|
||||
let provider = await this.getCredentialProvider();
|
||||
let id = this.createId(url, username);
|
||||
let credential = await provider.readCredential(id);
|
||||
return credential ? credential.password : undefined;
|
||||
}
|
||||
|
||||
private async getCredentialProvider(): Promise<azdata.CredentialProvider> {
|
||||
if (!this.credentialProvider) {
|
||||
this.credentialProvider = await azdata.credentials.getProvider(CredentialNamespace);
|
||||
}
|
||||
return this.credentialProvider;
|
||||
}
|
||||
|
||||
private createId(url: string, username: string): string {
|
||||
return `${url}::${username}`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { IControllerTreeChangeHandler } from './controllerTreeChangeHandler';
|
||||
import { TreeNode } from './treeNode';
|
||||
import { IconPath, BdcItemType } from '../constants';
|
||||
import { IEndPoint, IControllerError, getEndPoints } from '../controller/clusterControllerApi';
|
||||
import { showErrorMessage } from '../utils';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export abstract class ControllerTreeNode extends TreeNode {
|
||||
|
||||
constructor(
|
||||
label: string,
|
||||
parent: ControllerTreeNode,
|
||||
private _treeChangeHandler: IControllerTreeChangeHandler,
|
||||
private _description?: string,
|
||||
private _nodeType?: string,
|
||||
private _iconPath?: { dark: string, light: string }
|
||||
) {
|
||||
super(label, parent);
|
||||
this._description = this._description || this.label;
|
||||
}
|
||||
|
||||
public async getChildren(): Promise<ControllerTreeNode[]> {
|
||||
return this.children as ControllerTreeNode[];
|
||||
}
|
||||
|
||||
public refresh(): void {
|
||||
super.refresh();
|
||||
this.treeChangeHandler.notifyNodeChanged(this);
|
||||
}
|
||||
|
||||
public getTreeItem(): vscode.TreeItem {
|
||||
let item: vscode.TreeItem = {};
|
||||
item.id = this.id;
|
||||
item.label = this.label;
|
||||
item.collapsibleState = this.isLeaf ?
|
||||
vscode.TreeItemCollapsibleState.None :
|
||||
vscode.TreeItemCollapsibleState.Collapsed;
|
||||
item.iconPath = this._iconPath;
|
||||
item.contextValue = this._nodeType;
|
||||
item.tooltip = this._description;
|
||||
item.iconPath = this._iconPath;
|
||||
return item;
|
||||
}
|
||||
|
||||
public getNodeInfo(): azdata.NodeInfo {
|
||||
return {
|
||||
label: this.label,
|
||||
isLeaf: this.isLeaf,
|
||||
errorMessage: undefined,
|
||||
metadata: undefined,
|
||||
nodePath: this.nodePath,
|
||||
nodeStatus: undefined,
|
||||
nodeType: this._nodeType,
|
||||
iconType: this._nodeType,
|
||||
nodeSubType: undefined
|
||||
};
|
||||
}
|
||||
|
||||
public get description(): string {
|
||||
return this._description;
|
||||
}
|
||||
|
||||
public set description(description: string) {
|
||||
this._description = description;
|
||||
}
|
||||
|
||||
public get nodeType(): string {
|
||||
return this._nodeType;
|
||||
}
|
||||
|
||||
public set nodeType(nodeType: string) {
|
||||
this._nodeType = nodeType;
|
||||
}
|
||||
|
||||
public set iconPath(iconPath: { dark: string, light: string }) {
|
||||
this._iconPath = iconPath;
|
||||
}
|
||||
|
||||
public get iconPath(): { dark: string, light: string } {
|
||||
return this._iconPath;
|
||||
}
|
||||
|
||||
public set treeChangeHandler(treeChangeHandler: IControllerTreeChangeHandler) {
|
||||
this._treeChangeHandler = treeChangeHandler;
|
||||
}
|
||||
|
||||
public get treeChangeHandler(): IControllerTreeChangeHandler {
|
||||
return this._treeChangeHandler;
|
||||
}
|
||||
}
|
||||
|
||||
export class ControllerRootNode extends ControllerTreeNode {
|
||||
|
||||
private _masterNodeFactory: SqlMasterNodeFactory;
|
||||
|
||||
constructor(treeChangeHandler: IControllerTreeChangeHandler) {
|
||||
super('root', undefined, treeChangeHandler, undefined, BdcItemType.controllerRoot);
|
||||
this._masterNodeFactory = new SqlMasterNodeFactory();
|
||||
}
|
||||
|
||||
public async getChildren(): Promise<ControllerNode[]> {
|
||||
return this.children as ControllerNode[];
|
||||
}
|
||||
|
||||
public addControllerNode(url: string, username: string, password: string, rememberPassword: boolean, masterInstance?: IEndPoint): void {
|
||||
let controllerNode = this.getExistingControllerNode(url, username);
|
||||
if (controllerNode) {
|
||||
controllerNode.password = password;
|
||||
controllerNode.rememberPassword = rememberPassword;
|
||||
controllerNode.clearChildren();
|
||||
} else {
|
||||
controllerNode = new ControllerNode(url, username, password, rememberPassword, undefined, this, this.treeChangeHandler, undefined);
|
||||
this.addChild(controllerNode);
|
||||
}
|
||||
|
||||
if (masterInstance) {
|
||||
controllerNode.addSqlMasterNode(masterInstance.endpoint, masterInstance.description);
|
||||
}
|
||||
}
|
||||
|
||||
public deleteControllerNode(url: string, username: string): ControllerNode {
|
||||
if (!url || !username) {
|
||||
return undefined;
|
||||
}
|
||||
let nodes = this.children as ControllerNode[];
|
||||
let index = nodes.findIndex(e => e.url === url && e.username === username);
|
||||
let deleted = undefined;
|
||||
if (index >= 0) {
|
||||
deleted = nodes.splice(index, 1);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
private getExistingControllerNode(url: string, username: string): ControllerNode {
|
||||
if (!url || !username) {
|
||||
return undefined;
|
||||
}
|
||||
let nodes = this.children as ControllerNode[];
|
||||
return nodes.find(e => e.url === url && e.username === username);
|
||||
}
|
||||
|
||||
public get sqlMasterNodeFactory(): SqlMasterNodeFactory {
|
||||
return this._masterNodeFactory;
|
||||
}
|
||||
}
|
||||
|
||||
export class ControllerNode extends ControllerTreeNode {
|
||||
|
||||
constructor(
|
||||
private _url: string,
|
||||
private _username: string,
|
||||
private _password: string,
|
||||
private _rememberPassword: boolean,
|
||||
label: string,
|
||||
parent: ControllerTreeNode,
|
||||
treeChangeHandler: IControllerTreeChangeHandler,
|
||||
description?: string,
|
||||
) {
|
||||
super(label, parent, treeChangeHandler, description, BdcItemType.controller, IconPath.controllerNode);
|
||||
this.label = label;
|
||||
this.description = description;
|
||||
|
||||
}
|
||||
|
||||
public async getChildren(): Promise<ControllerTreeNode[]> {
|
||||
if (this.children && this.children.length > 0) {
|
||||
this.clearChildren();
|
||||
}
|
||||
|
||||
if (!this._password) {
|
||||
vscode.commands.executeCommand('bigDataClusters.command.addController', this);
|
||||
return this.children as ControllerTreeNode[];
|
||||
}
|
||||
|
||||
try {
|
||||
let response = await getEndPoints(this._url, this._username, this._password, true);
|
||||
if (response && response.endPoints) {
|
||||
let master = response.endPoints.find(e => e.name && e.name === 'sql-server-master');
|
||||
this.addSqlMasterNode(master.endpoint, master.description);
|
||||
}
|
||||
return this.children as ControllerTreeNode[];
|
||||
} catch (error) {
|
||||
showErrorMessage(error);
|
||||
return this.children as ControllerTreeNode[];
|
||||
}
|
||||
}
|
||||
|
||||
private static toIpAndPort(url: string): string {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
return url.trim().replace(/ /g, '').replace(/^.+\:\/\//, '').replace(/:(\d+)$/, ',$1');
|
||||
}
|
||||
|
||||
public addSqlMasterNode(endPointAddress: string, description: string): void {
|
||||
let epFolder = this.getEndPointFolderNode();
|
||||
let node = (this.root as ControllerRootNode).sqlMasterNodeFactory
|
||||
.getSqlMasterNode(endPointAddress, epFolder, undefined, this.treeChangeHandler, description);
|
||||
epFolder.addChild(node);
|
||||
}
|
||||
|
||||
private getEndPointFolderNode(): FolderNode {
|
||||
let label = localize('textSqlServers', 'SQL Servers');
|
||||
let epFolderNode = this.children.find(e => e instanceof FolderNode && e.label === label);
|
||||
if (!epFolderNode) {
|
||||
epFolderNode = new FolderNode(label, this, this.treeChangeHandler);
|
||||
this.addChild(epFolderNode);
|
||||
}
|
||||
return epFolderNode as FolderNode;
|
||||
}
|
||||
|
||||
public getTreeItem(): vscode.TreeItem {
|
||||
let item: vscode.TreeItem = super.getTreeItem();
|
||||
item.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
|
||||
return item;
|
||||
}
|
||||
|
||||
public get url() {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
public set url(url: string) {
|
||||
this._url = url;
|
||||
}
|
||||
|
||||
public get username() {
|
||||
return this._username;
|
||||
}
|
||||
|
||||
public set username(username: string) {
|
||||
this._username = username;
|
||||
}
|
||||
|
||||
public get password() {
|
||||
return this._password;
|
||||
}
|
||||
|
||||
public set password(pw: string) {
|
||||
this._password = pw;
|
||||
}
|
||||
|
||||
public get rememberPassword() {
|
||||
return this._rememberPassword;
|
||||
}
|
||||
|
||||
public set rememberPassword(rememberPassword: boolean) {
|
||||
this._rememberPassword = rememberPassword;
|
||||
}
|
||||
|
||||
public set label(label: string) {
|
||||
super.label = label || `controller: ${ControllerNode.toIpAndPort(this._url)} (${this._username})`;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return super.label;
|
||||
}
|
||||
|
||||
public set description(description: string) {
|
||||
super.description = description || super.label;
|
||||
}
|
||||
|
||||
public get description(): string {
|
||||
return super.description;
|
||||
}
|
||||
}
|
||||
|
||||
export class FolderNode extends ControllerTreeNode {
|
||||
constructor(
|
||||
label: string,
|
||||
parent: ControllerTreeNode,
|
||||
treeChangeHandler: IControllerTreeChangeHandler
|
||||
) {
|
||||
super(label, parent, treeChangeHandler, label, BdcItemType.folder, IconPath.folderNode);
|
||||
}
|
||||
}
|
||||
|
||||
export class SqlMasterNode extends ControllerTreeNode {
|
||||
private static readonly _role: string = 'sql-server-master';
|
||||
private _username: string;
|
||||
private _password: string;
|
||||
|
||||
constructor(
|
||||
private _endPointAddress: string,
|
||||
parent: ControllerTreeNode,
|
||||
label: string,
|
||||
treeChangeHandler: IControllerTreeChangeHandler,
|
||||
description?: string,
|
||||
) {
|
||||
super(label, parent, treeChangeHandler, description, BdcItemType.sqlMaster, IconPath.sqlMasterNode);
|
||||
this._username = 'sa';
|
||||
this.label = label;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
private getControllerPassword(): string {
|
||||
if (!this._password) {
|
||||
let current: TreeNode = this;
|
||||
while (current && !(current instanceof ControllerNode)) {
|
||||
current = current.parent;
|
||||
}
|
||||
this._password = current && current instanceof ControllerNode ? current.password : undefined;
|
||||
}
|
||||
return this._password;
|
||||
}
|
||||
|
||||
public getTreeItem(): vscode.TreeItem {
|
||||
let item = super.getTreeItem();
|
||||
let connectionProfile: azdata.IConnectionProfile = {
|
||||
id: this.id,
|
||||
connectionName: this.id,
|
||||
serverName: this._endPointAddress,
|
||||
databaseName: '',
|
||||
userName: this._username,
|
||||
password: this.getControllerPassword(),
|
||||
authenticationType: 'SqlLogin',
|
||||
savePassword: false,
|
||||
groupFullName: '',
|
||||
groupId: '',
|
||||
providerName: 'MSSQL',
|
||||
saveProfile: false,
|
||||
options: {}
|
||||
};
|
||||
return Object.assign(item, { payload: connectionProfile, childProvider: 'MSSQL' });
|
||||
}
|
||||
|
||||
public get role() {
|
||||
return SqlMasterNode._role;
|
||||
}
|
||||
|
||||
public get endPointAddress() {
|
||||
return this._endPointAddress;
|
||||
}
|
||||
|
||||
public set endPointAddress(endPointAddress: string) {
|
||||
this._endPointAddress = endPointAddress;
|
||||
}
|
||||
|
||||
public set label(label: string) {
|
||||
super.label = label || `master: ${this._endPointAddress} (${this._username})`;
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return super.label;
|
||||
}
|
||||
|
||||
public set description(description: string) {
|
||||
super.description = description || super.label;
|
||||
}
|
||||
|
||||
public get description(): string {
|
||||
return super.description;
|
||||
}
|
||||
}
|
||||
|
||||
export class SqlMasterNodeFactory {
|
||||
private registry: {} = {};
|
||||
|
||||
public getSqlMasterNode(
|
||||
endPointAddress: string,
|
||||
parent: ControllerTreeNode,
|
||||
label: string,
|
||||
treeChangeHandler: IControllerTreeChangeHandler,
|
||||
description?: string
|
||||
): SqlMasterNode {
|
||||
let id = this.createRegistryId(endPointAddress, 'sa');
|
||||
if (!this.registry[id]) {
|
||||
this.registry[id] = new SqlMasterNode(endPointAddress, parent, label, treeChangeHandler, description);
|
||||
} else {
|
||||
let node = this.registry[id] as SqlMasterNode;
|
||||
node.parent = parent;
|
||||
node.label = label;
|
||||
node.treeChangeHandler = treeChangeHandler;
|
||||
description = description;
|
||||
}
|
||||
return this.registry[id] as SqlMasterNode;
|
||||
}
|
||||
|
||||
private createRegistryId(endPointAddress: string, username: string): string {
|
||||
return `${endPointAddress}::${username}`;
|
||||
}
|
||||
}
|
||||
195
extensions/big-data-cluster/src/bigDataCluster/tree/treeNode.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { generateGuid } from '../utils';
|
||||
|
||||
export abstract class TreeNode {
|
||||
|
||||
private _id: string;
|
||||
private _children: TreeNode[];
|
||||
private _isLeaf: boolean;
|
||||
|
||||
constructor(private _label: string, private _parent?: TreeNode) {
|
||||
this.resetId();
|
||||
}
|
||||
|
||||
public resetId(): void {
|
||||
this._id = (this._label || '_') + `::${generateGuid()}`;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public set label(label: string) {
|
||||
if (!this._label) {
|
||||
this._label = label;
|
||||
this.resetId();
|
||||
} else {
|
||||
this._label = label;
|
||||
}
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this._label;
|
||||
}
|
||||
|
||||
public set parent(parent: TreeNode) {
|
||||
this._parent = parent;
|
||||
}
|
||||
|
||||
public get parent(): TreeNode {
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
public get children(): TreeNode[] {
|
||||
if (!this._children) {
|
||||
this._children = [];
|
||||
}
|
||||
return this._children;
|
||||
}
|
||||
|
||||
public get hasChildren(): boolean {
|
||||
return this.children && this.children.length > 0;
|
||||
}
|
||||
|
||||
public set isLeaf(isLeaf: boolean) {
|
||||
this._isLeaf = isLeaf;
|
||||
}
|
||||
|
||||
public get isLeaf(): boolean {
|
||||
return this._isLeaf;
|
||||
}
|
||||
|
||||
public get root(): TreeNode {
|
||||
return TreeNode.getRoot(this);
|
||||
}
|
||||
|
||||
public equals(node: TreeNode): boolean {
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
return this.nodePath === node.nodePath;
|
||||
}
|
||||
|
||||
public refresh(): void {
|
||||
this.resetId();
|
||||
}
|
||||
|
||||
public static getRoot(node: TreeNode): TreeNode {
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
let current: TreeNode = node;
|
||||
while (current.parent) {
|
||||
current = current.parent;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
public get nodePath(): string {
|
||||
return TreeNode.getNodePath(this);
|
||||
}
|
||||
|
||||
public static getNodePath(node: TreeNode): string {
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let current: TreeNode = node;
|
||||
let path = current._id;
|
||||
while (current.parent) {
|
||||
current = current.parent;
|
||||
path = `${current._id}/${path}`;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public async findNode(condition: (node: TreeNode) => boolean, expandIfNeeded?: boolean): Promise<TreeNode> {
|
||||
return TreeNode.findNode(this, condition, expandIfNeeded);
|
||||
}
|
||||
|
||||
public static async findNode(node: TreeNode, condition: (node: TreeNode) => boolean, expandIfNeeded?: boolean): Promise<TreeNode> {
|
||||
if (!node || !condition) {
|
||||
return undefined;
|
||||
}
|
||||
let result: TreeNode = undefined;
|
||||
let nodesToCheck: TreeNode[] = [node];
|
||||
while (nodesToCheck.length > 0) {
|
||||
let current = nodesToCheck.shift();
|
||||
if (condition(current)) {
|
||||
result = current;
|
||||
break;
|
||||
}
|
||||
if (current.hasChildren) {
|
||||
nodesToCheck = nodesToCheck.concat(current.children);
|
||||
} else if (expandIfNeeded) {
|
||||
let children = await current.getChildren();
|
||||
if (children && children.length > 0) {
|
||||
nodesToCheck = nodesToCheck.concat(children);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async filterNode(condition: (node: TreeNode) => boolean, expandIfNeeded?: boolean): Promise<TreeNode[]> {
|
||||
return TreeNode.filterNode(this, condition, expandIfNeeded);
|
||||
}
|
||||
|
||||
public static async filterNode(node: TreeNode, condition: (node: TreeNode) => boolean, expandIfNeeded?: boolean): Promise<TreeNode[]> {
|
||||
if (!node || !condition) {
|
||||
return undefined;
|
||||
}
|
||||
let result: TreeNode[] = [];
|
||||
let nodesToCheck: TreeNode[] = [node];
|
||||
while (nodesToCheck.length > 0) {
|
||||
let current = nodesToCheck.shift();
|
||||
if (condition(current)) {
|
||||
result.push(current);
|
||||
}
|
||||
if (current.hasChildren) {
|
||||
nodesToCheck = nodesToCheck.concat(current.children);
|
||||
} else if (expandIfNeeded) {
|
||||
let children = await current.getChildren();
|
||||
if (children && children.length > 0) {
|
||||
nodesToCheck = nodesToCheck.concat(children);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async findNodeByPath(path: string, expandIfNeeded?: boolean): Promise<TreeNode> {
|
||||
return TreeNode.findNodeByPath(this, path, expandIfNeeded);
|
||||
}
|
||||
|
||||
public static async findNodeByPath(node: TreeNode, path: string, expandIfNeeded?: boolean): Promise<TreeNode> {
|
||||
return TreeNode.findNode(node, node => {
|
||||
return node.nodePath && (node.nodePath === path || node.nodePath.startsWith(path));
|
||||
}, expandIfNeeded);
|
||||
}
|
||||
|
||||
public addChild(node: TreeNode): void {
|
||||
if (!this._children) {
|
||||
this._children = [];
|
||||
}
|
||||
this._children.push(node);
|
||||
}
|
||||
|
||||
public clearChildren(): void {
|
||||
if (this._children) {
|
||||
this._children = [];
|
||||
}
|
||||
}
|
||||
|
||||
public abstract async getChildren(): Promise<TreeNode[]>;
|
||||
public abstract getTreeItem(): vscode.TreeItem;
|
||||
public abstract getNodeInfo(): azdata.NodeInfo;
|
||||
}
|
||||
43
extensions/big-data-cluster/src/bigDataCluster/utils.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function generateGuid(): string {
|
||||
let hexValues: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
|
||||
let oct: string = '';
|
||||
let tmp: number;
|
||||
for (let a: number = 0; a < 4; a++) {
|
||||
tmp = (4294967296 * Math.random()) | 0;
|
||||
oct += hexValues[tmp & 0xF] +
|
||||
hexValues[tmp >> 4 & 0xF] +
|
||||
hexValues[tmp >> 8 & 0xF] +
|
||||
hexValues[tmp >> 12 & 0xF] +
|
||||
hexValues[tmp >> 16 & 0xF] +
|
||||
hexValues[tmp >> 20 & 0xF] +
|
||||
hexValues[tmp >> 24 & 0xF] +
|
||||
hexValues[tmp >> 28 & 0xF];
|
||||
}
|
||||
let clockSequenceHi: string = hexValues[8 + (Math.random() * 4) | 0];
|
||||
return oct.substr(0, 8) + '-' + oct.substr(9, 4) + '-4' + oct.substr(13, 3) + '-' + clockSequenceHi + oct.substr(16, 3) + '-' + oct.substr(19, 12);
|
||||
}
|
||||
|
||||
export function showErrorMessage(error: any): void {
|
||||
if (error) {
|
||||
let text: string = undefined;
|
||||
if (typeof error === 'string') {
|
||||
text = error as string;
|
||||
} else if (typeof error === 'object' && error !== null) {
|
||||
let message = error.message;
|
||||
let code = error.code || error.errno;
|
||||
text = (code ? `${code} ` : '') + message;
|
||||
} else {
|
||||
text = `${error}`;
|
||||
}
|
||||
vscode.window.showErrorMessage(text);
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { Host } from '../kubectl/host';
|
||||
import { Shell, Platform } from '../utility/shell';
|
||||
|
||||
const EXTENSION_CONFIG_KEY = 'mssql-bdc';
|
||||
const KUBECONFIG_PATH_KEY = 'mssql-bdc.kubeconfig';
|
||||
const KNOWN_KUBECONFIGS_KEY = 'mssql-bdc.knownKubeconfigs';
|
||||
|
||||
export async function addPathToConfig(configKey: string, value: string): Promise<void> {
|
||||
await setConfigValue(configKey, value);
|
||||
}
|
||||
|
||||
async function setConfigValue(configKey: string, value: any): Promise<void> {
|
||||
await atAllConfigScopes(addValueToConfigAtScope, configKey, value);
|
||||
}
|
||||
|
||||
async function addValueToConfigAtScope(configKey: string, value: any, scope: vscode.ConfigurationTarget, valueAtScope: any, createIfNotExist: boolean): Promise<void> {
|
||||
if (!createIfNotExist) {
|
||||
if (!valueAtScope || !(valueAtScope[configKey])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let newValue: any = {};
|
||||
if (valueAtScope) {
|
||||
newValue = Object.assign({}, valueAtScope);
|
||||
}
|
||||
newValue[configKey] = value;
|
||||
await vscode.workspace.getConfiguration().update(EXTENSION_CONFIG_KEY, newValue, scope);
|
||||
}
|
||||
|
||||
async function addValueToConfigArray(configKey: string, value: string): Promise<void> {
|
||||
await atAllConfigScopes(addValueToConfigArrayAtScope, configKey, value);
|
||||
}
|
||||
|
||||
async function addValueToConfigArrayAtScope(configKey: string, value: string, scope: vscode.ConfigurationTarget, valueAtScope: any, createIfNotExist: boolean): Promise<void> {
|
||||
if (!createIfNotExist) {
|
||||
if (!valueAtScope || !(valueAtScope[configKey])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let newValue: any = {};
|
||||
if (valueAtScope) {
|
||||
newValue = Object.assign({}, valueAtScope);
|
||||
}
|
||||
const arrayEntry: string[] = newValue[configKey] || [];
|
||||
arrayEntry.push(value);
|
||||
newValue[configKey] = arrayEntry;
|
||||
await vscode.workspace.getConfiguration().update(EXTENSION_CONFIG_KEY, newValue, scope);
|
||||
}
|
||||
|
||||
type ConfigUpdater<T> = (configKey: string, value: T, scope: vscode.ConfigurationTarget, valueAtScope: any, createIfNotExist: boolean) => Promise<void>;
|
||||
|
||||
async function atAllConfigScopes<T>(fn: ConfigUpdater<T>, configKey: string, value: T): Promise<void> {
|
||||
const config = vscode.workspace.getConfiguration().inspect(EXTENSION_CONFIG_KEY)!;
|
||||
await fn(configKey, value, vscode.ConfigurationTarget.Global, config.globalValue, true);
|
||||
await fn(configKey, value, vscode.ConfigurationTarget.Workspace, config.workspaceValue, false);
|
||||
await fn(configKey, value, vscode.ConfigurationTarget.WorkspaceFolder, config.workspaceFolderValue, false);
|
||||
}
|
||||
|
||||
// Functions for working with the list of known kubeconfigs
|
||||
|
||||
export function getKnownKubeconfigs(): string[] {
|
||||
const kkcConfig = vscode.workspace.getConfiguration(EXTENSION_CONFIG_KEY)[KNOWN_KUBECONFIGS_KEY];
|
||||
if (!kkcConfig || !kkcConfig.length) {
|
||||
return [];
|
||||
}
|
||||
return kkcConfig as string[];
|
||||
}
|
||||
|
||||
export async function addKnownKubeconfig(kubeconfigPath: string) {
|
||||
await addValueToConfigArray(KNOWN_KUBECONFIGS_KEY, kubeconfigPath);
|
||||
}
|
||||
|
||||
// Functions for working with the active kubeconfig setting
|
||||
|
||||
export async function setActiveKubeconfig(kubeconfig: string): Promise<void> {
|
||||
await addPathToConfig(KUBECONFIG_PATH_KEY, kubeconfig);
|
||||
}
|
||||
|
||||
export function getActiveKubeconfig(): string {
|
||||
return vscode.workspace.getConfiguration(EXTENSION_CONFIG_KEY)[KUBECONFIG_PATH_KEY];
|
||||
}
|
||||
|
||||
// Functions for working with tool paths
|
||||
|
||||
export function getToolPath(host: Host, shell: Shell, tool: string): string | undefined {
|
||||
const baseKey = toolPathBaseKey(tool);
|
||||
return getPathSetting(host, shell, baseKey);
|
||||
}
|
||||
|
||||
function getPathSetting(host: Host, shell: Shell, baseKey: string): string | undefined {
|
||||
const os = shell.platform();
|
||||
const osOverridePath = host.getConfiguration(EXTENSION_CONFIG_KEY)[osOverrideKey(os, baseKey)];
|
||||
return osOverridePath || host.getConfiguration(EXTENSION_CONFIG_KEY)[baseKey];
|
||||
}
|
||||
|
||||
export function toolPathBaseKey(tool: string): string {
|
||||
return `mssql-bdc.${tool}-path`;
|
||||
}
|
||||
|
||||
function osOverrideKey(os: Platform, baseKey: string): string {
|
||||
const osKey = osKeyString(os);
|
||||
return osKey ? `${baseKey}.${osKey}` : baseKey; // The 'else' clause should never happen so don't worry that this would result in double-checking a missing base key
|
||||
}
|
||||
|
||||
function osKeyString(os: Platform): string | null {
|
||||
switch (os) {
|
||||
case Platform.Windows: return 'windows';
|
||||
case Platform.MacOS: return 'mac';
|
||||
case Platform.Linux: return 'linux';
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { ClusterInfo } from '../interfaces';
|
||||
|
||||
export interface IKubeConfigParser {
|
||||
parse(configPath: string): ClusterInfo[];
|
||||
}
|
||||
|
||||
export class TestKubeConfigParser implements IKubeConfigParser {
|
||||
parse(configPath: string): ClusterInfo[] {
|
||||
let clusters = [];
|
||||
for (let i = 0; i < 18; i++) {
|
||||
let name;
|
||||
if (i % 2 === 0) {
|
||||
name = `kubernetes cluster ${i}`;
|
||||
}
|
||||
else {
|
||||
name = 'cluster dev ' + i;
|
||||
}
|
||||
clusters.push(
|
||||
{
|
||||
displayName: name,
|
||||
name: `kub-dev-xxxx-cluster-${i}`,
|
||||
user: 'root'
|
||||
}
|
||||
);
|
||||
}
|
||||
return clusters;
|
||||
}
|
||||
}
|
||||
|
||||
85
extensions/big-data-cluster/src/extension.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { ControllerTreeDataProvider } from './bigDataCluster/tree/controllerTreeDataProvider';
|
||||
import { IconPath } from './bigDataCluster/constants';
|
||||
import { TreeNode } from './bigDataCluster/tree/treeNode';
|
||||
import { AddControllerDialogModel, AddControllerDialog } from './bigDataCluster/dialog/addControllerDialog';
|
||||
import { ControllerNode } from './bigDataCluster/tree/controllerTreeNode';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export function activate(extensionContext: vscode.ExtensionContext) {
|
||||
IconPath.setExtensionContext(extensionContext);
|
||||
let treeDataProvider = new ControllerTreeDataProvider();
|
||||
|
||||
registerTreeDataProvider(treeDataProvider);
|
||||
registerCommands(treeDataProvider);
|
||||
}
|
||||
|
||||
export function deactivate() {
|
||||
}
|
||||
|
||||
function registerTreeDataProvider(treeDataProvider: ControllerTreeDataProvider): void {
|
||||
vscode.window.registerTreeDataProvider('sqlBigDataCluster', treeDataProvider);
|
||||
}
|
||||
|
||||
function registerCommands(treeDataProvider: ControllerTreeDataProvider): void {
|
||||
vscode.commands.registerCommand('bigDataClusters.command.addController', (node?: TreeNode) => {
|
||||
addBdcController(treeDataProvider, node);
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand('bigDataClusters.command.deleteController', (node: TreeNode) => {
|
||||
deleteBdcController(treeDataProvider, node);
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand('bigDataClusters.command.refreshController', (node: TreeNode) => {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
treeDataProvider.notifyNodeChanged(node);
|
||||
});
|
||||
}
|
||||
|
||||
function addBdcController(treeDataProvider: ControllerTreeDataProvider, node?: TreeNode): void {
|
||||
let model = new AddControllerDialogModel(treeDataProvider, node);
|
||||
let dialog = new AddControllerDialog(model);
|
||||
dialog.showDialog();
|
||||
}
|
||||
|
||||
async function deleteBdcController(treeDataProvider: ControllerTreeDataProvider, node: TreeNode): Promise<boolean> {
|
||||
if (!node && !(node instanceof ControllerNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let controllerNode = node as ControllerNode;
|
||||
|
||||
let choices: { [id: string]: boolean } = {};
|
||||
choices[localize('textYes', 'Yes')] = true;
|
||||
choices[localize('textNo', 'No')] = false;
|
||||
|
||||
let options = {
|
||||
ignoreFocusOut: false,
|
||||
placeHolder: localize('textConfirmDeleteController', 'Are you sure you want to delete \'{0}\'?', controllerNode.label)
|
||||
};
|
||||
|
||||
let result = await vscode.window.showQuickPick(Object.keys(choices), options);
|
||||
let remove: boolean = !!(result && choices[result]);
|
||||
if (remove) {
|
||||
deleteControllerInternal(treeDataProvider, controllerNode);
|
||||
}
|
||||
return remove;
|
||||
}
|
||||
|
||||
function deleteControllerInternal(treeDataProvider: ControllerTreeDataProvider, controllerNode: ControllerNode): void {
|
||||
let deleted = treeDataProvider.deleteController(controllerNode.url, controllerNode.username);
|
||||
if (deleted) {
|
||||
treeDataProvider.saveControllers();
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as stream from 'stream';
|
||||
import * as tmp from 'tmp';
|
||||
|
||||
import { succeeded, Errorable } from '../interfaces';
|
||||
|
||||
type DownloadFunc =
|
||||
(url: string, destination?: string, options?: any)
|
||||
=> Promise<Buffer> & stream.Duplex; // Stream has additional events - see https://www.npmjs.com/package/download
|
||||
|
||||
let download: DownloadFunc;
|
||||
|
||||
function ensureDownloadFunc() {
|
||||
if (!download) {
|
||||
const home = process.env['HOME'];
|
||||
download = require('download');
|
||||
if (home) {
|
||||
process.env['HOME'] = home;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function toTempFile(sourceUrl: string): Promise<Errorable<string>> {
|
||||
const tempFileObj = tmp.fileSync({ prefix: 'mssql-bdc-autoinstall-' });
|
||||
const downloadResult = await to(sourceUrl, tempFileObj.name);
|
||||
if (succeeded(downloadResult)) {
|
||||
return { succeeded: true, result: tempFileObj.name };
|
||||
}
|
||||
return { succeeded: false, error: downloadResult.error };
|
||||
}
|
||||
|
||||
export async function to(sourceUrl: string, destinationFile: string): Promise<Errorable<null>> {
|
||||
ensureDownloadFunc();
|
||||
try {
|
||||
await download(sourceUrl, path.dirname(destinationFile), { filename: path.basename(destinationFile) });
|
||||
return { succeeded: true, result: null };
|
||||
} catch (e) {
|
||||
return { succeeded: false, error: [e.message] };
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as download from './download';
|
||||
import * as fs from 'fs';
|
||||
import mkdirp = require('mkdirp');
|
||||
import * as path from 'path';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { Shell, Platform } from '../utility/shell';
|
||||
import { Errorable, failed } from '../interfaces';
|
||||
import { addPathToConfig, toolPathBaseKey } from '../config/config';
|
||||
|
||||
export async function installKubectl(shell: Shell): Promise<Errorable<null>> {
|
||||
const tool = 'kubectl';
|
||||
const binFile = (shell.isUnix()) ? 'kubectl' : 'kubectl.exe';
|
||||
const os = platformUrlString(shell.platform());
|
||||
|
||||
const version = await getStableKubectlVersion();
|
||||
if (failed(version)) {
|
||||
return { succeeded: false, error: version.error };
|
||||
}
|
||||
|
||||
const installFolder = getInstallFolder(shell, tool);
|
||||
mkdirp.sync(installFolder);
|
||||
|
||||
const kubectlUrl = `https://storage.googleapis.com/kubernetes-release/release/${version.result.trim()}/bin/${os}/amd64/${binFile}`;
|
||||
const downloadFile = path.join(installFolder, binFile);
|
||||
const downloadResult = await download.to(kubectlUrl, downloadFile);
|
||||
if (failed(downloadResult)) {
|
||||
return { succeeded: false, error: [localize('downloadKubectlFailed', 'Failed to download kubectl: {0}', downloadResult.error[0])] };
|
||||
}
|
||||
|
||||
if (shell.isUnix()) {
|
||||
fs.chmodSync(downloadFile, '0777');
|
||||
}
|
||||
|
||||
await addPathToConfig(toolPathBaseKey(tool), downloadFile);
|
||||
return { succeeded: true, result: null };
|
||||
}
|
||||
|
||||
async function getStableKubectlVersion(): Promise<Errorable<string>> {
|
||||
const downloadResult = await download.toTempFile('https://storage.googleapis.com/kubernetes-release/release/stable.txt');
|
||||
if (failed(downloadResult)) {
|
||||
return { succeeded: false, error: [localize('kubectlVersionCheckFailed', 'Failed to establish kubectl stable version: {0}', downloadResult.error[0])] };
|
||||
}
|
||||
const version = fs.readFileSync(downloadResult.result, 'utf-8');
|
||||
fs.unlinkSync(downloadResult.result);
|
||||
return { succeeded: true, result: version };
|
||||
}
|
||||
|
||||
export function getInstallFolder(shell: Shell, tool: string): string {
|
||||
return path.join(shell.home(), `.mssql-bdc/tools/${tool}`);
|
||||
}
|
||||
|
||||
function platformUrlString(platform: Platform, supported?: Platform[]): string | null {
|
||||
if (supported && supported.indexOf(platform) < 0) {
|
||||
return null;
|
||||
}
|
||||
switch (platform) {
|
||||
case Platform.Windows: return 'windows';
|
||||
case Platform.MacOS: return 'darwin';
|
||||
case Platform.Linux: return 'linux';
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export interface ClusterInfo {
|
||||
name: string;
|
||||
displayName: string;
|
||||
user: string;
|
||||
}
|
||||
|
||||
export enum TargetClusterType {
|
||||
ExistingKubernetesCluster,
|
||||
NewAksCluster
|
||||
}
|
||||
|
||||
export interface Succeeded<T> {
|
||||
readonly succeeded: true;
|
||||
readonly result: T;
|
||||
}
|
||||
|
||||
export interface Failed {
|
||||
readonly succeeded: false;
|
||||
readonly error: string[];
|
||||
}
|
||||
|
||||
export type Errorable<T> = Succeeded<T> | Failed;
|
||||
|
||||
export function succeeded<T>(e: Errorable<T>): e is Succeeded<T> {
|
||||
return e.succeeded;
|
||||
}
|
||||
|
||||
export function failed<T>(e: Errorable<T>): e is Failed {
|
||||
return !e.succeeded;
|
||||
}
|
||||
export interface ClusterPorts {
|
||||
sql: string;
|
||||
knox: string;
|
||||
controller: string;
|
||||
proxy: string;
|
||||
grafana: string;
|
||||
kibana: string;
|
||||
}
|
||||
|
||||
export interface ContainerRegistryInfo {
|
||||
registry: string;
|
||||
repository: string;
|
||||
imageTag: string;
|
||||
}
|
||||
|
||||
export interface TargetClusterTypeInfo {
|
||||
enabled: boolean;
|
||||
type: TargetClusterType;
|
||||
name: string;
|
||||
fullName: string;
|
||||
description: string;
|
||||
iconPath: {
|
||||
dark: string,
|
||||
light: string
|
||||
};
|
||||
}
|
||||
|
||||
export interface ToolInfo {
|
||||
name: string;
|
||||
description: string;
|
||||
version: string;
|
||||
status: ToolInstallationStatus;
|
||||
}
|
||||
|
||||
export enum ToolInstallationStatus {
|
||||
Installed,
|
||||
NotInstalled,
|
||||
Installing,
|
||||
FailedToInstall
|
||||
}
|
||||
|
||||
export enum ClusterType {
|
||||
Unknown = 0,
|
||||
AKS,
|
||||
Minikube,
|
||||
Kubernetes,
|
||||
Other
|
||||
}
|
||||
|
||||
export interface ClusterProfile {
|
||||
name: string;
|
||||
sqlServerMasterConfiguration: SQLServerMasterConfiguration;
|
||||
computePoolConfiguration: PoolConfiguration;
|
||||
dataPoolConfiguration: PoolConfiguration;
|
||||
storagePoolConfiguration: PoolConfiguration;
|
||||
sparkPoolConfiguration: PoolConfiguration;
|
||||
}
|
||||
|
||||
export interface PoolConfiguration {
|
||||
type: ClusterPoolType;
|
||||
scale: number;
|
||||
maxScale?: number;
|
||||
hardwareLabel?: string;
|
||||
}
|
||||
|
||||
export interface SQLServerMasterConfiguration extends PoolConfiguration {
|
||||
engineOnly: boolean;
|
||||
}
|
||||
|
||||
export enum ClusterPoolType {
|
||||
SQL,
|
||||
Compute,
|
||||
Data,
|
||||
Storage,
|
||||
Spark
|
||||
}
|
||||
|
||||
export interface ClusterResourceSummary {
|
||||
hardwareLabels: HardwareLabel[];
|
||||
}
|
||||
|
||||
export interface HardwareLabel {
|
||||
name: string;
|
||||
totalNodes: number;
|
||||
totalCores: number;
|
||||
totalMemoryInGB: number;
|
||||
totalDisks: number;
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { Shell } from '../utility/shell';
|
||||
import { Host } from './host';
|
||||
import { FS } from '../utility/fs';
|
||||
|
||||
export interface BinCheckContext {
|
||||
readonly host: Host;
|
||||
readonly fs: FS;
|
||||
readonly shell: Shell;
|
||||
readonly installDependenciesCallback: () => void;
|
||||
binFound: boolean;
|
||||
binPath: string;
|
||||
}
|
||||
|
||||
interface FindBinaryResult {
|
||||
err: number | null;
|
||||
output: string;
|
||||
}
|
||||
|
||||
async function findBinary(shell: Shell, binName: string): Promise<FindBinaryResult> {
|
||||
let cmd = `which ${binName}`;
|
||||
|
||||
if (shell.isWindows()) {
|
||||
cmd = `where.exe ${binName}.exe`;
|
||||
}
|
||||
|
||||
const opts = {
|
||||
async: true,
|
||||
env: {
|
||||
HOME: process.env.HOME,
|
||||
PATH: process.env.PATH
|
||||
}
|
||||
};
|
||||
|
||||
const execResult = await shell.execCore(cmd, opts);
|
||||
if (execResult.code) {
|
||||
return { err: execResult.code, output: execResult.stderr };
|
||||
}
|
||||
|
||||
return { err: null, output: execResult.stdout };
|
||||
}
|
||||
|
||||
export function execPath(shell: Shell, basePath: string): string {
|
||||
let bin = basePath;
|
||||
if (shell.isWindows() && bin && !(bin.endsWith('.exe'))) {
|
||||
bin = bin + '.exe';
|
||||
}
|
||||
return bin;
|
||||
}
|
||||
|
||||
type CheckPresentFailureReason = 'inferFailed' | 'configuredFileMissing';
|
||||
const installDependenciesAction = localize('installDependenciesAction', 'Install dependencies');
|
||||
const learnMoreAction = localize('learnMoreAction', 'Learn more');
|
||||
function alertNoBin(host: Host, binName: string, failureReason: CheckPresentFailureReason, message: string, installDependencies: () => void): void {
|
||||
switch (failureReason) {
|
||||
case 'inferFailed':
|
||||
host.showErrorMessage(message, installDependenciesAction, learnMoreAction).then(
|
||||
(str) => {
|
||||
switch (str) {
|
||||
case learnMoreAction:
|
||||
host.showInformationMessage(localize('moreInfoMsg', 'Add {0} directory to path, or set "mssql-bdc.{0}-path" config to {0} binary.', binName));
|
||||
break;
|
||||
case installDependenciesAction:
|
||||
installDependencies();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
break;
|
||||
case 'configuredFileMissing':
|
||||
host.showErrorMessage(message, installDependenciesAction).then(
|
||||
(str) => {
|
||||
if (str === installDependenciesAction) {
|
||||
installDependencies();
|
||||
}
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkForBinary(context: BinCheckContext, bin: string | undefined, binName: string, inferFailedMessage: string, configuredFileMissingMessage: string, alertOnFail: boolean): Promise<boolean> {
|
||||
if (!bin) {
|
||||
const fb = await findBinary(context.shell, binName);
|
||||
|
||||
if (fb.err || fb.output.length === 0) {
|
||||
if (alertOnFail) {
|
||||
alertNoBin(context.host, binName, 'inferFailed', inferFailedMessage, context.installDependenciesCallback);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
context.binFound = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (context.shell.isWindows) {
|
||||
context.binFound = context.fs.existsSync(bin);
|
||||
} else {
|
||||
const sr = await context.shell.exec(`ls ${bin}`);
|
||||
context.binFound = (!!sr && sr.code === 0);
|
||||
}
|
||||
if (context.binFound) {
|
||||
context.binPath = bin;
|
||||
} else {
|
||||
if (alertOnFail) {
|
||||
alertNoBin(context.host, binName, 'configuredFileMissing', configuredFileMissingMessage, context.installDependenciesCallback);
|
||||
}
|
||||
}
|
||||
|
||||
return context.binFound;
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Errorable, failed } from '../interfaces';
|
||||
|
||||
interface CompatibilityGuaranteed {
|
||||
readonly guaranteed: true;
|
||||
}
|
||||
|
||||
interface CompatibilityNotGuaranteed {
|
||||
readonly guaranteed: false;
|
||||
readonly didCheck: boolean;
|
||||
readonly clientVersion: string;
|
||||
readonly serverVersion: string;
|
||||
}
|
||||
|
||||
export type Compatibility = CompatibilityGuaranteed | CompatibilityNotGuaranteed;
|
||||
|
||||
export function isGuaranteedCompatible(c: Compatibility): c is CompatibilityGuaranteed {
|
||||
return c.guaranteed;
|
||||
}
|
||||
|
||||
export interface Version {
|
||||
readonly major: string;
|
||||
readonly minor: string;
|
||||
readonly gitVersion: string;
|
||||
}
|
||||
|
||||
export async function check(kubectlLoadJSON: (cmd: string) => Promise<Errorable<any>>): Promise<Compatibility> {
|
||||
const version = await kubectlLoadJSON('version -o json');
|
||||
if (failed(version)) {
|
||||
return {
|
||||
guaranteed: false,
|
||||
didCheck: false,
|
||||
clientVersion: '',
|
||||
serverVersion: ''
|
||||
};
|
||||
}
|
||||
|
||||
const clientVersion: Version = version.result.clientVersion;
|
||||
const serverVersion: Version = version.result.serverVersion;
|
||||
|
||||
if (isCompatible(clientVersion, serverVersion)) {
|
||||
return { guaranteed: true };
|
||||
}
|
||||
|
||||
return {
|
||||
guaranteed: false,
|
||||
didCheck: true,
|
||||
clientVersion: clientVersion.gitVersion,
|
||||
serverVersion: serverVersion.gitVersion
|
||||
};
|
||||
}
|
||||
|
||||
function isCompatible(clientVersion: Version, serverVersion: Version): boolean {
|
||||
if (clientVersion.major === serverVersion.major) {
|
||||
const clientMinor = Number.parseInt(clientVersion.minor);
|
||||
const serverMinor = Number.parseInt(serverVersion.minor);
|
||||
if (Number.isInteger(clientMinor) && Number.isInteger(serverMinor) && Math.abs(clientMinor - serverMinor) <= 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export interface Host {
|
||||
showErrorMessage(message: string, ...items: string[]): Thenable<string | undefined>;
|
||||
showWarningMessage(message: string, ...items: string[]): Thenable<string | undefined>;
|
||||
showInformationMessage(message: string, ...items: string[]): Thenable<string | undefined>;
|
||||
getConfiguration(key: string): any;
|
||||
onDidChangeConfiguration(listener: (ch: vscode.ConfigurationChangeEvent) => any): vscode.Disposable;
|
||||
}
|
||||
|
||||
export const host: Host = {
|
||||
showErrorMessage: showErrorMessage,
|
||||
showWarningMessage: showWarningMessage,
|
||||
showInformationMessage: showInformationMessage,
|
||||
getConfiguration: getConfiguration,
|
||||
onDidChangeConfiguration: onDidChangeConfiguration,
|
||||
};
|
||||
|
||||
function showErrorMessage(message: string, ...items: string[]): Thenable<string | undefined> {
|
||||
return vscode.window.showErrorMessage(message, ...items);
|
||||
}
|
||||
|
||||
function showWarningMessage(message: string, ...items: string[]): Thenable<string | undefined> {
|
||||
return vscode.window.showWarningMessage(message, ...items);
|
||||
}
|
||||
|
||||
function showInformationMessage(message: string, ...items: string[]): Thenable<string | undefined> {
|
||||
return vscode.window.showInformationMessage(message, ...items);
|
||||
}
|
||||
|
||||
function getConfiguration(key: string): any {
|
||||
return vscode.workspace.getConfiguration(key);
|
||||
}
|
||||
|
||||
function onDidChangeConfiguration(listener: (e: vscode.ConfigurationChangeEvent) => any): vscode.Disposable {
|
||||
return vscode.workspace.onDidChangeConfiguration(listener);
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { Host } from './host';
|
||||
import { FS } from '../utility/fs';
|
||||
import { Shell, ShellResult } from '../utility/shell';
|
||||
import * as binutil from './binutil';
|
||||
import { Errorable } from '../interfaces';
|
||||
import * as compatibility from './compatibility';
|
||||
import { getToolPath } from '../config/config';
|
||||
|
||||
export interface Kubectl {
|
||||
checkPresent(errorMessageMode: CheckPresentMessageMode): Promise<boolean>;
|
||||
asJson<T>(command: string): Promise<Errorable<T>>;
|
||||
invokeAsync(command: string, stdin?: string): Promise<ShellResult | undefined>;
|
||||
getContext(): Context;
|
||||
}
|
||||
|
||||
interface Context {
|
||||
readonly host: Host;
|
||||
readonly fs: FS;
|
||||
readonly shell: Shell;
|
||||
readonly installDependenciesCallback: () => void;
|
||||
binFound: boolean;
|
||||
binPath: string;
|
||||
}
|
||||
|
||||
class KubectlImpl implements Kubectl {
|
||||
constructor(host: Host, fs: FS, shell: Shell, installDependenciesCallback: () => void, kubectlFound: boolean) {
|
||||
this.context = { host: host, fs: fs, shell: shell, installDependenciesCallback: installDependenciesCallback, binFound: kubectlFound, binPath: 'kubectl' };
|
||||
}
|
||||
|
||||
readonly context: Context;
|
||||
|
||||
checkPresent(errorMessageMode: CheckPresentMessageMode): Promise<boolean> {
|
||||
return checkPresent(this.context, errorMessageMode);
|
||||
}
|
||||
asJson<T>(command: string): Promise<Errorable<T>> {
|
||||
return asJson(this.context, command);
|
||||
}
|
||||
invokeAsync(command: string, stdin?: string): Promise<ShellResult | undefined> {
|
||||
return invokeAsync(this.context, command, stdin);
|
||||
}
|
||||
|
||||
getContext(): Context {
|
||||
return this.context;
|
||||
}
|
||||
}
|
||||
|
||||
export function create(host: Host, fs: FS, shell: Shell, installDependenciesCallback: () => void): Kubectl {
|
||||
return new KubectlImpl(host, fs, shell, installDependenciesCallback, false);
|
||||
}
|
||||
|
||||
export enum CheckPresentMessageMode {
|
||||
Command,
|
||||
Activation,
|
||||
Silent,
|
||||
}
|
||||
|
||||
async function checkPresent(context: Context, errorMessageMode: CheckPresentMessageMode): Promise<boolean> {
|
||||
if (context.binFound) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return await checkForKubectlInternal(context, errorMessageMode);
|
||||
}
|
||||
|
||||
async function checkForKubectlInternal(context: Context, errorMessageMode: CheckPresentMessageMode): Promise<boolean> {
|
||||
const binName = 'kubectl';
|
||||
const bin = getToolPath(context.host, context.shell, binName);
|
||||
|
||||
const contextMessage = getCheckKubectlContextMessage(errorMessageMode);
|
||||
const inferFailedMessage = localize('binaryNotFound', 'Could not find {0} binary. {1}', binName, contextMessage);
|
||||
const configuredFileMissingMessage = localize('binaryNotInstalled', '{0} is not installed. {1}', bin, contextMessage);
|
||||
|
||||
return await binutil.checkForBinary(context, bin, binName, inferFailedMessage, configuredFileMissingMessage, errorMessageMode !== CheckPresentMessageMode.Silent);
|
||||
}
|
||||
|
||||
function getCheckKubectlContextMessage(errorMessageMode: CheckPresentMessageMode): string {
|
||||
if (errorMessageMode === CheckPresentMessageMode.Activation) {
|
||||
return localize('kubernetesRequired', ' SQL Server Big data cluster requires kubernetes.');
|
||||
} else if (errorMessageMode === CheckPresentMessageMode.Command) {
|
||||
return localize('cannotExecuteCmd', ' Cannot execute command.');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
async function invokeAsync(context: Context, command: string, stdin?: string): Promise<ShellResult | undefined> {
|
||||
if (await checkPresent(context, CheckPresentMessageMode.Command)) {
|
||||
const bin = baseKubectlPath(context);
|
||||
const cmd = `${bin} ${command}`;
|
||||
const sr = await context.shell.exec(cmd, stdin);
|
||||
if (sr && sr.code !== 0) {
|
||||
checkPossibleIncompatibility(context);
|
||||
}
|
||||
return sr;
|
||||
} else {
|
||||
return { code: -1, stdout: '', stderr: '' };
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: invalidate this when the context changes or if we know kubectl has changed (e.g. config)
|
||||
let checkedCompatibility = false; // We don't want to spam the user (or CPU!) repeatedly running the version check
|
||||
|
||||
async function checkPossibleIncompatibility(context: Context): Promise<void> {
|
||||
if (checkedCompatibility) {
|
||||
return;
|
||||
}
|
||||
checkedCompatibility = true;
|
||||
const compat = await compatibility.check((cmd) => asJson<compatibility.Version>(context, cmd));
|
||||
if (!compatibility.isGuaranteedCompatible(compat) && compat.didCheck) {
|
||||
const versionAlert = localize('kubectlVersionIncompatible', 'kubectl version ${0} may be incompatible with cluster Kubernetes version {1}', compat.clientVersion, compat.serverVersion);
|
||||
context.host.showWarningMessage(versionAlert);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function baseKubectlPath(context: Context): string {
|
||||
let bin = getToolPath(context.host, context.shell, 'kubectl');
|
||||
if (!bin) {
|
||||
bin = 'kubectl';
|
||||
}
|
||||
return bin;
|
||||
}
|
||||
|
||||
async function asJson<T>(context: Context, command: string): Promise<Errorable<T>> {
|
||||
const shellResult = await invokeAsync(context, command);
|
||||
if (!shellResult) {
|
||||
return { succeeded: false, error: [localize('cannotRunCommand', 'Unable to run command ({0})', command)] };
|
||||
}
|
||||
|
||||
if (shellResult.code === 0) {
|
||||
return { succeeded: true, result: JSON.parse(shellResult.stdout.trim()) as T };
|
||||
|
||||
}
|
||||
return { succeeded: false, error: [shellResult.stderr] };
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { Kubectl } from './kubectl';
|
||||
import { failed, ClusterType } from '../interfaces';
|
||||
|
||||
export interface KubectlContext {
|
||||
readonly clusterName: string;
|
||||
readonly contextName: string;
|
||||
readonly userName: string;
|
||||
readonly active: boolean;
|
||||
}
|
||||
|
||||
interface Kubeconfig {
|
||||
readonly apiVersion: string;
|
||||
readonly 'current-context': string;
|
||||
readonly clusters: {
|
||||
readonly name: string;
|
||||
readonly cluster: {
|
||||
readonly server: string;
|
||||
readonly 'certificate-authority'?: string;
|
||||
readonly 'certificate-authority-data'?: string;
|
||||
};
|
||||
}[] | undefined;
|
||||
readonly contexts: {
|
||||
readonly name: string;
|
||||
readonly context: {
|
||||
readonly cluster: string;
|
||||
readonly user: string;
|
||||
readonly namespace?: string;
|
||||
};
|
||||
}[] | undefined;
|
||||
readonly users: {
|
||||
readonly name: string;
|
||||
readonly user: {};
|
||||
}[] | undefined;
|
||||
}
|
||||
|
||||
export interface ClusterConfig {
|
||||
readonly server: string;
|
||||
readonly certificateAuthority: string | undefined;
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function getKubeconfig(kubectl: Kubectl): Promise<Kubeconfig | null> {
|
||||
const shellResult = await kubectl.asJson<any>('config view -o json');
|
||||
if (failed(shellResult)) {
|
||||
vscode.window.showErrorMessage(shellResult.error[0]);
|
||||
return null;
|
||||
}
|
||||
return shellResult.result;
|
||||
}
|
||||
|
||||
export async function getCurrentClusterConfig(kubectl: Kubectl): Promise<ClusterConfig | undefined> {
|
||||
const kubeConfig = await getKubeconfig(kubectl);
|
||||
if (!kubeConfig || !kubeConfig.clusters || !kubeConfig.contexts) {
|
||||
return undefined;
|
||||
}
|
||||
const contextConfig = kubeConfig.contexts.find((context) => context.name === kubeConfig['current-context'])!;
|
||||
const clusterConfig = kubeConfig.clusters.find((cluster) => cluster.name === contextConfig.context.cluster)!;
|
||||
return {
|
||||
server: clusterConfig.cluster.server,
|
||||
certificateAuthority: clusterConfig.cluster['certificate-authority']
|
||||
};
|
||||
}
|
||||
|
||||
export async function getContexts(kubectl: Kubectl): Promise<KubectlContext[]> {
|
||||
const kubectlConfig = await getKubeconfig(kubectl);
|
||||
if (!kubectlConfig) {
|
||||
return [];
|
||||
}
|
||||
const currentContext = kubectlConfig['current-context'];
|
||||
const contexts = kubectlConfig.contexts || [];
|
||||
return contexts.map((c) => {
|
||||
return {
|
||||
clusterName: c.context.cluster,
|
||||
contextName: c.name,
|
||||
userName: c.context.user,
|
||||
active: c.name === currentContext
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function setContext(kubectl: Kubectl, targetContext: string): Promise<void> {
|
||||
const shellResult = await kubectl.invokeAsync(`config use-context ${targetContext}`);
|
||||
if (!shellResult || shellResult.code !== 0) {
|
||||
// TODO: Update error handling for now.
|
||||
let errMsg = shellResult ? shellResult.stderr : localize('runKubectlFailed', 'Unable to run kubectl');
|
||||
vscode.window.showErrorMessage(localize('setClusterFailed', 'Failed to set \'{0}\' as current cluster: {1}', targetContext, errMsg));
|
||||
}
|
||||
}
|
||||
|
||||
export async function inferCurrentClusterType(kubectl: Kubectl): Promise<ClusterType> {
|
||||
let latestContextName = '';
|
||||
|
||||
const ctxsr = await kubectl.invokeAsync('config current-context');
|
||||
if (ctxsr && ctxsr.code === 0) {
|
||||
latestContextName = ctxsr.stdout.trim();
|
||||
} else {
|
||||
return ClusterType.Other;
|
||||
}
|
||||
|
||||
const cisr = await kubectl.invokeAsync('cluster-info');
|
||||
if (!cisr || cisr.code !== 0) {
|
||||
return ClusterType.Unknown;
|
||||
}
|
||||
const masterInfos = cisr.stdout.split('\n')
|
||||
.filter((s) => s.indexOf('master is running at') >= 0);
|
||||
|
||||
if (masterInfos.length === 0) {
|
||||
return ClusterType.Other;
|
||||
}
|
||||
|
||||
const masterInfo = masterInfos[0];
|
||||
if (masterInfo.indexOf('azmk8s.io') >= 0 || masterInfo.indexOf('azure.com') >= 0) {
|
||||
return ClusterType.AKS;
|
||||
}
|
||||
|
||||
if (latestContextName) {
|
||||
const gcsr = await kubectl.invokeAsync(`config get-contexts ${latestContextName}`);
|
||||
if (gcsr && gcsr.code === 0) {
|
||||
if (gcsr.stdout.indexOf('minikube') >= 0) {
|
||||
return ClusterType.Minikube;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ClusterType.Other;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export interface ISqlServerBigDataClusterChannel {
|
||||
showOutput(message: any, title?: string): void;
|
||||
}
|
||||
|
||||
const outputChannelName = localize('bigDataClusterOutputChannel', 'SQL Server big data cluster');
|
||||
class SqlServerBigDataCluster implements ISqlServerBigDataClusterChannel {
|
||||
private readonly channel: vscode.OutputChannel = vscode.window.createOutputChannel(outputChannelName);
|
||||
|
||||
showOutput(message: any, title?: string): void {
|
||||
if (title) {
|
||||
const simplifiedTime = (new Date()).toISOString().replace(/z|t/gi, ' ').trim(); // YYYY-MM-DD HH:mm:ss.sss
|
||||
const hightlightingTitle = `[${title} ${simplifiedTime}]`;
|
||||
this.channel.appendLine(hightlightingTitle);
|
||||
}
|
||||
this.channel.appendLine(message);
|
||||
this.channel.show();
|
||||
}
|
||||
}
|
||||
|
||||
export const sqlserverbigdataclusterchannel: ISqlServerBigDataClusterChannel = new SqlServerBigDataCluster();
|
||||
@@ -1,60 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import vscode = require('vscode');
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { MainController } from './mainController';
|
||||
|
||||
import { fs } from './utility/fs';
|
||||
|
||||
import { host } from './kubectl/host';
|
||||
import { sqlserverbigdataclusterchannel } from './kubectl/sqlServerBigDataClusterChannel';
|
||||
import { shell, Shell } from './utility/shell';
|
||||
import { CheckPresentMessageMode, create as kubectlCreate } from './kubectl/kubectl';
|
||||
import { installKubectl } from './installer/installer';
|
||||
import { Errorable, failed } from './interfaces';
|
||||
|
||||
const kubectl = kubectlCreate(host, fs, shell, installDependencies);
|
||||
export let controller: MainController;
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
controller = new MainController(context, kubectl);
|
||||
controller.activate();
|
||||
}
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
export function deactivate(): void {
|
||||
if (controller) {
|
||||
controller.deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
export async function installDependencies() {
|
||||
const gotKubectl = await kubectl.checkPresent(CheckPresentMessageMode.Silent);
|
||||
|
||||
|
||||
const installPromises = [
|
||||
installDependency('kubectl', gotKubectl, installKubectl)
|
||||
];
|
||||
|
||||
await Promise.all(installPromises);
|
||||
|
||||
sqlserverbigdataclusterchannel.showOutput(localize('done', 'Done'));
|
||||
}
|
||||
|
||||
async function installDependency(name: string, alreadyGot: boolean, installFunc: (shell: Shell) => Promise<Errorable<null>>): Promise<void> {
|
||||
if (alreadyGot) {
|
||||
sqlserverbigdataclusterchannel.showOutput(localize('dependencyInstalled', '{0} already installed...', name));
|
||||
} else {
|
||||
sqlserverbigdataclusterchannel.showOutput(localize('installingDependency', 'Installing {0}...', name));
|
||||
const result = await installFunc(shell);
|
||||
if (failed(result)) {
|
||||
sqlserverbigdataclusterchannel.showOutput(localize('installingDependencyFailed', 'Unable to install {0}: {1}', name, result.error[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { CreateClusterWizard } from './wizards/create-cluster/createClusterWizard';
|
||||
import { Kubectl } from './kubectl/kubectl';
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
*/
|
||||
export class MainController {
|
||||
protected _context: vscode.ExtensionContext;
|
||||
protected _kubectl: Kubectl;
|
||||
|
||||
public constructor(context: vscode.ExtensionContext, kubectl: Kubectl) {
|
||||
this._context = context;
|
||||
this._kubectl = kubectl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates the extension
|
||||
*/
|
||||
public activate(): void {
|
||||
vscode.commands.registerCommand('mssql.cluster.create', () => {
|
||||
let wizard = new CreateClusterWizard(this._context, this._kubectl);
|
||||
wizard.open();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivates the extension
|
||||
*/
|
||||
public deactivate(): void { }
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { fs } from '../utility/fs';
|
||||
import { Shell } from '../utility/shell';
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import mkdirp = require('mkdirp');
|
||||
import { Kubectl, baseKubectlPath } from '../kubectl/kubectl';
|
||||
import { KubectlContext } from '../kubectl/kubectlUtils';
|
||||
|
||||
export interface Scriptable {
|
||||
getScriptProperties(): Promise<ScriptingDictionary<string>>;
|
||||
getTargetKubectlContext(): KubectlContext;
|
||||
}
|
||||
|
||||
export interface ScriptingDictionary<V> {
|
||||
[name: string]: V;
|
||||
}
|
||||
|
||||
const deployFilePrefix: string = 'mssql-bdc-deploy';
|
||||
export class ScriptGenerator {
|
||||
|
||||
private _shell: Shell;
|
||||
private _kubectl: Kubectl;
|
||||
|
||||
private _kubectlPath: string;
|
||||
constructor(_kubectl: Kubectl) {
|
||||
this._kubectl = _kubectl;
|
||||
this._shell = this._kubectl.getContext().shell;
|
||||
this._kubectlPath = baseKubectlPath(this._kubectl.getContext());
|
||||
}
|
||||
|
||||
public async generateDeploymentScript(scriptable: Scriptable): Promise<void> {
|
||||
let targetClusterName = scriptable.getTargetKubectlContext().clusterName;
|
||||
let targetContextName = scriptable.getTargetKubectlContext().contextName;
|
||||
|
||||
let timestamp = new Date().getTime();
|
||||
let deployFolder = this.getDeploymentFolder(this._shell);
|
||||
let deployFileSuffix = this._shell.isWindows() ? `.bat` : `.sh`;
|
||||
let deployFileName = `${deployFilePrefix}-${targetClusterName}-${timestamp}${deployFileSuffix}`;
|
||||
let deployFilePath = path.join(deployFolder, deployFileName);
|
||||
|
||||
let envVars = '';
|
||||
let propertiesDict = await scriptable.getScriptProperties();
|
||||
for (let key in propertiesDict) {
|
||||
let value = propertiesDict[key];
|
||||
envVars += this._shell.isWindows() ? `Set ${key} = ${value}\n` : `export ${key} = ${value}\n`;
|
||||
}
|
||||
envVars += os.EOL;
|
||||
|
||||
let kubeContextcommand = `${this._kubectlPath} config use-context ${targetContextName}\n`;
|
||||
// Todo: The API for mssqlctl may change per version, so need a version check to use proper syntax.
|
||||
let deployCommand = `mssqlctl create cluster ${targetClusterName}\n`;
|
||||
|
||||
let deployContent = envVars + kubeContextcommand + deployCommand;
|
||||
|
||||
mkdirp.sync(deployFolder);
|
||||
await fs.writeFile(deployFilePath, deployContent, handleError);
|
||||
}
|
||||
|
||||
public getDeploymentFolder(shell: Shell): string {
|
||||
return path.join(shell.home(), `.mssql-bdc/deployment`);
|
||||
}
|
||||
}
|
||||
|
||||
const handleError = (err: NodeJS.ErrnoException) => {
|
||||
if (err) {
|
||||
vscode.window.showErrorMessage(err.message);
|
||||
}
|
||||
};
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/// <reference path='../../../../src/vs/vscode.d.ts'/>
|
||||
/// <reference path='../../../../src/sql/azdata.d.ts'/>
|
||||
/// <reference path='../../../../src/sql/azdata.proposed.d.ts'/>
|
||||
/// <reference path='../../../../src/vs/vscode.d.ts'/>
|
||||
/// <reference types='@types/node'/>
|
||||
@@ -1,65 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as sysfs from 'fs';
|
||||
|
||||
export interface FS {
|
||||
existsSync(path: string): boolean;
|
||||
readFile(filename: string, encoding: string, callback: (err: NodeJS.ErrnoException, data: string) => void): void;
|
||||
readFileSync(filename: string, encoding: string): string;
|
||||
readFileToBufferSync(filename: string): Buffer;
|
||||
writeFile(filename: string, data: any, callback?: (err: NodeJS.ErrnoException) => void): void;
|
||||
writeFileSync(filename: string, data: any): void;
|
||||
dirSync(path: string): string[];
|
||||
unlinkAsync(path: string): Promise<void>;
|
||||
existsAsync(path: string): Promise<boolean>;
|
||||
openAsync(path: string, flags: string): Promise<void>;
|
||||
statSync(path: string): sysfs.Stats;
|
||||
}
|
||||
|
||||
export const fs: FS = {
|
||||
existsSync: (path) => sysfs.existsSync(path),
|
||||
readFile: (filename, encoding, callback) => sysfs.readFile(filename, encoding, callback),
|
||||
readFileSync: (filename, encoding) => sysfs.readFileSync(filename, encoding),
|
||||
readFileToBufferSync: (filename) => sysfs.readFileSync(filename),
|
||||
writeFile: (filename, data, callback) => sysfs.writeFile(filename, data, callback),
|
||||
writeFileSync: (filename, data) => sysfs.writeFileSync(filename, data),
|
||||
dirSync: (path) => sysfs.readdirSync(path),
|
||||
|
||||
unlinkAsync: (path) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
sysfs.unlink(path, (error) => {
|
||||
if (error) {
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
existsAsync: (path) => {
|
||||
return new Promise((resolve) => {
|
||||
sysfs.exists(path, (exists) => {
|
||||
resolve(exists);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
openAsync: (path, flags) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
sysfs.open(path, flags, (error, _fd) => {
|
||||
if (error) {
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
statSync: (path) => sysfs.statSync(path)
|
||||
};
|
||||
@@ -1,204 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as shelljs from 'shelljs';
|
||||
import * as path from 'path';
|
||||
import { getActiveKubeconfig, getToolPath } from '../config/config';
|
||||
import { host } from '../kubectl/host';
|
||||
|
||||
export enum Platform {
|
||||
Windows,
|
||||
MacOS,
|
||||
Linux,
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
export interface ExecCallback extends shelljs.ExecCallback { }
|
||||
|
||||
export interface Shell {
|
||||
isWindows(): boolean;
|
||||
isUnix(): boolean;
|
||||
platform(): Platform;
|
||||
home(): string;
|
||||
combinePath(basePath: string, relativePath: string): string;
|
||||
fileUri(filePath: string): vscode.Uri;
|
||||
execOpts(): any;
|
||||
exec(cmd: string, stdin?: string): Promise<ShellResult | undefined>;
|
||||
execCore(cmd: string, opts: any, stdin?: string): Promise<ShellResult>;
|
||||
unquotedPath(path: string): string;
|
||||
which(bin: string): string | null;
|
||||
cat(path: string): string;
|
||||
ls(path: string): string[];
|
||||
}
|
||||
|
||||
export const shell: Shell = {
|
||||
isWindows: isWindows,
|
||||
isUnix: isUnix,
|
||||
platform: platform,
|
||||
home: home,
|
||||
combinePath: combinePath,
|
||||
fileUri: fileUri,
|
||||
execOpts: execOpts,
|
||||
exec: exec,
|
||||
execCore: execCore,
|
||||
unquotedPath: unquotedPath,
|
||||
which: which,
|
||||
cat: cat,
|
||||
ls: ls,
|
||||
};
|
||||
|
||||
const WINDOWS: string = 'win32';
|
||||
|
||||
export interface ShellResult {
|
||||
readonly code: number;
|
||||
readonly stdout: string;
|
||||
readonly stderr: string;
|
||||
}
|
||||
|
||||
export type ShellHandler = (code: number, stdout: string, stderr: string) => void;
|
||||
|
||||
function isWindows(): boolean {
|
||||
return (process.platform === WINDOWS);
|
||||
}
|
||||
|
||||
function isUnix(): boolean {
|
||||
return !isWindows();
|
||||
}
|
||||
|
||||
function platform(): Platform {
|
||||
switch (process.platform) {
|
||||
case 'win32': return Platform.Windows;
|
||||
case 'darwin': return Platform.MacOS;
|
||||
case 'linux': return Platform.Linux;
|
||||
default: return Platform.Unsupported;
|
||||
}
|
||||
}
|
||||
|
||||
function concatIfBoth(s1: string | undefined, s2: string | undefined): string | undefined {
|
||||
return s1 && s2 ? s1.concat(s2) : undefined;
|
||||
}
|
||||
|
||||
function home(): string {
|
||||
return process.env['HOME'] ||
|
||||
concatIfBoth(process.env['HOMEDRIVE'], process.env['HOMEPATH']) ||
|
||||
process.env['USERPROFILE'] ||
|
||||
'';
|
||||
}
|
||||
|
||||
function combinePath(basePath: string, relativePath: string) {
|
||||
let separator = '/';
|
||||
if (isWindows()) {
|
||||
relativePath = relativePath.replace(/\//g, '\\');
|
||||
separator = '\\';
|
||||
}
|
||||
return basePath + separator + relativePath;
|
||||
}
|
||||
|
||||
function isWindowsFilePath(filePath: string) {
|
||||
return filePath[1] === ':' && filePath[2] === '\\';
|
||||
}
|
||||
|
||||
function fileUri(filePath: string): vscode.Uri {
|
||||
if (isWindowsFilePath(filePath)) {
|
||||
return vscode.Uri.parse('file:///' + filePath.replace(/\\/g, '/'));
|
||||
}
|
||||
return vscode.Uri.parse('file://' + filePath);
|
||||
}
|
||||
|
||||
function execOpts(): any {
|
||||
let env = process.env;
|
||||
if (isWindows()) {
|
||||
env = Object.assign({}, env, { HOME: home() });
|
||||
}
|
||||
env = shellEnvironment(env);
|
||||
const opts = {
|
||||
cwd: vscode.workspace.rootPath,
|
||||
env: env,
|
||||
async: true
|
||||
};
|
||||
return opts;
|
||||
}
|
||||
|
||||
async function exec(cmd: string, stdin?: string): Promise<ShellResult | undefined> {
|
||||
try {
|
||||
return await execCore(cmd, execOpts(), stdin);
|
||||
} catch (ex) {
|
||||
vscode.window.showErrorMessage(ex);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function execCore(cmd: string, opts: any, stdin?: string): Promise<ShellResult> {
|
||||
return new Promise<ShellResult>((resolve) => {
|
||||
const proc = shelljs.exec(cmd, opts, (code, stdout, stderr) => resolve({ code: code, stdout: stdout, stderr: stderr }));
|
||||
if (stdin) {
|
||||
proc.stdin.end(stdin);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function unquotedPath(path: string): string {
|
||||
if (isWindows() && path && path.length > 1 && path.startsWith('"') && path.endsWith('"')) {
|
||||
return path.substring(1, path.length - 1);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
export function shellEnvironment(baseEnvironment: any): any {
|
||||
const env = Object.assign({}, baseEnvironment);
|
||||
const pathVariable = pathVariableName(env);
|
||||
for (const tool of ['kubectl']) {
|
||||
const toolPath = getToolPath(host, shell, tool);
|
||||
if (toolPath) {
|
||||
const toolDirectory = path.dirname(toolPath);
|
||||
const currentPath = env[pathVariable];
|
||||
env[pathVariable] = toolDirectory + (currentPath ? `${pathEntrySeparator()}${currentPath}` : '');
|
||||
}
|
||||
}
|
||||
|
||||
const kubeconfig = getActiveKubeconfig();
|
||||
if (kubeconfig) {
|
||||
env['KUBECONFIG'] = kubeconfig;
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
function pathVariableName(env: any): string {
|
||||
if (isWindows()) {
|
||||
for (const v of Object.keys(env)) {
|
||||
if (v.toLowerCase() === 'path') {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 'PATH';
|
||||
}
|
||||
|
||||
function pathEntrySeparator() {
|
||||
return isWindows() ? ';' : ':';
|
||||
}
|
||||
|
||||
function which(bin: string): string | null {
|
||||
return shelljs.which(bin);
|
||||
}
|
||||
|
||||
function cat(path: string): string {
|
||||
return shelljs.cat(path);
|
||||
}
|
||||
|
||||
function ls(path: string): string[] {
|
||||
return shelljs.ls(path);
|
||||
}
|
||||
|
||||
export function shellMessage(sr: ShellResult | undefined, invocationFailureMessage: string): string {
|
||||
if (!sr) {
|
||||
return invocationFailureMessage;
|
||||
}
|
||||
return sr.code === 0 ? sr.stdout : sr.stderr;
|
||||
}
|
||||
@@ -1,328 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { TargetClusterType, ClusterPorts, ClusterType, ContainerRegistryInfo, TargetClusterTypeInfo, ToolInfo, ToolInstallationStatus, ClusterProfile, PoolConfiguration, SQLServerMasterConfiguration, ClusterPoolType, ClusterResourceSummary } from '../../interfaces';
|
||||
import { getContexts, KubectlContext, setContext, inferCurrentClusterType } from '../../kubectl/kubectlUtils';
|
||||
import { Kubectl } from '../../kubectl/kubectl';
|
||||
import { Scriptable, ScriptingDictionary } from '../../scripting/scripting';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class CreateClusterModel implements Scriptable {
|
||||
|
||||
private _tmp_tools_installed: boolean = false;
|
||||
private scriptingProperties: ScriptingDictionary<string> = {};
|
||||
constructor(private _kubectl: Kubectl) {
|
||||
}
|
||||
|
||||
public async loadClusters(): Promise<KubectlContext[]> {
|
||||
return await getContexts(this._kubectl);
|
||||
}
|
||||
|
||||
public async changeKubernetesContext(targetContext: string): Promise<void> {
|
||||
await setContext(this._kubectl, targetContext);
|
||||
}
|
||||
|
||||
public getDefaultPorts(): Thenable<ClusterPorts> {
|
||||
let promise = new Promise<ClusterPorts>(resolve => {
|
||||
resolve({
|
||||
sql: '31433',
|
||||
knox: '30443',
|
||||
controller: '30888',
|
||||
proxy: '30909',
|
||||
grafana: '30119',
|
||||
kibana: '30999'
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
public getDefaultContainerRegistryInfo(): Thenable<ContainerRegistryInfo> {
|
||||
let promise = new Promise<ContainerRegistryInfo>(resolve => {
|
||||
resolve({
|
||||
registry: 'private-repo.microsoft.com',
|
||||
repository: 'mssql-private-preview',
|
||||
imageTag: 'latest'
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
public getAllTargetClusterTypeInfo(): Thenable<TargetClusterTypeInfo[]> {
|
||||
let promise = new Promise<TargetClusterTypeInfo[]>(resolve => {
|
||||
let aksCluster: TargetClusterTypeInfo = {
|
||||
enabled: false,
|
||||
type: TargetClusterType.NewAksCluster,
|
||||
name: localize('bdc-create.AKSClusterCardText', 'New AKS Cluster'),
|
||||
fullName: localize('bdc-create.AKSClusterFullName', 'New Azure Kubernetes Service cluster'),
|
||||
description: localize('bdc-create.AKSClusterDescription',
|
||||
'This option configures new Azure Kubernetes Service (AKS) for SQL Server big data cluster deployments. AKS makes it simple to create, configure and manage a cluster of virutal machines that are preconfigured with a Kubernetes cluster to run containerized applications.'),
|
||||
iconPath: {
|
||||
dark: 'images/aks.svg',
|
||||
light: 'images/aks.svg'
|
||||
}
|
||||
};
|
||||
|
||||
let existingCluster: TargetClusterTypeInfo = {
|
||||
enabled: true,
|
||||
type: TargetClusterType.ExistingKubernetesCluster,
|
||||
name: localize('bdc-create.ExistingClusterCardText', 'Existing Cluster'),
|
||||
fullName: localize('bdc-create.ExistingClusterFullName', 'Existing Kubernetes cluster'),
|
||||
description: localize('bdc-create.ExistingClusterDescription', 'This option assumes you already have a Kubernetes cluster installed, Once a prerequisite check is done, ensure the correct cluster context is selected.'),
|
||||
iconPath: {
|
||||
dark: 'images/kubernetes.svg',
|
||||
light: 'images/kubernetes.svg'
|
||||
}
|
||||
};
|
||||
resolve([aksCluster, existingCluster]);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
public getRequiredToolStatus(): Thenable<ToolInfo[]> {
|
||||
let kubeCtl = {
|
||||
name: 'kubectl',
|
||||
description: 'Tool used for managing the Kubernetes cluster',
|
||||
version: '',
|
||||
status: ToolInstallationStatus.Installed
|
||||
};
|
||||
let mssqlCtl = {
|
||||
name: 'mssqlctl',
|
||||
description: 'Command-line tool for installing and managing the SQL Server big data cluster',
|
||||
version: '',
|
||||
status: ToolInstallationStatus.Installed
|
||||
};
|
||||
let azureCli = {
|
||||
name: 'Azure CLI',
|
||||
description: 'Tool used for managing Azure services',
|
||||
version: '',
|
||||
status: this._tmp_tools_installed ? ToolInstallationStatus.Installed : ToolInstallationStatus.NotInstalled
|
||||
};
|
||||
let promise = new Promise<ToolInfo[]>(resolve => {
|
||||
setTimeout(() => {
|
||||
let tools = this.targetClusterType === TargetClusterType.ExistingKubernetesCluster ? [kubeCtl, mssqlCtl] : [kubeCtl, mssqlCtl, azureCli];
|
||||
resolve(tools);
|
||||
}, 1000);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
public installTool(tool: ToolInfo): Thenable<void> {
|
||||
let promise = new Promise<void>(resolve => {
|
||||
setTimeout(() => {
|
||||
tool.status = ToolInstallationStatus.Installed;
|
||||
this._tmp_tools_installed = true;
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
public getDefaultKubeConfigPath(): string {
|
||||
return path.join(os.homedir(), '.kube', 'config');
|
||||
}
|
||||
|
||||
public clusterName: string;
|
||||
|
||||
public targetClusterType: TargetClusterType;
|
||||
|
||||
public selectedCluster: KubectlContext;
|
||||
|
||||
public adminUserName: string;
|
||||
|
||||
public adminPassword: string;
|
||||
|
||||
public sqlPort: string;
|
||||
|
||||
public knoxPort: string;
|
||||
|
||||
public controllerPort: string;
|
||||
|
||||
public proxyPort: string;
|
||||
|
||||
public grafanaPort: string;
|
||||
|
||||
public kibanaPort: string;
|
||||
|
||||
public containerRegistry: string;
|
||||
|
||||
public containerRepository: string;
|
||||
|
||||
public containerImageTag: string;
|
||||
|
||||
public containerRegistryUserName: string;
|
||||
|
||||
public containerRegistryPassword: string;
|
||||
|
||||
public profile: ClusterProfile;
|
||||
|
||||
public async getTargetClusterPlatform(targetContextName: string): Promise<string> {
|
||||
await setContext(this._kubectl, targetContextName);
|
||||
let clusterType = await inferCurrentClusterType(this._kubectl);
|
||||
|
||||
switch (clusterType) {
|
||||
case ClusterType.AKS:
|
||||
return 'aks';
|
||||
case ClusterType.Minikube:
|
||||
return 'minikube';
|
||||
case ClusterType.Other:
|
||||
default:
|
||||
return 'kubernetes';
|
||||
}
|
||||
}
|
||||
|
||||
public async getScriptProperties(): Promise<ScriptingDictionary<string>> {
|
||||
|
||||
// Cluster settings
|
||||
this.scriptingProperties['CLUSTER_NAME'] = this.selectedCluster.clusterName;
|
||||
this.scriptingProperties['CLUSTER_PLATFORM'] = await this.getTargetClusterPlatform(this.selectedCluster.contextName);
|
||||
|
||||
// Default pool count for now. TODO: Update from user input
|
||||
this.scriptingProperties['CLUSTER_DATA_POOL_REPLICAS'] = '1';
|
||||
this.scriptingProperties['CLUSTER_COMPUTE_POOL_REPLICAS'] = '2';
|
||||
this.scriptingProperties['CLUSTER_STORAGE_POOL_REPLICAS'] = '3';
|
||||
|
||||
// SQL Server settings
|
||||
this.scriptingProperties['CONTROLLER_USERNAME'] = this.adminUserName;
|
||||
this.scriptingProperties['CONTROLLER_PASSWORD'] = this.adminPassword;
|
||||
this.scriptingProperties['KNOX_PASSWORD'] = this.adminPassword;
|
||||
this.scriptingProperties['MSSQL_SA_PASSWORD'] = this.adminPassword;
|
||||
|
||||
// docker settings
|
||||
this.scriptingProperties['DOCKER_REPOSITORY'] = this.containerRepository;
|
||||
this.scriptingProperties['DOCKER_REGISTRY'] = this.containerRegistry;
|
||||
this.scriptingProperties['DOCKER_PASSWORD'] = this.containerRegistryPassword;
|
||||
this.scriptingProperties['DOCKER_USERNAME'] = this.containerRegistryUserName;
|
||||
this.scriptingProperties['DOCKER_IMAGE_TAG'] = this.containerImageTag;
|
||||
|
||||
// port settings
|
||||
this.scriptingProperties['MASTER_SQL_PORT'] = this.sqlPort;
|
||||
this.scriptingProperties['KNOX_PORT'] = this.knoxPort;
|
||||
this.scriptingProperties['GRAFANA_PORT'] = this.grafanaPort;
|
||||
this.scriptingProperties['KIBANA_PORT'] = this.kibanaPort;
|
||||
|
||||
return this.scriptingProperties;
|
||||
}
|
||||
|
||||
public getTargetKubectlContext(): KubectlContext {
|
||||
return this.selectedCluster;
|
||||
}
|
||||
|
||||
public getClusterResource(): Thenable<ClusterResourceSummary> {
|
||||
let promise = new Promise<ClusterResourceSummary>(resolve => {
|
||||
setTimeout(() => {
|
||||
let resoureSummary: ClusterResourceSummary = {
|
||||
hardwareLabels: [
|
||||
{
|
||||
name: '<Default>',
|
||||
totalNodes: 10,
|
||||
totalCores: 22,
|
||||
totalDisks: 128,
|
||||
totalMemoryInGB: 77
|
||||
},
|
||||
{
|
||||
name: '#data',
|
||||
totalNodes: 4,
|
||||
totalCores: 22,
|
||||
totalDisks: 200,
|
||||
totalMemoryInGB: 100
|
||||
},
|
||||
{
|
||||
name: '#compute',
|
||||
totalNodes: 12,
|
||||
totalCores: 124,
|
||||
totalDisks: 24,
|
||||
totalMemoryInGB: 100
|
||||
},
|
||||
{
|
||||
name: '#premium',
|
||||
totalNodes: 10,
|
||||
totalCores: 100,
|
||||
totalDisks: 200,
|
||||
totalMemoryInGB: 770
|
||||
}
|
||||
]
|
||||
};
|
||||
resolve(resoureSummary);
|
||||
}, 1000);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
public getProfiles(): Thenable<ClusterProfile[]> {
|
||||
let promise = new Promise<ClusterProfile[]>(resolve => {
|
||||
setTimeout(() => {
|
||||
let profiles: ClusterProfile[] = [];
|
||||
profiles.push({
|
||||
name: 'Basic',
|
||||
sqlServerMasterConfiguration: this.createSQLPoolConfiguration(1, 1),
|
||||
computePoolConfiguration: this.createComputePoolConfiguration(2),
|
||||
dataPoolConfiguration: this.createDataPoolConfiguration(2),
|
||||
storagePoolConfiguration: this.createStoragePoolConfiguration(2),
|
||||
sparkPoolConfiguration: this.createSparkPoolConfiguration(2)
|
||||
});
|
||||
profiles.push({
|
||||
name: 'Standard',
|
||||
sqlServerMasterConfiguration: this.createSQLPoolConfiguration(3, 9),
|
||||
computePoolConfiguration: this.createComputePoolConfiguration(5),
|
||||
dataPoolConfiguration: this.createDataPoolConfiguration(5),
|
||||
storagePoolConfiguration: this.createStoragePoolConfiguration(5),
|
||||
sparkPoolConfiguration: this.createSparkPoolConfiguration(5)
|
||||
});
|
||||
profiles.push({
|
||||
name: 'Premium',
|
||||
sqlServerMasterConfiguration: this.createSQLPoolConfiguration(5, 9),
|
||||
computePoolConfiguration: this.createComputePoolConfiguration(7),
|
||||
dataPoolConfiguration: this.createDataPoolConfiguration(7),
|
||||
storagePoolConfiguration: this.createStoragePoolConfiguration(7),
|
||||
sparkPoolConfiguration: this.createSparkPoolConfiguration(7)
|
||||
});
|
||||
resolve(profiles);
|
||||
}, 1000);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
private createSQLPoolConfiguration(scale: number, maxScale: number): SQLServerMasterConfiguration {
|
||||
return <SQLServerMasterConfiguration>{
|
||||
type: ClusterPoolType.SQL,
|
||||
engineOnly: false,
|
||||
scale: scale,
|
||||
maxScale: maxScale
|
||||
};
|
||||
}
|
||||
|
||||
private createComputePoolConfiguration(scale: number): PoolConfiguration {
|
||||
return {
|
||||
type: ClusterPoolType.Compute,
|
||||
scale: scale
|
||||
};
|
||||
}
|
||||
|
||||
private createDataPoolConfiguration(scale: number): PoolConfiguration {
|
||||
return {
|
||||
type: ClusterPoolType.Data,
|
||||
scale: scale
|
||||
};
|
||||
}
|
||||
|
||||
private createStoragePoolConfiguration(scale: number): PoolConfiguration {
|
||||
return {
|
||||
type: ClusterPoolType.Storage,
|
||||
scale: scale
|
||||
};
|
||||
}
|
||||
|
||||
private createSparkPoolConfiguration(scale: number): PoolConfiguration {
|
||||
return {
|
||||
type: ClusterPoolType.Spark,
|
||||
scale: scale
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { CreateClusterModel } from './createClusterModel';
|
||||
import { SelectExistingClusterPage } from './pages/selectExistingClusterPage';
|
||||
import { SummaryPage } from './pages/summaryPage';
|
||||
import { SettingsPage } from './pages/settingsPage';
|
||||
import { ClusterProfilePage } from './pages/clusterProfilePage';
|
||||
import { ExtensionContext } from 'vscode';
|
||||
import { WizardBase } from '../wizardBase';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { Kubectl } from '../../kubectl/kubectl';
|
||||
import { SelectTargetClusterTypePage } from './pages/selectTargetClusterTypePage';
|
||||
import { ScriptGenerator } from '../../scripting/scripting';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class CreateClusterWizard extends WizardBase<CreateClusterModel, CreateClusterWizard> {
|
||||
private scripter: ScriptGenerator;
|
||||
constructor(context: ExtensionContext, kubectl: Kubectl) {
|
||||
let model = new CreateClusterModel(kubectl);
|
||||
super(model, context, localize('bdc-create.wizardTitle', 'Create a big data cluster'));
|
||||
this.scripter = new ScriptGenerator(kubectl);
|
||||
}
|
||||
|
||||
protected initialize(): void {
|
||||
let settingsPage = new SettingsPage(this);
|
||||
let clusterProfilePage = new ClusterProfilePage(this);
|
||||
let selectTargetClusterPage = new SelectExistingClusterPage(this);
|
||||
let summaryPage = new SummaryPage(this);
|
||||
let targetClusterTypePage = new SelectTargetClusterTypePage(this);
|
||||
this.setPages([targetClusterTypePage, selectTargetClusterPage, settingsPage, clusterProfilePage, summaryPage]);
|
||||
|
||||
this.wizardObject.generateScriptButton.label = localize('bdc-create.generateScriptsButtonText', 'Generate Scripts');
|
||||
this.wizardObject.generateScriptButton.hidden = false;
|
||||
this.wizardObject.doneButton.label = localize('bdc-create.createClusterButtonText', 'Create');
|
||||
|
||||
this.registerDisposable(this.wizardObject.generateScriptButton.onClick(async () => {
|
||||
this.wizardObject.generateScriptButton.enabled = false;
|
||||
this.scripter.generateDeploymentScript(this.model).then(() => {
|
||||
this.wizardObject.generateScriptButton.enabled = true;
|
||||
//TODO: Add error handling.
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
protected onCancel(): void {
|
||||
}
|
||||
|
||||
protected onOk(): void {
|
||||
}
|
||||
}
|
||||
@@ -1,389 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { WizardPageBase } from '../../wizardPageBase';
|
||||
import { CreateClusterWizard } from '../createClusterWizard';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { ClusterProfile, PoolConfiguration, ClusterPoolType, SQLServerMasterConfiguration, ClusterResourceSummary } from '../../../interfaces';
|
||||
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
const LabelWidth = '200px';
|
||||
const InputWidth = '300px';
|
||||
|
||||
export class ClusterProfilePage extends WizardPageBase<CreateClusterWizard> {
|
||||
private view: azdata.ModelView;
|
||||
private clusterProfiles: ClusterProfile[];
|
||||
private poolList: azdata.FlexContainer;
|
||||
private detailContainer: azdata.FlexContainer;
|
||||
private clusterResourceView: azdata.GroupContainer;
|
||||
private poolListMap = {};
|
||||
private clusterResourceContainer: azdata.FlexContainer;
|
||||
private clusterResourceLoadingComponent: azdata.LoadingComponent;
|
||||
private clusterResource: ClusterResourceSummary;
|
||||
|
||||
|
||||
constructor(wizard: CreateClusterWizard) {
|
||||
super(localize('bdc-create.clusterProfilePageTitle', 'Select a cluster profile'),
|
||||
localize('bdc-create.clusterProfilePageDescription', 'Select your requirement and we will provide you a pre-defined default scaling. You can later go to cluster configuration and customize it.'),
|
||||
wizard);
|
||||
}
|
||||
|
||||
public onEnter(): void {
|
||||
this.updatePoolList();
|
||||
this.clusterResourceLoadingComponent.loading = true;
|
||||
this.wizard.model.getClusterResource().then((resource) => {
|
||||
this.clusterResource = resource;
|
||||
this.initializeClusterResourceView();
|
||||
});
|
||||
this.wizard.wizardObject.registerNavigationValidator(() => {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
protected initialize(view: azdata.ModelView): Thenable<void> {
|
||||
this.view = view;
|
||||
let fetchProfilePromise = this.wizard.model.getProfiles().then(p => { this.clusterProfiles = p; });
|
||||
return Promise.all([fetchProfilePromise]).then(() => {
|
||||
this.wizard.model.profile = this.clusterProfiles[0];
|
||||
this.clusterResourceView = this.view.modelBuilder.groupContainer().withLayout({
|
||||
header: localize('bdc-create.TargetClusterOverview', 'Target cluster scale overview'),
|
||||
collapsed: true,
|
||||
collapsible: true
|
||||
}).component();
|
||||
|
||||
this.clusterResourceContainer = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
||||
this.clusterResourceLoadingComponent = this.view.modelBuilder.loadingComponent().withItem(this.clusterResourceContainer).component();
|
||||
this.clusterResourceView.addItem(this.clusterResourceLoadingComponent);
|
||||
|
||||
let profileLabel = view.modelBuilder.text().withProperties({ value: localize('bdc-create.clusterProfileLabel', 'Deployment profile') }).component();
|
||||
let profileDropdown = view.modelBuilder.dropDown().withProperties<azdata.DropDownProperties>({
|
||||
values: this.clusterProfiles.map(profile => profile.name),
|
||||
width: '300px'
|
||||
}).component();
|
||||
let dropdownRow = this.view.modelBuilder.flexContainer().withItems([profileLabel, profileDropdown], { CSSStyles: { 'margin-right': '30px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
|
||||
let poolContainer = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', width: '100%', height: '100%' }).component();
|
||||
this.poolList = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '300px', height: '100%' }).component();
|
||||
poolContainer.addItem(this.poolList, {
|
||||
CSSStyles: {
|
||||
'border-top-style': 'solid',
|
||||
'border-top-width': '2px',
|
||||
'border-right-style': 'solid',
|
||||
'border-right-width': '2px',
|
||||
'border-color': 'lightgray'
|
||||
}
|
||||
});
|
||||
|
||||
this.detailContainer = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '760px', height: '100%' }).component();
|
||||
poolContainer.addItem(this.detailContainer, {
|
||||
CSSStyles: {
|
||||
'border-top-style': 'solid',
|
||||
'border-top-width': '2px',
|
||||
'border-color': 'lightgray'
|
||||
}
|
||||
});
|
||||
|
||||
this.wizard.registerDisposable(profileDropdown.onValueChanged(() => {
|
||||
let profiles = this.clusterProfiles.filter(p => profileDropdown.value === p.name);
|
||||
if (profiles && profiles.length === 1) {
|
||||
this.wizard.model.profile = profiles[0];
|
||||
this.updatePoolList();
|
||||
this.clearPoolDetail();
|
||||
}
|
||||
}));
|
||||
|
||||
this.initializePoolList();
|
||||
|
||||
let pageContainer = this.view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'column',
|
||||
height: '800px'
|
||||
}).component();
|
||||
pageContainer.addItem(this.clusterResourceView, {
|
||||
flex: '0 0 auto',
|
||||
CSSStyles: {
|
||||
'margin-bottom': '20px',
|
||||
'padding-bottom': '5px',
|
||||
'padding-top': '5px'
|
||||
}
|
||||
});
|
||||
pageContainer.addItem(dropdownRow, {
|
||||
flex: '0 0 auto',
|
||||
CSSStyles: { 'margin-bottom': '10px' }
|
||||
});
|
||||
pageContainer.addItem(poolContainer, {
|
||||
flex: '1 1 auto',
|
||||
CSSStyles: {
|
||||
'display': 'flex'
|
||||
}
|
||||
});
|
||||
let formBuilder = view.modelBuilder.formContainer();
|
||||
let form = formBuilder.withFormItems([{
|
||||
title: '',
|
||||
component: pageContainer
|
||||
}], {
|
||||
horizontal: false,
|
||||
componentWidth: '100%'
|
||||
}).component();
|
||||
|
||||
return view.initializeModel(form);
|
||||
});
|
||||
}
|
||||
|
||||
private initializeClusterResourceView(): void {
|
||||
this.clusterResourceContainer.clearItems();
|
||||
let text = this.view.modelBuilder.text().withProperties({ value: localize('bdc-create.HardwareProfileText', 'Hardware profile') }).component();
|
||||
let height = (this.clusterResource.hardwareLabels.length * 25) + 30;
|
||||
let labelColumn: azdata.TableColumn = {
|
||||
value: localize('bdc-create.HardwareLabelColumnName', 'Label'),
|
||||
width: 100
|
||||
};
|
||||
let totalNodesColumn: azdata.TableColumn = {
|
||||
value: localize('bdc-create.TotalNodesColumnName', 'Nodes'),
|
||||
width: 50
|
||||
};
|
||||
let totalCoresColumn: azdata.TableColumn = {
|
||||
value: localize('bdc-create.TotalCoresColumnName', 'Cores'),
|
||||
width: 50
|
||||
};
|
||||
let totalMemoryColumn: azdata.TableColumn = {
|
||||
value: localize('bdc-create.TotalMemoryColumnName', 'Memory'),
|
||||
width: 50
|
||||
};
|
||||
let totalDisksColumn: azdata.TableColumn = {
|
||||
value: localize('bdc-create.TotalDisksColumnName', 'Disks'),
|
||||
width: 50
|
||||
};
|
||||
|
||||
let table = this.view.modelBuilder.table().withProperties<azdata.TableComponentProperties>({
|
||||
height: `${height}px`,
|
||||
data: this.clusterResource.hardwareLabels.map(label => [label.name, label.totalNodes, label.totalCores, label.totalMemoryInGB, label.totalDisks]),
|
||||
columns: [labelColumn, totalNodesColumn, totalCoresColumn, totalMemoryColumn, totalDisksColumn],
|
||||
width: '300px'
|
||||
|
||||
}).component();
|
||||
this.clusterResourceContainer.addItems([text, table]);
|
||||
this.clusterResourceLoadingComponent.loading = false;
|
||||
}
|
||||
|
||||
private initializePoolList(): void {
|
||||
let pools = [this.wizard.model.profile.sqlServerMasterConfiguration,
|
||||
this.wizard.model.profile.computePoolConfiguration,
|
||||
this.wizard.model.profile.dataPoolConfiguration,
|
||||
this.wizard.model.profile.sparkPoolConfiguration,
|
||||
this.wizard.model.profile.storagePoolConfiguration];
|
||||
pools.forEach(pool => {
|
||||
let poolSummaryButton = this.view.modelBuilder.divContainer().withProperties<azdata.DivContainerProperties>({ clickable: true }).component();
|
||||
let container = this.view.modelBuilder.flexContainer().component();
|
||||
this.wizard.registerDisposable(poolSummaryButton.onDidClick(() => {
|
||||
this.clearPoolDetail();
|
||||
let currentPool: PoolConfiguration;
|
||||
switch (pool.type) {
|
||||
case ClusterPoolType.SQL:
|
||||
currentPool = this.wizard.model.profile.sqlServerMasterConfiguration;
|
||||
break;
|
||||
case ClusterPoolType.Compute:
|
||||
currentPool = this.wizard.model.profile.computePoolConfiguration;
|
||||
break;
|
||||
case ClusterPoolType.Data:
|
||||
currentPool = this.wizard.model.profile.dataPoolConfiguration;
|
||||
break;
|
||||
case ClusterPoolType.Storage:
|
||||
currentPool = this.wizard.model.profile.storagePoolConfiguration;
|
||||
break;
|
||||
case ClusterPoolType.Spark:
|
||||
currentPool = this.wizard.model.profile.sparkPoolConfiguration;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (currentPool) {
|
||||
this.detailContainer.addItem(this.createPoolConfigurationPart(currentPool), { CSSStyles: { 'margin-left': '10px' } });
|
||||
}
|
||||
}));
|
||||
|
||||
let text = this.view.modelBuilder.text().component();
|
||||
this.poolListMap[pool.type] = text;
|
||||
text.width = '250px';
|
||||
let chrevron = this.view.modelBuilder.text().withProperties({ value: '>' }).component();
|
||||
chrevron.width = '30px';
|
||||
container.addItem(text);
|
||||
container.addItem(chrevron, {
|
||||
CSSStyles: {
|
||||
'font-size': '20px',
|
||||
'line-height': '0px'
|
||||
}
|
||||
});
|
||||
poolSummaryButton.addItem(container);
|
||||
this.poolList.addItem(poolSummaryButton, {
|
||||
CSSStyles: {
|
||||
'border-bottom-style': 'solid',
|
||||
'border-bottom-width': '1px',
|
||||
'border-color': 'lightgray',
|
||||
'cursor': 'pointer'
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private createPoolConfigurationPart(configuration: PoolConfiguration): azdata.Component {
|
||||
let container = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
||||
switch (configuration.type) {
|
||||
case ClusterPoolType.SQL:
|
||||
this.createSQLConfigurationPart(container, configuration as SQLServerMasterConfiguration);
|
||||
break;
|
||||
default:
|
||||
this.createDefaultPoolConfigurationPart(container, configuration);
|
||||
break;
|
||||
}
|
||||
return container;
|
||||
}
|
||||
|
||||
private createSQLConfigurationPart(container: azdata.FlexContainer, configuration: SQLServerMasterConfiguration): void {
|
||||
this.createDefaultPoolConfigurationPart(container, configuration);
|
||||
this.addFeatureSetRow(container, configuration);
|
||||
}
|
||||
|
||||
private createDefaultPoolConfigurationPart(container: azdata.FlexContainer, configuration: PoolConfiguration): void {
|
||||
this.addPoolNameLabel(container, this.getPoolDisplayName(configuration.type));
|
||||
this.addPoolDescriptionLabel(container, this.getPoolDescription(configuration.type));
|
||||
this.addScaleRow(container, configuration);
|
||||
this.addHardwareLabelRow(container, configuration);
|
||||
}
|
||||
|
||||
private addPoolNameLabel(container: azdata.FlexContainer, text: string): void {
|
||||
let poolNameLabel = this.view.modelBuilder.text().withProperties({ value: text }).component();
|
||||
container.addItem(poolNameLabel, {
|
||||
flex: '0 0 auto', CSSStyles: {
|
||||
'font-size': '13px',
|
||||
'font-weight': 'bold'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private addPoolDescriptionLabel(container: azdata.FlexContainer, text: string): void {
|
||||
let label = this.view.modelBuilder.text().withProperties({ value: text }).component();
|
||||
container.addItem(label, {
|
||||
flex: '0 0 auto',
|
||||
CSSStyles: {
|
||||
'margin-bottom': '20px'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private addScaleRow(container: azdata.FlexContainer, configuration: PoolConfiguration): void {
|
||||
let label = this.view.modelBuilder.text().withProperties({ value: localize('bdc-create.ScaleLabel', 'Scale') }).component();
|
||||
label.width = LabelWidth;
|
||||
let input = this.view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
inputType: 'number',
|
||||
value: configuration.scale.toString(),
|
||||
min: 1,
|
||||
max: configuration.maxScale
|
||||
}).component();
|
||||
|
||||
this.wizard.registerDisposable(input.onTextChanged(() => {
|
||||
configuration.scale = Number(input.value);
|
||||
this.updatePoolList();
|
||||
}));
|
||||
input.width = InputWidth;
|
||||
let row = this.createRow([label, input]);
|
||||
container.addItem(row);
|
||||
}
|
||||
|
||||
private addHardwareLabelRow(container: azdata.FlexContainer, configuration: PoolConfiguration): void {
|
||||
let label = this.view.modelBuilder.text().withProperties({ value: localize('bdc-create.HardwareProfileLabel', 'Hardware profile label') }).component();
|
||||
label.width = LabelWidth;
|
||||
let optionalValues = this.clusterResource.hardwareLabels.map(label => label.name);
|
||||
configuration.hardwareLabel = configuration.hardwareLabel ? configuration.hardwareLabel : optionalValues[0];
|
||||
let input = this.view.modelBuilder.dropDown().withProperties<azdata.DropDownProperties>({ value: configuration.hardwareLabel, values: optionalValues }).component();
|
||||
this.wizard.registerDisposable(input.onValueChanged(() => {
|
||||
configuration.hardwareLabel = input.value.toString();
|
||||
}));
|
||||
input.width = InputWidth;
|
||||
let row = this.createRow([label, input]);
|
||||
container.addItem(row);
|
||||
}
|
||||
|
||||
private addFeatureSetRow(container: azdata.FlexContainer, configuration: SQLServerMasterConfiguration): void {
|
||||
const radioGroupName = 'featureset';
|
||||
let label = this.view.modelBuilder.text().withProperties({ value: localize('bdc-create.FeatureSetLabel', 'Feature set') }).component();
|
||||
label.width = LabelWidth;
|
||||
let engineOnlyOption = this.view.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({ label: localize('bdc-create.EngineOnlyText', 'Engine only'), name: radioGroupName, checked: configuration.engineOnly }).component();
|
||||
let engineWithFeaturesOption = this.view.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({ label: localize('bdc-create.EngineWithFeaturesText', 'Engine with optional features'), name: radioGroupName, checked: !configuration.engineOnly }).component();
|
||||
let optionContainer = this.view.modelBuilder.divContainer().component();
|
||||
optionContainer.width = InputWidth;
|
||||
optionContainer.addItems([engineOnlyOption, engineWithFeaturesOption]);
|
||||
container.addItem(this.createRow([label, optionContainer]));
|
||||
this.wizard.registerDisposable(engineOnlyOption.onDidClick(() => {
|
||||
configuration.engineOnly = true;
|
||||
}));
|
||||
this.wizard.registerDisposable(engineWithFeaturesOption.onDidClick(() => {
|
||||
configuration.engineOnly = false;
|
||||
}));
|
||||
}
|
||||
|
||||
private createRow(items: azdata.Component[]): azdata.FlexContainer {
|
||||
return this.view.modelBuilder.flexContainer().withItems(items, {
|
||||
CSSStyles: {
|
||||
'margin-right': '5px'
|
||||
}
|
||||
}).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
|
||||
}
|
||||
|
||||
private getPoolDisplayName(poolType: ClusterPoolType): string {
|
||||
switch (poolType) {
|
||||
case ClusterPoolType.SQL:
|
||||
return localize('bdc-create.SQLServerMasterDisplayName', 'SQL Server master');
|
||||
case ClusterPoolType.Compute:
|
||||
return localize('bdc-create.ComputePoolDisplayName', 'Compute pool');
|
||||
case ClusterPoolType.Data:
|
||||
return localize('bdc-create.DataPoolDisplayName', 'Data pool');
|
||||
case ClusterPoolType.Storage:
|
||||
return localize('bdc-create.StoragePoolDisplayName', 'Storage pool');
|
||||
case ClusterPoolType.Spark:
|
||||
return localize('bdc-create.SparkPoolDisplayName', 'Spark pool');
|
||||
default:
|
||||
throw new Error('unknown pool type');
|
||||
}
|
||||
}
|
||||
|
||||
private getPoolDescription(poolType: ClusterPoolType): string {
|
||||
switch (poolType) {
|
||||
case ClusterPoolType.SQL:
|
||||
return localize('bdc-create.SQLServerMasterDescription', 'The SQL Server instance provides an externally accessible TDS endpoint for the cluster');
|
||||
case ClusterPoolType.Compute:
|
||||
return localize('bdc-create.ComputePoolDescription', 'TODO: Add description');
|
||||
case ClusterPoolType.Data:
|
||||
return localize('bdc-create.DataPoolDescription', 'TODO: Add description');
|
||||
case ClusterPoolType.Storage:
|
||||
return localize('bdc-create.StoragePoolDescription', 'TODO: Add description');
|
||||
case ClusterPoolType.Spark:
|
||||
return localize('bdc-create.SparkPoolDescription', 'TODO: Add description');
|
||||
default:
|
||||
throw new Error('unknown pool type');
|
||||
}
|
||||
}
|
||||
|
||||
private updatePoolList(): void {
|
||||
let pools = [this.wizard.model.profile.sqlServerMasterConfiguration,
|
||||
this.wizard.model.profile.computePoolConfiguration,
|
||||
this.wizard.model.profile.dataPoolConfiguration,
|
||||
this.wizard.model.profile.sparkPoolConfiguration,
|
||||
this.wizard.model.profile.storagePoolConfiguration];
|
||||
pools.forEach(pool => {
|
||||
let text = this.poolListMap[pool.type] as azdata.TextComponent;
|
||||
if (text) {
|
||||
text.value = localize({
|
||||
key: 'bdc-create.poolLabelTemplate',
|
||||
comment: ['{0} is the pool name, {1} is the scale number']
|
||||
}, '{0} ({1})', this.getPoolDisplayName(pool.type), pool.scale);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private clearPoolDetail(): void {
|
||||
this.detailContainer.clearItems();
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as os from 'os';
|
||||
import * as fs from 'fs';
|
||||
import { WizardPageBase } from '../../wizardPageBase';
|
||||
import { CreateClusterWizard } from '../createClusterWizard';
|
||||
import { setActiveKubeconfig } from '../../../config/config';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
const ClusterRadioButtonGroupName = 'cluster';
|
||||
|
||||
export class SelectExistingClusterPage extends WizardPageBase<CreateClusterWizard> {
|
||||
private existingClusterControl: azdata.FlexContainer;
|
||||
private clusterContextsLabel: azdata.TextComponent;
|
||||
private errorLoadingClustersLabel: azdata.TextComponent;
|
||||
private clusterContextList: azdata.DivContainer;
|
||||
private clusterContextLoadingComponent: azdata.LoadingComponent;
|
||||
private configFileInput: azdata.InputBoxComponent;
|
||||
private browseFileButton: azdata.ButtonComponent;
|
||||
private loadDefaultKubeConfigFile: boolean = true;
|
||||
private view: azdata.ModelView;
|
||||
|
||||
constructor(wizard: CreateClusterWizard) {
|
||||
super(localize('bdc-create.selectTargetClusterPageTitle', 'Where do you want to deploy this SQL Server big data cluster?'),
|
||||
localize('bdc-create.selectTargetClusterPageDescription', 'Select the kubeconfig file and then select a cluster context from the list'),
|
||||
wizard);
|
||||
}
|
||||
|
||||
protected initialize(view: azdata.ModelView): Thenable<void> {
|
||||
this.view = view;
|
||||
this.initExistingClusterControl();
|
||||
let formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
[
|
||||
{
|
||||
component: this.existingClusterControl,
|
||||
title: ''
|
||||
}
|
||||
],
|
||||
{
|
||||
horizontal: false
|
||||
}
|
||||
).withLayout({ width: '100%', height: '100%' });
|
||||
|
||||
let form = formBuilder.component();
|
||||
return view.initializeModel(form);
|
||||
}
|
||||
|
||||
public onEnter() {
|
||||
if (this.loadDefaultKubeConfigFile) {
|
||||
let defaultKubeConfigPath = this.wizard.model.getDefaultKubeConfigPath();
|
||||
if (fs.existsSync(defaultKubeConfigPath)) {
|
||||
this.loadClusterContexts(defaultKubeConfigPath);
|
||||
}
|
||||
this.loadDefaultKubeConfigFile = false;
|
||||
}
|
||||
|
||||
this.wizard.wizardObject.registerNavigationValidator((e) => {
|
||||
if (e.lastPage > e.newPage) {
|
||||
this.wizard.wizardObject.message = null;
|
||||
return true;
|
||||
}
|
||||
let clusterSelected = this.wizard.model.selectedCluster !== undefined;
|
||||
if (!clusterSelected) {
|
||||
this.wizard.wizardObject.message = {
|
||||
text: localize('bdc-create.ClusterContextNotSelectedMessage', 'Please select a cluster context.'),
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
}
|
||||
return clusterSelected;
|
||||
});
|
||||
}
|
||||
|
||||
private initExistingClusterControl(): void {
|
||||
let self = this;
|
||||
const labelWidth = '150px';
|
||||
let configFileLabel = this.view.modelBuilder.text().withProperties({ value: localize('bdc-create.kubeConfigFileLabelText', 'Kube config file path') }).component();
|
||||
configFileLabel.width = labelWidth;
|
||||
this.configFileInput = this.view.modelBuilder.inputBox().withProperties({ width: '300px' }).component();
|
||||
this.configFileInput.enabled = false;
|
||||
this.browseFileButton = this.view.modelBuilder.button().withProperties({ label: localize('bdc-browseText', 'Browse'), width: '100px' }).component();
|
||||
let configFileContainer = this.view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'row', alignItems: 'baseline' })
|
||||
.withItems([configFileLabel, this.configFileInput, this.browseFileButton], { CSSStyles: { 'margin-right': '10px' } }).component();
|
||||
this.clusterContextsLabel = this.view.modelBuilder.text().withProperties({ value: localize('bdc-clusterContextsLabelText', 'Cluster Contexts') }).component();
|
||||
this.clusterContextsLabel.width = labelWidth;
|
||||
this.errorLoadingClustersLabel = this.view.modelBuilder.text().withProperties({ value: localize('bdc-errorLoadingClustersText', 'No cluster information is found in the config file or an error ocurred while loading the config file') }).component();
|
||||
this.clusterContextList = this.view.modelBuilder.divContainer().component();
|
||||
this.clusterContextLoadingComponent = this.view.modelBuilder.loadingComponent().withItem(this.clusterContextList).component();
|
||||
this.existingClusterControl = this.view.modelBuilder.divContainer().component();
|
||||
let clusterContextContainer = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'start' }).component();
|
||||
clusterContextContainer.addItem(this.clusterContextsLabel, { flex: '0 0 auto' });
|
||||
clusterContextContainer.addItem(this.clusterContextLoadingComponent, { flex: '0 0 auto', CSSStyles: { 'width': '400px', 'margin-left': '10px', 'margin-top': '10px' } });
|
||||
|
||||
this.existingClusterControl.addItem(configFileContainer, { CSSStyles: { 'margin-top': '0px' } });
|
||||
this.existingClusterControl.addItem(clusterContextContainer, {
|
||||
CSSStyles: { 'margin- top': '10px' }
|
||||
});
|
||||
|
||||
this.wizard.registerDisposable(this.browseFileButton.onDidClick(async () => {
|
||||
let fileUris = await vscode.window.showOpenDialog(
|
||||
{
|
||||
canSelectFiles: true,
|
||||
canSelectFolders: false,
|
||||
canSelectMany: false,
|
||||
defaultUri: vscode.Uri.file(os.homedir()),
|
||||
openLabel: localize('bdc-selectKubeConfigFileText', 'Select'),
|
||||
filters: {
|
||||
'KubeConfig Files': ['*'],
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!fileUris || fileUris.length === 0) {
|
||||
return;
|
||||
}
|
||||
self.clusterContextList.clearItems();
|
||||
|
||||
let fileUri = fileUris[0];
|
||||
|
||||
self.loadClusterContexts(fileUri.fsPath);
|
||||
}));
|
||||
}
|
||||
|
||||
private async loadClusterContexts(configPath: string): Promise<void> {
|
||||
this.clusterContextLoadingComponent.loading = true;
|
||||
let self = this;
|
||||
this.configFileInput.value = configPath;
|
||||
await setActiveKubeconfig(configPath);
|
||||
|
||||
let clusters = await this.wizard.model.loadClusters();
|
||||
if (clusters.length !== 0) {
|
||||
let options = clusters.map(cluster => {
|
||||
let option = this.view.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({
|
||||
label: cluster.contextName,
|
||||
checked: cluster.active,
|
||||
name: ClusterRadioButtonGroupName
|
||||
}).component();
|
||||
|
||||
if (cluster.active) {
|
||||
self.wizard.model.selectedCluster = cluster;
|
||||
self.wizard.wizardObject.message = null;
|
||||
}
|
||||
|
||||
this.wizard.registerDisposable(option.onDidClick(() => {
|
||||
self.wizard.model.selectedCluster = cluster;
|
||||
self.wizard.wizardObject.message = null;
|
||||
}));
|
||||
return option;
|
||||
});
|
||||
self.clusterContextList.addItems(options);
|
||||
} else {
|
||||
self.clusterContextList.addItem(this.errorLoadingClustersLabel);
|
||||
}
|
||||
this.clusterContextLoadingComponent.loading = false;
|
||||
}
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { WizardPageBase } from '../../wizardPageBase';
|
||||
import { TargetClusterTypeInfo, ToolInstallationStatus, ToolInfo } from '../../../interfaces';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { CreateClusterWizard } from '../createClusterWizard';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
const InstallToolsButtonText = localize('bdc-create.InstallToolsText', 'Install Tools');
|
||||
const InstallingButtonText = localize('bdc-create.InstallingButtonText', 'Installing...');
|
||||
|
||||
export class SelectTargetClusterTypePage extends WizardPageBase<CreateClusterWizard> {
|
||||
private cards: azdata.CardComponent[];
|
||||
private toolsTable: azdata.TableComponent;
|
||||
private formBuilder: azdata.FormBuilder;
|
||||
private form: azdata.FormContainer;
|
||||
private installToolsButton: azdata.window.Button;
|
||||
private toolsLoadingWrapper: azdata.LoadingComponent;
|
||||
private refreshToolsButton: azdata.window.Button;
|
||||
private targetDescriptionText: azdata.TextComponent;
|
||||
private targetDescriptionGroup: azdata.FormComponent;
|
||||
private isValid: boolean = false;
|
||||
private isLoading: boolean = false;
|
||||
private requiredTools: ToolInfo[];
|
||||
|
||||
constructor(wizard: CreateClusterWizard) {
|
||||
super(localize('bdc-create.selectTargetClusterTypePageTitle', 'What is your target cluster environment?'),
|
||||
localize('bdc-create.selectTargetClusterTypePageDescription', 'Choose the target environment and then install the required tools for it.'),
|
||||
wizard);
|
||||
this.installToolsButton = azdata.window.createButton(InstallToolsButtonText);
|
||||
this.installToolsButton.hidden = true;
|
||||
this.wizard.registerDisposable(this.installToolsButton.onClick(async () => {
|
||||
this.wizard.wizardObject.message = null;
|
||||
this.installToolsButton.label = InstallingButtonText;
|
||||
this.installToolsButton.enabled = false;
|
||||
this.refreshToolsButton.enabled = false;
|
||||
if (this.requiredTools) {
|
||||
for (let i = 0; i < this.requiredTools.length; i++) {
|
||||
let tool = this.requiredTools[i];
|
||||
if (tool.status === ToolInstallationStatus.NotInstalled) {
|
||||
tool.status = ToolInstallationStatus.Installing;
|
||||
this.updateToolStatusTable();
|
||||
await this.wizard.model.installTool(tool);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.installToolsButton.label = InstallToolsButtonText;
|
||||
this.updateRequiredToolStatus();
|
||||
}));
|
||||
this.wizard.addButton(this.installToolsButton);
|
||||
|
||||
this.refreshToolsButton = azdata.window.createButton(localize('bdc-create.RefreshToolsButtonText', 'Refresh Status'));
|
||||
this.refreshToolsButton.hidden = true;
|
||||
this.wizard.registerDisposable(this.refreshToolsButton.onClick(() => {
|
||||
this.updateRequiredToolStatus();
|
||||
}));
|
||||
this.wizard.addButton(this.refreshToolsButton);
|
||||
}
|
||||
|
||||
protected initialize(view: azdata.ModelView): Thenable<void> {
|
||||
let self = this;
|
||||
self.registerNavigationValidator();
|
||||
return self.wizard.model.getAllTargetClusterTypeInfo().then((clusterTypes) => {
|
||||
self.cards = [];
|
||||
|
||||
clusterTypes.forEach(clusterType => {
|
||||
let card = self.createCard(view, clusterType);
|
||||
self.cards.push(card);
|
||||
});
|
||||
let cardsContainer = view.modelBuilder.flexContainer().withItems(self.cards, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '20px' } }).withLayout({ flexFlow: 'row', alignItems: 'left' }).component();
|
||||
|
||||
self.targetDescriptionText = view.modelBuilder.text().component();
|
||||
|
||||
let toolColumn: azdata.TableColumn = {
|
||||
value: localize('bdc-create.toolNameColumnHeader', 'Tool'),
|
||||
width: 100
|
||||
};
|
||||
let descriptionColumn: azdata.TableColumn = {
|
||||
value: localize('bdc-create.toolDescriptionColumnHeader', 'Description'),
|
||||
width: 500
|
||||
};
|
||||
let versionColumn: azdata.TableColumn = {
|
||||
value: localize('bdc-create.toolVersionColumnHeader', 'Version'),
|
||||
width: 200
|
||||
};
|
||||
let statusColumn: azdata.TableColumn = {
|
||||
value: localize('bdc-create.toolStatusColumnHeader', 'Status'),
|
||||
width: 200
|
||||
};
|
||||
|
||||
self.toolsTable = view.modelBuilder.table().withProperties<azdata.TableComponentProperties>({
|
||||
height: 150,
|
||||
data: [],
|
||||
columns: [toolColumn, descriptionColumn, versionColumn, statusColumn],
|
||||
width: 1000
|
||||
}).component();
|
||||
|
||||
self.toolsLoadingWrapper = view.modelBuilder.loadingComponent().withItem(self.toolsTable).component();
|
||||
self.formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
[
|
||||
{
|
||||
component: cardsContainer,
|
||||
title: localize('bdc-create.PickTargetEnvironmentText', 'Pick target environment')
|
||||
}
|
||||
],
|
||||
{
|
||||
horizontal: false
|
||||
}
|
||||
);
|
||||
|
||||
self.form = self.formBuilder.withLayout({ width: '100%' }).component();
|
||||
return view.initializeModel(self.form);
|
||||
});
|
||||
}
|
||||
|
||||
public onEnter(): void {
|
||||
this.installToolsButton.hidden = false;
|
||||
this.refreshToolsButton.hidden = false;
|
||||
this.refreshToolsButton.enabled = true;
|
||||
this.installToolsButton.enabled = false;
|
||||
this.registerNavigationValidator();
|
||||
}
|
||||
|
||||
private registerNavigationValidator(): void {
|
||||
this.wizard.wizardObject.registerNavigationValidator(() => {
|
||||
if (this.isLoading) {
|
||||
let messageText = localize('bdc-create.ToolsRefreshingText', 'Please wait while the required tools status is being refreshed.');
|
||||
let messageLevel = azdata.window.MessageLevel.Information;
|
||||
this.wizard.wizardObject.message = {
|
||||
level: messageLevel,
|
||||
text: messageText
|
||||
};
|
||||
return false;
|
||||
}
|
||||
if (!this.isValid) {
|
||||
let messageText = this.cards.filter(c => { return c.selected; }).length === 0 ?
|
||||
localize('bdc-create.TargetClusterTypeNotSelectedText', 'Please select a target cluster type.') :
|
||||
localize('bdc-create.MissingToolsText', 'Please install the required tools.');
|
||||
this.wizard.wizardObject.message = {
|
||||
level: azdata.window.MessageLevel.Error,
|
||||
text: messageText
|
||||
};
|
||||
}
|
||||
return this.isValid;
|
||||
});
|
||||
}
|
||||
|
||||
public onLeave(): void {
|
||||
this.installToolsButton.hidden = true;
|
||||
this.refreshToolsButton.hidden = true;
|
||||
}
|
||||
|
||||
private createCard(view: azdata.ModelView, targetClusterTypeInfo: TargetClusterTypeInfo): azdata.CardComponent {
|
||||
let self = this;
|
||||
let descriptions = targetClusterTypeInfo.enabled ? [] : [localize('bdc-create.ComingSoonText', '(Coming Soon)')];
|
||||
let card = view.modelBuilder.card().withProperties<azdata.CardProperties>({
|
||||
cardType: azdata.CardType.VerticalButton,
|
||||
iconPath: {
|
||||
dark: self.wizard.context.asAbsolutePath(targetClusterTypeInfo.iconPath.dark),
|
||||
light: self.wizard.context.asAbsolutePath(targetClusterTypeInfo.iconPath.light)
|
||||
},
|
||||
label: targetClusterTypeInfo.name,
|
||||
descriptions: descriptions
|
||||
}).component();
|
||||
|
||||
card.enabled = targetClusterTypeInfo.enabled;
|
||||
|
||||
self.wizard.registerDisposable(card.onCardSelectedChanged(() => {
|
||||
self.onCardSelected(card, targetClusterTypeInfo);
|
||||
}));
|
||||
return card;
|
||||
}
|
||||
|
||||
private onCardSelected(card: azdata.CardComponent, targetClusterTypeInfo: TargetClusterTypeInfo): void {
|
||||
let self = this;
|
||||
if (card.selected) {
|
||||
self.wizard.wizardObject.message = null;
|
||||
self.wizard.model.targetClusterType = targetClusterTypeInfo.type;
|
||||
self.cards.forEach(c => {
|
||||
if (c !== card) {
|
||||
c.selected = false;
|
||||
}
|
||||
});
|
||||
|
||||
self.targetDescriptionText.value = targetClusterTypeInfo.description;
|
||||
|
||||
if (self.form.items.length === 1) {
|
||||
self.formBuilder.addFormItem({
|
||||
title: localize('bdc-create.RequiredToolsText', 'Required tools'),
|
||||
component: self.toolsLoadingWrapper
|
||||
});
|
||||
} else {
|
||||
self.formBuilder.removeFormItem(self.targetDescriptionGroup);
|
||||
}
|
||||
|
||||
self.targetDescriptionGroup = {
|
||||
title: targetClusterTypeInfo.fullName,
|
||||
component: self.targetDescriptionText
|
||||
};
|
||||
self.formBuilder.insertFormItem(self.targetDescriptionGroup, 1);
|
||||
|
||||
self.updateRequiredToolStatus();
|
||||
} else {
|
||||
if (self.cards.filter(c => { return c !== card && c.selected; }).length === 0) {
|
||||
card.selected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateRequiredToolStatus(): Thenable<void> {
|
||||
this.isLoading = true;
|
||||
this.installToolsButton.hidden = false;
|
||||
this.refreshToolsButton.hidden = false;
|
||||
this.toolsLoadingWrapper.loading = true;
|
||||
this.refreshToolsButton.enabled = false;
|
||||
this.installToolsButton.enabled = false;
|
||||
return this.wizard.model.getRequiredToolStatus().then(tools => {
|
||||
this.requiredTools = tools;
|
||||
this.isLoading = false;
|
||||
this.toolsLoadingWrapper.loading = false;
|
||||
this.refreshToolsButton.enabled = true;
|
||||
this.installToolsButton.enabled = tools.filter(tool => tool.status !== ToolInstallationStatus.Installed).length !== 0;
|
||||
this.isValid = !this.installToolsButton.enabled;
|
||||
this.wizard.wizardObject.message = null;
|
||||
this.updateToolStatusTable();
|
||||
});
|
||||
}
|
||||
|
||||
private getStatusText(status: ToolInstallationStatus): string {
|
||||
switch (status) {
|
||||
case ToolInstallationStatus.Installed:
|
||||
return '✔️ ' + localize('bdc-create.InstalledText', 'Installed');
|
||||
case ToolInstallationStatus.NotInstalled:
|
||||
return '❌ ' + localize('bdc-create.NotInstalledText', 'Not Installed');
|
||||
case ToolInstallationStatus.Installing:
|
||||
return '⌛ ' + localize('bdc-create.InstallingText', 'Installing...');
|
||||
case ToolInstallationStatus.FailedToInstall:
|
||||
return '❌ ' + localize('bdc-create.FailedToInstallText', 'Install Failed');
|
||||
default:
|
||||
return 'unknown status';
|
||||
}
|
||||
}
|
||||
|
||||
private updateToolStatusTable(): void {
|
||||
if (this.requiredTools) {
|
||||
let tableData = this.requiredTools.map(tool => {
|
||||
return [tool.name, tool.description, tool.version, this.getStatusText(tool.status)];
|
||||
});
|
||||
this.toolsTable.data = tableData;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { WizardPageBase } from '../../wizardPageBase';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { ClusterPorts, ContainerRegistryInfo } from '../../../interfaces';
|
||||
import { CreateClusterWizard } from '../createClusterWizard';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
const UserNameInputWidth = '300px';
|
||||
const PortInputWidth = '100px';
|
||||
const RestoreDefaultValuesText = localize('bdc-create.RestoreDefaultValuesText', 'Restore Default Values');
|
||||
|
||||
export class SettingsPage extends WizardPageBase<CreateClusterWizard> {
|
||||
private acceptEulaCheckbox: azdata.CheckBoxComponent;
|
||||
|
||||
constructor(wizard: CreateClusterWizard) {
|
||||
super(localize('bdc-create.settingsPageTitle', 'Settings'),
|
||||
localize('bdc-create.settingsPageDescription', 'Configure the settings required for deploying SQL Server big data cluster'),
|
||||
wizard);
|
||||
}
|
||||
|
||||
public onEnter(): void {
|
||||
this.wizard.wizardObject.registerNavigationValidator((e) => {
|
||||
if (e.lastPage > e.newPage) {
|
||||
this.wizard.wizardObject.message = null;
|
||||
return true;
|
||||
}
|
||||
if (!this.acceptEulaCheckbox.checked) {
|
||||
this.wizard.wizardObject.message = {
|
||||
text: localize('bdc-create.EulaNotAccepted', 'You need to accept the terms of services and privacy policy in order to proceed'),
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
} else {
|
||||
this.wizard.wizardObject.message = null;
|
||||
}
|
||||
return this.acceptEulaCheckbox.checked;
|
||||
});
|
||||
}
|
||||
|
||||
protected initialize(view: azdata.ModelView): Thenable<void> {
|
||||
let clusterPorts: ClusterPorts;
|
||||
let containerRegistryInfo: ContainerRegistryInfo;
|
||||
|
||||
let clusterPortsPromise = this.wizard.model.getDefaultPorts().then(ports => {
|
||||
clusterPorts = ports;
|
||||
});
|
||||
|
||||
let containerRegistryPromise = this.wizard.model.getDefaultContainerRegistryInfo().then(containerRegistry => {
|
||||
containerRegistryInfo = containerRegistry;
|
||||
});
|
||||
return Promise.all([clusterPortsPromise, containerRegistryPromise]).then(() => {
|
||||
let formBuilder = view.modelBuilder.formContainer();
|
||||
|
||||
// User settings
|
||||
let clusterNameInput = this.createInputWithLabel(view, {
|
||||
label: localize('bdc-create.ClusterName', 'Cluster name'),
|
||||
inputWidth: UserNameInputWidth,
|
||||
isRequiredField: true
|
||||
}, (input) => {
|
||||
this.wizard.model.clusterName = input.value;
|
||||
});
|
||||
|
||||
let adminUserNameInput = this.createInputWithLabel(view, {
|
||||
label: localize('bdc-create.AdminUsernameText', 'Admin username'),
|
||||
isRequiredField: true,
|
||||
inputWidth: UserNameInputWidth
|
||||
}, (inputBox: azdata.InputBoxComponent) => {
|
||||
this.wizard.model.adminUserName = inputBox.value;
|
||||
});
|
||||
let adminPasswordInput = this.createInputWithLabel(view, {
|
||||
label: localize('bdc-create.AdminUserPasswordText', 'Password'),
|
||||
isRequiredField: true,
|
||||
inputType: 'password',
|
||||
inputWidth: UserNameInputWidth
|
||||
}, (inputBox: azdata.InputBoxComponent) => {
|
||||
this.wizard.model.adminPassword = inputBox.value;
|
||||
});
|
||||
|
||||
// Port settings
|
||||
let sqlPortInput = this.createInputWithLabel(view, {
|
||||
label: localize('bdc-create.SQLPortText', 'SQL Server master'),
|
||||
isRequiredField: true,
|
||||
inputWidth: PortInputWidth,
|
||||
initialValue: clusterPorts.sql
|
||||
}, (inputBox: azdata.InputBoxComponent) => {
|
||||
this.wizard.model.sqlPort = inputBox.value;
|
||||
});
|
||||
let knoxPortInput = this.createInputWithLabel(view, {
|
||||
label: localize('bdc-create.KnoxPortText', 'Knox'),
|
||||
isRequiredField: true,
|
||||
inputWidth: PortInputWidth,
|
||||
initialValue: clusterPorts.knox
|
||||
}, (inputBox: azdata.InputBoxComponent) => {
|
||||
this.wizard.model.knoxPort = inputBox.value;
|
||||
});
|
||||
let controllerPortInput = this.createInputWithLabel(view, {
|
||||
label: localize('bdc-create.ControllerPortText', 'Controller'),
|
||||
isRequiredField: true,
|
||||
inputWidth: PortInputWidth,
|
||||
initialValue: clusterPorts.controller
|
||||
}, (inputBox: azdata.InputBoxComponent) => {
|
||||
this.wizard.model.controllerPort = inputBox.value;
|
||||
});
|
||||
let proxyPortInput = this.createInputWithLabel(view, {
|
||||
label: localize('bdc-create.ProxyPortText', 'Proxy'),
|
||||
isRequiredField: true,
|
||||
inputWidth: PortInputWidth,
|
||||
initialValue: clusterPorts.proxy
|
||||
}, (inputBox: azdata.InputBoxComponent) => {
|
||||
this.wizard.model.proxyPort = inputBox.value;
|
||||
});
|
||||
let grafanaPortInput = this.createInputWithLabel(view, {
|
||||
label: localize('bdc-create.GrafanaPortText', 'Grafana dashboard'),
|
||||
isRequiredField: true,
|
||||
inputWidth: PortInputWidth,
|
||||
initialValue: clusterPorts.grafana
|
||||
}, (inputBox: azdata.InputBoxComponent) => {
|
||||
this.wizard.model.grafanaPort = inputBox.value;
|
||||
});
|
||||
let kibanaPortInput = this.createInputWithLabel(view, {
|
||||
label: localize('bdc-create.KibanaPortText', 'Kibana dashboard'),
|
||||
isRequiredField: true,
|
||||
inputWidth: PortInputWidth,
|
||||
initialValue: clusterPorts.kibana
|
||||
}, (inputBox: azdata.InputBoxComponent) => {
|
||||
this.wizard.model.kibanaPort = inputBox.value;
|
||||
});
|
||||
let restorePortSettingsButton = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
label: RestoreDefaultValuesText,
|
||||
width: 200
|
||||
}).component();
|
||||
this.wizard.registerDisposable(restorePortSettingsButton.onDidClick(() => {
|
||||
sqlPortInput.input.value = clusterPorts.sql;
|
||||
knoxPortInput.input.value = clusterPorts.knox;
|
||||
controllerPortInput.input.value = clusterPorts.controller;
|
||||
proxyPortInput.input.value = clusterPorts.proxy;
|
||||
grafanaPortInput.input.value = clusterPorts.grafana;
|
||||
kibanaPortInput.input.value = clusterPorts.kibana;
|
||||
}));
|
||||
|
||||
// Container Registry Settings
|
||||
const registryUserNamePasswordHintText = localize('bdc-create.RegistryUserNamePasswordHintText', 'only required for private registries');
|
||||
let registryInput = this.createInputWithLabel(view, {
|
||||
label: localize('bdc-create.RegistryText', 'Registry'),
|
||||
isRequiredField: true,
|
||||
inputWidth: UserNameInputWidth,
|
||||
initialValue: containerRegistryInfo.registry
|
||||
}, (inputBox: azdata.InputBoxComponent) => {
|
||||
this.wizard.model.containerRegistry = inputBox.value;
|
||||
});
|
||||
|
||||
let repositoryInput = this.createInputWithLabel(view, {
|
||||
label: localize('bdc-create.RepositoryText', 'Repository'),
|
||||
isRequiredField: true,
|
||||
inputWidth: UserNameInputWidth,
|
||||
initialValue: containerRegistryInfo.repository
|
||||
}, (inputBox: azdata.InputBoxComponent) => {
|
||||
this.wizard.model.containerRepository = inputBox.value;
|
||||
});
|
||||
|
||||
let imageTagInput = this.createInputWithLabel(view, {
|
||||
label: localize('bdc-create.ImageTagText', 'Image tag'),
|
||||
isRequiredField: true,
|
||||
inputWidth: UserNameInputWidth,
|
||||
initialValue: containerRegistryInfo.imageTag
|
||||
}, (inputBox: azdata.InputBoxComponent) => {
|
||||
this.wizard.model.containerRegistry = inputBox.value;
|
||||
});
|
||||
|
||||
let registryUserNameInput = this.createInputWithLabel(view, {
|
||||
label: localize('bdc-create.RegistryUserNameText', 'Username'),
|
||||
isRequiredField: false,
|
||||
inputWidth: UserNameInputWidth,
|
||||
placeHolder: registryUserNamePasswordHintText
|
||||
}, (inputBox: azdata.InputBoxComponent) => {
|
||||
this.wizard.model.containerRegistryUserName = inputBox.value;
|
||||
});
|
||||
|
||||
let registryPasswordInput = this.createInputWithLabel(view, {
|
||||
label: localize('bdc-create.RegistryPasswordText', 'Password'),
|
||||
isRequiredField: false,
|
||||
inputWidth: UserNameInputWidth,
|
||||
placeHolder: registryUserNamePasswordHintText,
|
||||
inputType: 'password'
|
||||
}, (inputBox: azdata.InputBoxComponent) => {
|
||||
this.wizard.model.containerRegistryPassword = inputBox.value;
|
||||
});
|
||||
let restoreContainerSettingsButton = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
label: RestoreDefaultValuesText,
|
||||
width: 200
|
||||
}).component();
|
||||
this.wizard.registerDisposable(restoreContainerSettingsButton.onDidClick(() => {
|
||||
registryInput.input.value = containerRegistryInfo.registry;
|
||||
repositoryInput.input.value = containerRegistryInfo.repository;
|
||||
imageTagInput.input.value = containerRegistryInfo.imageTag;
|
||||
}));
|
||||
|
||||
let basicSettingsGroup = view.modelBuilder.groupContainer().withItems([clusterNameInput.row, adminUserNameInput.row, adminPasswordInput.row]).withLayout({ header: localize('bdc-create.BasicSettingsText', 'Basic Settings'), collapsible: true }).component();
|
||||
let containerSettingsGroup = view.modelBuilder.groupContainer().withItems([registryInput.row, repositoryInput.row, imageTagInput.row, registryUserNameInput.row, registryPasswordInput.row, restoreContainerSettingsButton]).withLayout({ header: localize('bdc-create.ContainerRegistrySettings', 'Container Registry Settings'), collapsible: true }).component();
|
||||
let portSettingsGroup = view.modelBuilder.groupContainer().withItems([sqlPortInput.row, knoxPortInput.row, controllerPortInput.row, proxyPortInput.row, grafanaPortInput.row, kibanaPortInput.row, restorePortSettingsButton]).withLayout({ header: localize('bdc-create.PortSettings', 'Port Settings (Optional)'), collapsible: true, collapsed: true }).component();
|
||||
|
||||
this.acceptEulaCheckbox = view.modelBuilder.checkBox().component();
|
||||
this.acceptEulaCheckbox.checked = false;
|
||||
|
||||
let eulaLink: azdata.LinkArea = {
|
||||
text: localize('bdc-create.LicenseTerms', 'license terms'),
|
||||
url: 'https://go.microsoft.com/fwlink/?LinkId=2002534'
|
||||
};
|
||||
let privacyPolicyLink: azdata.LinkArea = {
|
||||
text: localize('bdc-create.PrivacyPolicyText', 'privacy policy'),
|
||||
url: 'https://go.microsoft.com/fwlink/?LinkId=853010'
|
||||
};
|
||||
|
||||
let checkboxText = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: localize({
|
||||
key: 'bdc-create.AcceptTermsText',
|
||||
comment: ['{0} is the place holder for license terms, {1} is the place holder for privacy policy']
|
||||
}, 'I accept the {0} and {1}.'),
|
||||
links: [eulaLink, privacyPolicyLink]
|
||||
}).component();
|
||||
|
||||
let eulaContainer = this.createRow(view, [this.acceptEulaCheckbox, checkboxText]);
|
||||
|
||||
let form = formBuilder.withFormItems([
|
||||
{
|
||||
title: '',
|
||||
component: eulaContainer
|
||||
}, {
|
||||
title: '',
|
||||
component: basicSettingsGroup
|
||||
}, {
|
||||
title: '',
|
||||
component: containerSettingsGroup
|
||||
}, {
|
||||
title: '',
|
||||
component: portSettingsGroup
|
||||
}]).component();
|
||||
return view.initializeModel(form);
|
||||
});
|
||||
}
|
||||
|
||||
private createInputWithLabel(view: azdata.ModelView, options: {
|
||||
label: string,
|
||||
isRequiredField: boolean,
|
||||
inputWidth: string,
|
||||
inputType?: string,
|
||||
initialValue?: string,
|
||||
placeHolder?: string
|
||||
}, textChangedHandler: (inputBox: azdata.InputBoxComponent) => void): { row: azdata.FlexContainer, input: azdata.InputBoxComponent } {
|
||||
let inputType = !!options.inputType ? options.inputType : 'text';
|
||||
let input = view.modelBuilder.inputBox().withProperties({
|
||||
required: options.isRequiredField,
|
||||
inputType: inputType
|
||||
}).component();
|
||||
let text = view.modelBuilder.text().withProperties({ value: options.label }).component();
|
||||
input.width = options.inputWidth;
|
||||
text.width = '150px';
|
||||
input.placeHolder = options.placeHolder;
|
||||
this.wizard.registerDisposable(input.onTextChanged(() => {
|
||||
textChangedHandler(input);
|
||||
}));
|
||||
input.value = options.initialValue;
|
||||
let row = this.createRow(view, [text, input]);
|
||||
return {
|
||||
input: input,
|
||||
row: row
|
||||
};
|
||||
}
|
||||
|
||||
private createRow(view: azdata.ModelView, items: azdata.Component[]): azdata.FlexContainer {
|
||||
return view.modelBuilder.flexContainer().withItems(items, { CSSStyles: { 'margin-right': '5px' } }).withLayout({ flexFlow: 'row', alignItems: 'center' }).component();
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { WizardPageBase } from '../../wizardPageBase';
|
||||
import { CreateClusterWizard } from '../createClusterWizard';
|
||||
import * as nls from 'vscode-nls';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
const LabelWidth = '250px';
|
||||
|
||||
export class SummaryPage extends WizardPageBase<CreateClusterWizard> {
|
||||
private view: azdata.ModelView;
|
||||
private targetTypeText: azdata.TextComponent;
|
||||
private targetClusterContextText: azdata.TextComponent;
|
||||
private clusterNameText: azdata.TextComponent;
|
||||
private clusterAdminUsernameText: azdata.TextComponent;
|
||||
private acceptEulaText: azdata.TextComponent;
|
||||
private deploymentProfileText: azdata.TextComponent;
|
||||
private sqlServerMasterScaleText: azdata.TextComponent;
|
||||
private storagePoolScaleText: azdata.TextComponent;
|
||||
private computePoolScaleText: azdata.TextComponent;
|
||||
private dataPoolScaleText: azdata.TextComponent;
|
||||
private sparkPoolScaleText: azdata.TextComponent;
|
||||
|
||||
constructor(wizard: CreateClusterWizard) {
|
||||
super(localize('bdc-create.summaryPageTitle', 'Summary'), '', wizard);
|
||||
}
|
||||
|
||||
protected initialize(view: azdata.ModelView): Thenable<void> {
|
||||
this.view = view;
|
||||
let targetClusterInfoGroup = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
||||
let bdcClusterInfoGroup = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
||||
this.targetTypeText = this.view.modelBuilder.text().component();
|
||||
this.targetClusterContextText = this.view.modelBuilder.text().component();
|
||||
this.clusterNameText = this.view.modelBuilder.text().component();
|
||||
this.clusterAdminUsernameText = this.view.modelBuilder.text().component();
|
||||
this.acceptEulaText = this.view.modelBuilder.text().component();
|
||||
this.deploymentProfileText = this.view.modelBuilder.text().component();
|
||||
this.sqlServerMasterScaleText = this.view.modelBuilder.text().component();
|
||||
this.storagePoolScaleText = this.view.modelBuilder.text().component();
|
||||
this.computePoolScaleText = this.view.modelBuilder.text().component();
|
||||
this.dataPoolScaleText = this.view.modelBuilder.text().component();
|
||||
this.sparkPoolScaleText = this.view.modelBuilder.text().component();
|
||||
targetClusterInfoGroup.addItem(this.createRow(localize('bdc-create.TargetClusterTypeText', 'Cluster type'), this.targetTypeText));
|
||||
targetClusterInfoGroup.addItem(this.createRow(localize('bdc-create.ClusterContextText', 'Cluster context'), this.targetClusterContextText));
|
||||
|
||||
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.ClusterNameText', 'Cluster name'), this.clusterNameText));
|
||||
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.ClusterAdminUsernameText', 'Cluster Admin username'), this.clusterAdminUsernameText));
|
||||
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.AcceptEulaText', 'Accept license agreement'), this.acceptEulaText));
|
||||
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.DeploymentProfileText', 'Deployment profile'), this.deploymentProfileText));
|
||||
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.SqlServerMasterScaleText', 'SQL Server master scale'), this.sqlServerMasterScaleText));
|
||||
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.ComputePoolScaleText', 'Compute pool scale'), this.computePoolScaleText));
|
||||
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.DataPoolScaleText', 'Data pool scale'), this.dataPoolScaleText));
|
||||
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.StoragePoolScaleText', 'Storage pool scale'), this.storagePoolScaleText));
|
||||
bdcClusterInfoGroup.addItem(this.createRow(localize('bdc-create.SparkPoolScaleText', 'Spark pool scale'), this.sparkPoolScaleText));
|
||||
|
||||
let formBuilder = view.modelBuilder.formContainer();
|
||||
let form = formBuilder.withFormItems([{
|
||||
title: localize('bdc-create.TargetClusterGroupTitle', 'TARGET CLUSTER'),
|
||||
component: targetClusterInfoGroup
|
||||
}, {
|
||||
title: localize('bdc-create.BigDataClusterGroupTitle', 'SQL SERVER BIG DATA CLUSTER'),
|
||||
component: bdcClusterInfoGroup
|
||||
}]).component();
|
||||
|
||||
return view.initializeModel(form);
|
||||
}
|
||||
|
||||
public onEnter(): void {
|
||||
this.wizard.model.getAllTargetClusterTypeInfo().then((clusterTypes) => {
|
||||
let selectedClusterType = clusterTypes.filter(clusterType => clusterType.type === this.wizard.model.targetClusterType)[0];
|
||||
this.targetTypeText.value = selectedClusterType.fullName;
|
||||
this.targetClusterContextText.value = this.wizard.model.selectedCluster.contextName;
|
||||
this.clusterNameText.value = this.wizard.model.clusterName;
|
||||
this.clusterAdminUsernameText.value = this.wizard.model.adminUserName;
|
||||
this.acceptEulaText.value = localize('bdc-create.YesText', 'Yes');
|
||||
this.deploymentProfileText.value = this.wizard.model.profile.name;
|
||||
this.sqlServerMasterScaleText.value = this.wizard.model.profile.sqlServerMasterConfiguration.scale.toString();
|
||||
this.computePoolScaleText.value = this.wizard.model.profile.computePoolConfiguration.scale.toString();
|
||||
this.dataPoolScaleText.value = this.wizard.model.profile.dataPoolConfiguration.scale.toString();
|
||||
this.storagePoolScaleText.value = this.wizard.model.profile.storagePoolConfiguration.scale.toString();
|
||||
this.sparkPoolScaleText.value = this.wizard.model.profile.sparkPoolConfiguration.scale.toString();
|
||||
|
||||
});
|
||||
this.wizard.wizardObject.generateScriptButton.hidden = false;
|
||||
}
|
||||
|
||||
public onLeave(): void {
|
||||
this.wizard.wizardObject.generateScriptButton.hidden = true;
|
||||
}
|
||||
|
||||
private createRow(label: string, textComponent: azdata.TextComponent): azdata.FlexContainer {
|
||||
let row = this.view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', alignItems: 'baseline' }).component();
|
||||
let labelComponent = this.view.modelBuilder.text().withProperties({ value: label }).component();
|
||||
labelComponent.width = LabelWidth;
|
||||
textComponent.width = LabelWidth;
|
||||
row.addItems([labelComponent, textComponent]);
|
||||
return row;
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { ExtensionContext, Disposable } from 'vscode';
|
||||
import { WizardPageBase } from './wizardPageBase';
|
||||
|
||||
export abstract class WizardBase<T, W> {
|
||||
|
||||
public wizardObject: azdata.window.Wizard;
|
||||
private customButtons: azdata.window.Button[];
|
||||
private pages: WizardPageBase<W>[];
|
||||
|
||||
private toDispose: Disposable[] = [];
|
||||
|
||||
constructor(public model: T, public context: ExtensionContext, private title: string) {
|
||||
this.customButtons = [];
|
||||
}
|
||||
|
||||
public open(): Thenable<void> {
|
||||
this.wizardObject = azdata.window.createWizard(this.title);
|
||||
this.initialize();
|
||||
this.wizardObject.customButtons = this.customButtons;
|
||||
this.toDispose.push(this.wizardObject.onPageChanged((e) => {
|
||||
let previousPage = this.pages[e.lastPage];
|
||||
let newPage = this.pages[e.newPage];
|
||||
previousPage.onLeave();
|
||||
newPage.onEnter();
|
||||
}));
|
||||
|
||||
this.toDispose.push(this.wizardObject.doneButton.onClick(() => {
|
||||
this.onOk();
|
||||
this.dispose();
|
||||
}));
|
||||
this.toDispose.push(this.wizardObject.cancelButton.onClick(() => {
|
||||
this.onCancel();
|
||||
this.dispose();
|
||||
}));
|
||||
|
||||
return this.wizardObject.open().then(() => {
|
||||
if (this.pages && this.pages.length > 0) {
|
||||
this.pages[0].onEnter();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
protected abstract initialize(): void;
|
||||
protected abstract onOk(): void;
|
||||
protected abstract onCancel(): void;
|
||||
|
||||
public addButton(button: azdata.window.Button) {
|
||||
this.customButtons.push(button);
|
||||
}
|
||||
|
||||
protected setPages(pages: WizardPageBase<W>[]) {
|
||||
this.wizardObject.pages = pages.map(p => p.pageObject);
|
||||
this.pages = pages;
|
||||
}
|
||||
|
||||
private dispose() {
|
||||
this.toDispose.forEach((disposable: Disposable) => {
|
||||
try {
|
||||
disposable.dispose();
|
||||
}
|
||||
catch{ }
|
||||
});
|
||||
}
|
||||
|
||||
public registerDisposable(disposable: Disposable): void {
|
||||
this.toDispose.push(disposable);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
|
||||
export abstract class WizardPageBase<T> {
|
||||
private _page: azdata.window.WizardPage;
|
||||
|
||||
public get pageObject(): azdata.window.WizardPage {
|
||||
return this._page;
|
||||
}
|
||||
|
||||
public get wizard(): T {
|
||||
return this._wizard;
|
||||
}
|
||||
|
||||
constructor(title: string, description: string, private _wizard: T) {
|
||||
this._page = azdata.window.createWizardPage(title);
|
||||
this._page.description = description;
|
||||
this._page.registerContent((view: azdata.ModelView) => {
|
||||
return this.initialize(view);
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract initialize(view: azdata.ModelView): Thenable<void>;
|
||||
|
||||
public onEnter(): void { }
|
||||
|
||||
public onLeave(): void { }
|
||||
}
|
||||
@@ -1,15 +1,23 @@
|
||||
{
|
||||
"extends": "../shared.tsconfig.json",
|
||||
"compileOnSave": true,
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"outDir": "./out",
|
||||
"strict": false,
|
||||
"alwaysStrict": false,
|
||||
"noImplicitAny": false,
|
||||
"noImplicitReturns": false,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false
|
||||
"lib": [
|
||||
"es6",
|
||||
"es2015.promise"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"declaration": false,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
}
|
||||