├── .gitignore ├── logo.png ├── media ├── css │ ├── theme.css │ ├── theme.dark.css │ ├── bootstrap-reboot.min.css │ ├── bootstrap-reboot.rtl.min.css │ ├── bootstrap-reboot.rtl.css │ └── bootstrap-reboot.css ├── images │ ├── ps-logo.png │ ├── psu-jobs.png │ ├── psu-rest.png │ ├── psu-hosting.png │ ├── psu-modules.png │ ├── psu-community.png │ ├── psu-dashboard.png │ ├── psu-security.png │ └── psu-development.png ├── logo.svg ├── js │ └── fontawesome.js └── welcome.html ├── images ├── apis.png ├── config.png ├── connect.png ├── scripts.png ├── splash.png ├── dashboards.png └── connections.png ├── CHANGELOG.md ├── .vscodeignore ├── .vscode ├── extensions.json ├── tasks.json ├── settings.json └── launch.json ├── .hintrc ├── src ├── commands │ ├── utils.ts │ ├── walkthrough.ts │ ├── welcomeCommand.ts │ ├── helpCommand.ts │ ├── connect.ts │ ├── modules.ts │ ├── terminals.ts │ ├── debugger.ts │ ├── config.ts │ ├── endpoints.ts │ ├── localDev.ts │ ├── scripts.ts │ └── dashboards.ts ├── parentTreeItem.ts ├── test │ ├── suite │ │ ├── extension.test.ts │ │ └── index.ts │ └── runTest.ts ├── Universal.VSCode.psm1 ├── container.ts ├── info-treeview.ts ├── job-tracker.ts ├── api-treeview.ts ├── settings.ts ├── connection-treeview.ts ├── configuration-treeview.ts ├── types.ts ├── webviews │ └── welcome.ts ├── platform-treeview.ts ├── extension.ts ├── automation-treeview.ts └── dashboard-treeview.ts ├── walkthroughs └── getting-started │ ├── 2-login.md │ ├── 3-connect.md │ └── 1-install.md ├── .eslintrc.json ├── .github └── workflows │ ├── ci.yaml │ └── production.yaml ├── tsconfig.json ├── LICENSE ├── vscode.build.ps1 ├── README.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | kit -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-code/HEAD/logo.png -------------------------------------------------------------------------------- /media/css/theme.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #cacecb; 3 | color: #2d2d2d; 4 | } -------------------------------------------------------------------------------- /images/apis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-code/HEAD/images/apis.png -------------------------------------------------------------------------------- /media/css/theme.dark.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #262626; 3 | color: #cacecb; 4 | } -------------------------------------------------------------------------------- /images/config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-code/HEAD/images/config.png -------------------------------------------------------------------------------- /images/connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-code/HEAD/images/connect.png -------------------------------------------------------------------------------- /images/scripts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-code/HEAD/images/scripts.png -------------------------------------------------------------------------------- /images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-code/HEAD/images/splash.png -------------------------------------------------------------------------------- /images/dashboards.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-code/HEAD/images/dashboards.png -------------------------------------------------------------------------------- /images/connections.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-code/HEAD/images/connections.png -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | [Changelog](https://docs.powershelluniversal.com/changelogs/extension-changelog) -------------------------------------------------------------------------------- /media/images/ps-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-code/HEAD/media/images/ps-logo.png -------------------------------------------------------------------------------- /media/images/psu-jobs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-code/HEAD/media/images/psu-jobs.png -------------------------------------------------------------------------------- /media/images/psu-rest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-code/HEAD/media/images/psu-rest.png -------------------------------------------------------------------------------- /media/images/psu-hosting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-code/HEAD/media/images/psu-hosting.png -------------------------------------------------------------------------------- /media/images/psu-modules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-code/HEAD/media/images/psu-modules.png -------------------------------------------------------------------------------- /media/images/psu-community.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-code/HEAD/media/images/psu-community.png -------------------------------------------------------------------------------- /media/images/psu-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-code/HEAD/media/images/psu-dashboard.png -------------------------------------------------------------------------------- /media/images/psu-security.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-code/HEAD/media/images/psu-security.png -------------------------------------------------------------------------------- /media/images/psu-development.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironmansoftware/universal-code/HEAD/media/images/psu-development.png -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | .gitignore 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/.eslintrc.json 9 | **/*.map 10 | **/*.ts 11 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.hintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "development" 4 | ], 5 | "hints": { 6 | "axe/text-alternatives": [ 7 | "default", 8 | { 9 | "document-title": "off" 10 | } 11 | ], 12 | "meta-viewport": "off", 13 | "axe/language": "off" 14 | } 15 | } -------------------------------------------------------------------------------- /src/commands/utils.ts: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | const path = require('path'); 3 | 4 | export function tmpdir() { 5 | if (os.platform() === 'win32') { 6 | return path.join(os.userInfo().homedir, "AppData", "Local", "Temp"); 7 | } else { 8 | return os.tmpdir(); 9 | } 10 | } -------------------------------------------------------------------------------- /src/parentTreeItem.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export default abstract class ParentTreeItem extends vscode.TreeItem { 4 | constructor(label : string , state : vscode.TreeItemCollapsibleState ) { 5 | super(label, state) 6 | } 7 | 8 | abstract getChildren(): Thenable 9 | } -------------------------------------------------------------------------------- /walkthroughs/getting-started/2-login.md: -------------------------------------------------------------------------------- 1 | ## Login to PowerShell Universal 2 | 3 | Once you have installed PowerShell Universal, it will be listening at `http://localhost:5000`. You will have to set your default user name and password and then can login. 4 | 5 | [💡 Login to PowerShell Universal](http://localhost:5000) -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/commands/walkthrough.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export const registerWalkthroughCommands = (context: vscode.ExtensionContext) => { 4 | vscode.commands.registerCommand('powershell-universal.walkthrough', Welcome); 5 | } 6 | 7 | export const Welcome = async (context: vscode.ExtensionContext) => { 8 | vscode.commands.executeCommand('workbench.action.openWalkthrough', { 9 | category: 'ironmansoftware.powershell-universal#universal.welcome' 10 | }) 11 | } -------------------------------------------------------------------------------- /walkthroughs/getting-started/3-connect.md: -------------------------------------------------------------------------------- 1 | ## Connect Visual Studio Code 2 | 3 | The PowerShell Universal Visual Studio Code extension requires connection information to login to your PowerShell Universal instance. Once logged into PowerShell Universal, click Settings \ Files and then Edit with VS Code. You'll be prompted and VS Code will be configured automatically. 4 | 5 |

6 | PowerShell Universal 7 |

8 | 9 | [💡 Settings \ Files](http://localhost:5000/admin/settings/files) -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import * as vscode from 'vscode'; 6 | // import * as myExtension from '../../extension'; 7 | 8 | suite('Extension Test Suite', () => { 9 | vscode.window.showInformationMessage('Start all tests.'); 10 | 11 | test('Sample test', () => { 12 | assert.equal(-1, [1, 2, 3].indexOf(5)); 13 | assert.equal(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/class-name-casing": "warn", 13 | "@typescript-eslint/semi": "warn", 14 | "curly": "warn", 15 | "eqeqeq": "warn", 16 | "no-throw-literal": "warn", 17 | "semi": "off", 18 | "no-undef": "off" 19 | } 20 | } -------------------------------------------------------------------------------- /src/commands/welcomeCommand.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import WelcomePanel from '../webviews/welcome'; 3 | 4 | export const registerWelcomeCommands = (context: vscode.ExtensionContext) => { 5 | vscode.commands.registerCommand('powershell-universal.welcome', Welcome); 6 | } 7 | 8 | export const Welcome = async (context: vscode.ExtensionContext) => { 9 | var extension = vscode.extensions.getExtension('ironmansoftware.powershell-universal'); 10 | if (extension) { 11 | WelcomePanel.createOrShow(extension.extensionUri) 12 | } 13 | } -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request, workflow_dispatch] 3 | 4 | jobs: 5 | build: 6 | name: Build 7 | runs-on: windows-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | - name: InvokeBuild 11 | run: Install-Module InvokeBuild -Scope CurrentUser -Force 12 | shell: powershell 13 | - name: Build 14 | run: Invoke-Build 15 | shell: powershell 16 | - uses: actions/upload-artifact@v4 17 | with: 18 | name: vsix 19 | path: kit/*.vsix -------------------------------------------------------------------------------- /walkthroughs/getting-started/1-install.md: -------------------------------------------------------------------------------- 1 | ## Install PowerShell Universal 2 | 3 |

4 | PowerShell Universal 5 |

6 | 7 | 8 | PowerShell Universal is a single pane of glass for managing your automation environment. The server is cross-platform. You can install it by using PowerShell, using a Windows MSI, with Chocolatey, or run as a Docker container. 9 | 10 | [Download](https://powershelluniversal.com/downloads) | [💡Learn More About Installing PowerShell Universal](https://docs.powershelluniversal.com/getting-started) -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off", 11 | "workspace.isHidden": false, 12 | "powershell.codeFormatting.addWhitespaceAroundPipe": true 13 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true 12 | /* Additional Checks */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | }, 17 | "exclude": [ 18 | "node_modules", 19 | ".vscode-test" 20 | ] 21 | } -------------------------------------------------------------------------------- /.github/workflows/production.yaml: -------------------------------------------------------------------------------- 1 | name: Production 2 | on: [workflow_dispatch] 3 | 4 | jobs: 5 | build: 6 | name: Build and Publish 7 | runs-on: windows-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | - name: InvokeBuild 11 | run: Install-Module InvokeBuild -Scope CurrentUser -Force 12 | shell: powershell 13 | - name: Build 14 | run: Invoke-Build 15 | shell: powershell 16 | - name: Publish 17 | run: Invoke-Build -Task PublishExtension 18 | env: 19 | MarketplaceToken: ${{ secrets.MARKETPLACE_TOKEN }} 20 | shell: powershell -------------------------------------------------------------------------------- /media/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from 'vscode-test'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true 10 | }); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | console.error(err); 34 | e(err); 35 | } 36 | }); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2020 Ironman Software 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/commands/helpCommand.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | const help = () => { 4 | return vscode.commands.registerCommand('powershell-universal.help', async (item: vscode.TreeItem) => { 5 | 6 | let url = ''; 7 | switch (item.label) { 8 | case "Documentation": 9 | url = "https://docs.powershelluniversal.com"; 10 | break; 11 | case "Forums": 12 | url = "https://forums.ironmansoftware.com"; 13 | break; 14 | case "Issues": 15 | url = "https://github.com/ironmansoftware/powershell-universal"; 16 | break; 17 | case "Pricing": 18 | url = "https://powershelluniversal.com/pricing"; 19 | break; 20 | case "Gallery": 21 | url = "https://powershelluniversal.com/gallery"; 22 | break; 23 | } 24 | 25 | if (url !== '') { 26 | vscode.env.openExternal(vscode.Uri.parse(url)); 27 | } 28 | }); 29 | }; 30 | 31 | export default help; 32 | -------------------------------------------------------------------------------- /src/Universal.VSCode.psm1: -------------------------------------------------------------------------------- 1 | function Install-UniversalModule { 2 | param($Version) 3 | 4 | if ($Version -contains "beta") { 5 | Write-Warning "This feature is not supported for beta versions" 6 | return 7 | } 8 | 9 | $Parameters = @{ 10 | Name = "Universal" 11 | RequiredVersion = $Version 12 | } 13 | 14 | $Universal = Import-Module @Parameters -ErrorAction SilentlyContinue -PassThru 15 | if ($null -eq $Universal) { 16 | Install-Module @Parameters -Scope CurrentUser -Force -AllowClobber -ErrorAction SilentlyContinue 17 | Import-Module @Parameters -ErrorAction SilentlyContinue 18 | } 19 | } 20 | 21 | function Import-LocalDevelopmentModule { 22 | param($Version, $Port) 23 | 24 | $ModulePath = [IO.Path]::Combine($ENV:USERPROFILE, ".psu", $Version, "Modules", "Universal", "Universal.psd1") 25 | Import-Module $ModulePath 26 | 27 | Connect-PSUServer -Url "http://localhost:$Port" -Credential (New-Object System.Management.Automation.PSCredential("admin", (ConvertTo-SecureString "admin" -AsPlainText -Force))) 28 | } -------------------------------------------------------------------------------- /src/commands/connect.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { ConnectionTreeItem } from '../connection-treeview'; 3 | import { Container } from '../container'; 4 | import { load } from '../settings'; 5 | 6 | export const registerConnectCommands = (context: vscode.ExtensionContext) => { 7 | vscode.commands.registerCommand('powershell-universal.addConnection', AddConnection); 8 | vscode.commands.registerCommand('powershell-universal.connection', x => Connection(x, context)); 9 | vscode.commands.registerCommand('powershell-universal.reconnect', x => Connection(x, context)); 10 | } 11 | 12 | export const AddConnection = async (context: vscode.ExtensionContext) => { 13 | vscode.commands.executeCommand('workbench.action.openSettings', "PowerShell Universal"); 14 | } 15 | 16 | export const Connection = async (treeItem: ConnectionTreeItem, context: vscode.ExtensionContext) => { 17 | const name = treeItem.connection.name; 18 | context.globalState.update("universal.connection", name); 19 | vscode.commands.executeCommand('powershell-universal.refreshAllTreeViews'); 20 | 21 | await Container.universal.installAndLoadModule(); 22 | } -------------------------------------------------------------------------------- /vscode.build.ps1: -------------------------------------------------------------------------------- 1 | task BuildExtension { 2 | & { 3 | $ErrorActionPreference = 'SilentlyContinue' 4 | npm install -g npm 5 | npm install -g typescript@latest 6 | npm install -g vsce 7 | npm install 8 | 9 | $HttpClient = Get-Content "$PSScriptRoot\node_modules\@microsoft\signalr\dist\esm\HttpClient.d.ts" -Raw 10 | "// @ts-nocheck`r`n$HttpClient" | Out-File "$PSScriptRoot\node_modules\@microsoft\signalr\dist\esm\HttpClient.d.ts" -Force 11 | 12 | Remove-Item (Join-Path $PSScriptRoot "out") -Force -Recurse -ErrorAction SilentlyContinue 13 | New-Item (Join-Path $PSScriptRoot "out") -ItemType Directory 14 | Remove-Item (Join-Path $PSScriptRoot "kit") -Force -Recurse -ErrorAction SilentlyContinue 15 | New-Item (Join-Path $PSScriptRoot "kit") -ItemType Directory 16 | Copy-Item "$PSScriptRoot\src\Universal.VSCode.psm1" "$PSScriptRoot\out" 17 | 18 | vsce package 19 | 20 | Copy-Item (Join-Path $PSScriptRoot "*.vsix") (Join-Path $PSScriptRoot "kit") 21 | } 22 | } 23 | 24 | task PublishExtension { 25 | $vsix = (Get-ChildItem "$PSScriptRoot\*.vsix").FullName 26 | vsce publish --packagePath $vsix -p $env:MarketplaceToken 27 | } 28 | 29 | task . BuildExtension -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "PowerShell Attach to Host Process", 10 | "type": "PowerShell", 11 | "request": "attach", 12 | "processId": 29216, 13 | "runspaceId": 3 14 | }, 15 | { 16 | "name": "Run Extension", 17 | "type": "extensionHost", 18 | "request": "launch", 19 | "runtimeExecutable": "${execPath}", 20 | "args": [ 21 | "--extensionDevelopmentPath=${workspaceFolder}" 22 | ], 23 | "outFiles": [ 24 | "${workspaceFolder}/out/**/*.js" 25 | ], 26 | "preLaunchTask": "${defaultBuildTask}" 27 | }, 28 | { 29 | "name": "Extension Tests", 30 | "type": "extensionHost", 31 | "request": "launch", 32 | "runtimeExecutable": "${execPath}", 33 | "args": [ 34 | "--extensionDevelopmentPath=${workspaceFolder}", 35 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 36 | ], 37 | "outFiles": [ 38 | "${workspaceFolder}/out/test/**/*.js" 39 | ], 40 | "preLaunchTask": "${defaultBuildTask}" 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /src/container.ts: -------------------------------------------------------------------------------- 1 | import { Universal } from "./universal"; 2 | import { ExtensionContext, OutputChannel, window } from "vscode"; 3 | import { ConfigTreeViewProvider } from "./configuration-treeview"; 4 | 5 | export class Container { 6 | static initialize(context: ExtensionContext, universal: Universal) { 7 | this._context = context; 8 | this._universal = universal; 9 | } 10 | 11 | public static ConfigFileTreeView: ConfigTreeViewProvider; 12 | 13 | private static _connected: boolean; 14 | static get connected() { 15 | return this._connected; 16 | } 17 | 18 | static set connected(value: boolean) { 19 | this._connected = value; 20 | } 21 | 22 | private static _universal: Universal; 23 | static get universal() { 24 | return this._universal; 25 | } 26 | 27 | private static _context: ExtensionContext; 28 | static get context() { 29 | return this._context; 30 | } 31 | 32 | private static _outputPanels: Array = []; 33 | static getPanel(name: string) { 34 | let panel = this._outputPanels.find(panel => panel.name === name); 35 | if (!panel) { 36 | panel = window.createOutputChannel(name); 37 | this._outputPanels.push(panel); 38 | } 39 | return panel; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/info-treeview.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | class Node extends vscode.TreeItem { 4 | constructor(label: string, icon: string) { 5 | super(label); 6 | 7 | this.iconPath = new vscode.ThemeIcon(icon); 8 | this.contextValue = 'help'; 9 | this.command = { 10 | command: 'powershell-universal.help', 11 | arguments: [this], 12 | title: 'Help' 13 | } 14 | } 15 | } 16 | 17 | export class InfoTreeViewProvider implements vscode.TreeDataProvider { 18 | 19 | private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); 20 | readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; 21 | 22 | getTreeItem(element: vscode.TreeItem): vscode.TreeItem | Thenable { 23 | return element; 24 | } 25 | 26 | getChildren(element?: vscode.TreeItem | undefined): vscode.ProviderResult { 27 | if (element == null) { 28 | return [ 29 | new Node('Documentation', 'book'), 30 | new Node('Gallery', 'gift'), 31 | new Node('Forums', 'account'), 32 | new Node('Issues', 'github'), 33 | new Node('Pricing', 'key') 34 | ] 35 | } 36 | 37 | return null; 38 | } 39 | 40 | refresh(node?: vscode.TreeItem): void { 41 | this._onDidChangeTreeData.fire(node); 42 | } 43 | } -------------------------------------------------------------------------------- /src/job-tracker.ts: -------------------------------------------------------------------------------- 1 | import { Container } from "./container"; 2 | import { JobStatus } from "./types"; 3 | import * as vscode from 'vscode'; 4 | import { load } from './settings'; 5 | 6 | export const trackJob = (id: number) => { 7 | 8 | var lastStatus = JobStatus.Queued; 9 | 10 | const token = setInterval(async () => { 11 | const job = await Container.universal.getJob(id); 12 | 13 | var result: any = ''; 14 | if (job.status === JobStatus.Canceled) { 15 | clearInterval(token); 16 | result = await vscode.window.showWarningMessage(`Job ${id} canceled.`, "View Job"); 17 | } 18 | 19 | if (job.status === JobStatus.Failed) { 20 | clearInterval(token); 21 | result = await vscode.window.showErrorMessage(`Job ${id} failed.`, "View Job"); 22 | } 23 | 24 | if (job.status === JobStatus.Completed) { 25 | clearInterval(token); 26 | result = await vscode.window.showInformationMessage(`Job ${id} succeeded.`, "View Job"); 27 | } 28 | 29 | if (job.status === JobStatus.WaitingOnFeedback && lastStatus != JobStatus.WaitingOnFeedback) { 30 | result = await vscode.window.showInformationMessage(`Job ${id} is waiting on feedback.`, "View Job"); 31 | } 32 | 33 | if (result === "View Job") { 34 | const settings = load(); 35 | const connectionName = Container.context.globalState.get("universal.connection"); 36 | 37 | var url = settings.url; 38 | 39 | if (connectionName && connectionName !== 'Default') { 40 | const connection = settings.connections.find(m => m.name === connectionName); 41 | if (connection) { 42 | url = connection.url; 43 | } 44 | } 45 | 46 | vscode.env.openExternal(vscode.Uri.parse(`${url}/admin/automation/jobs/${id}`)); 47 | } 48 | 49 | lastStatus = job.status; 50 | }, 1000); 51 | } -------------------------------------------------------------------------------- /src/commands/modules.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { Container } from '../container'; 3 | const path = require('path'); 4 | import * as fs from 'fs'; 5 | import { tmpdir } from './utils'; 6 | import { CustomModule } from '../platform-treeview'; 7 | 8 | let files: Array = []; 9 | 10 | export const registerModuleCommands = (context: vscode.ExtensionContext) => { 11 | vscode.commands.registerCommand('powershell-universal.editModule', editModuleCommand); 12 | vscode.commands.registerCommand('powershell-universal.newModule', addModuleCommand); 13 | 14 | vscode.workspace.onDidSaveTextDocument(async (file) => { 15 | if (file.fileName.includes('.universal.code.modules')) { 16 | const info = files.find(x => x.filePath.toLowerCase() === file.fileName.toLowerCase()); 17 | Container.universal.getModule(info.id).then((module) => { 18 | module.content = file.getText(); 19 | Container.universal.updateModule(module); 20 | }); 21 | } 22 | }); 23 | }; 24 | 25 | export const addModuleCommand = async () => { 26 | const name = await vscode.window.showInputBox({ 27 | prompt: 'Enter the name of the module to add.' 28 | }); 29 | 30 | if (!name) { return; } 31 | 32 | await Container.universal.newModule(name); 33 | 34 | vscode.commands.executeCommand('powershell-universal.refreshPlatformTreeView'); 35 | }; 36 | 37 | export const editModuleCommand = async (node: CustomModule) => { 38 | const filePath = path.join(tmpdir(), '.universal.code.modules', `${node.module.id}.ps1`); 39 | const codePath = path.join(tmpdir(), '.universal.code.modules'); 40 | const config = await Container.universal.getModule(node.module.id); 41 | if (!fs.existsSync(codePath)) { 42 | fs.mkdirSync(codePath); 43 | } 44 | fs.writeFileSync(filePath, config.content); 45 | 46 | const textDocument = await vscode.workspace.openTextDocument(filePath); 47 | vscode.window.showTextDocument(textDocument); 48 | 49 | files.push({ 50 | id: node.module.id, 51 | filePath: filePath 52 | }); 53 | }; 54 | -------------------------------------------------------------------------------- /src/api-treeview.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { Container } from './container'; 3 | import { Endpoint } from './types'; 4 | import ParentTreeItem from './parentTreeItem'; 5 | 6 | export class ApiTreeViewProvider implements vscode.TreeDataProvider { 7 | 8 | private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); 9 | readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; 10 | 11 | getTreeItem(element: vscode.TreeItem): vscode.TreeItem | Thenable { 12 | return element; 13 | } 14 | 15 | async getChildren(element?: vscode.TreeItem | undefined) { 16 | if (element == null) { 17 | try { 18 | const endpoints = await Container.universal.getEndpoints(); 19 | if (!endpoints.map) return []; 20 | const items = endpoints.map(y => new EndpointTreeItem(y)); 21 | return items; 22 | } 23 | catch (ex) { 24 | Container.universal.showConnectionError("Failed to query API endpoints. " + ex); 25 | return []; 26 | } 27 | } 28 | 29 | if (element instanceof ParentTreeItem) { 30 | var parentTreeItem = element as ParentTreeItem; 31 | return parentTreeItem.getChildren(); 32 | } 33 | } 34 | 35 | refresh(node?: vscode.TreeItem): void { 36 | this._onDidChangeTreeData.fire(node); 37 | } 38 | } 39 | 40 | const formatMethod = (endpoint: Endpoint) => { 41 | if (Array.isArray(endpoint.method)) { 42 | return endpoint.method.map(m => m.toUpperCase()).join(","); 43 | } 44 | else { 45 | return endpoint.method.toUpperCase(); 46 | } 47 | } 48 | 49 | export class EndpointTreeItem extends vscode.TreeItem { 50 | public endpoint: Endpoint; 51 | 52 | constructor(endpoint: Endpoint) { 53 | super(`(${formatMethod(endpoint)}) ${endpoint.url}`, vscode.TreeItemCollapsibleState.None); 54 | 55 | this.endpoint = endpoint; 56 | const icon = endpoint.authentication ? "lock" : "unlock"; 57 | const themeIcon = new vscode.ThemeIcon(icon); 58 | this.iconPath = themeIcon; 59 | } 60 | 61 | contextValue = "endpoint"; 62 | } -------------------------------------------------------------------------------- /src/settings.ts: -------------------------------------------------------------------------------- 1 | import vscode = require("vscode"); 2 | export let PowerShellLanguageId = "powerShellUniversal"; 3 | 4 | export interface ISettings { 5 | appToken: string; 6 | url: string; 7 | localEditing: boolean; 8 | checkModules: boolean; 9 | connections: IConnection[]; 10 | } 11 | 12 | export interface IConnection { 13 | name: string; 14 | appToken?: string | null; 15 | url: string; 16 | allowInvalidCertificate?: boolean; 17 | windowsAuth?: boolean; 18 | } 19 | 20 | export function load(): ISettings { 21 | const configuration: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(PowerShellLanguageId); 22 | 23 | return { 24 | appToken: configuration.get("appToken", ""), 25 | url: configuration.get("url", "http://localhost:5000"), 26 | localEditing: configuration.get("localEditing", false), 27 | checkModules: configuration.get("checkModules", false), 28 | connections: configuration.get("connections", []), 29 | }; 30 | } 31 | 32 | export async function SetAppToken(value: string) { 33 | const configuration: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(PowerShellLanguageId); 34 | await configuration.update("appToken", value, vscode.ConfigurationTarget.Global); 35 | } 36 | 37 | export async function SetServerPath(value: string) { 38 | const configuration: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(PowerShellLanguageId); 39 | await configuration.update("serverPath", value, vscode.ConfigurationTarget.Global); 40 | } 41 | 42 | export async function SetUrl(value: string) { 43 | const configuration: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(PowerShellLanguageId); 44 | await configuration.update("url", value, vscode.ConfigurationTarget.Global); 45 | } 46 | 47 | export async function SetPort(value: number) { 48 | const configuration: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(PowerShellLanguageId); 49 | await configuration.update("port", value, vscode.ConfigurationTarget.Global); 50 | } 51 | 52 | export async function SetCheckModules(value: boolean) { 53 | const configuration: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(PowerShellLanguageId); 54 | await configuration.update("checkModules", value, vscode.ConfigurationTarget.Global); 55 | } -------------------------------------------------------------------------------- /src/commands/terminals.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { Container } from '../container'; 3 | import { TerminalTreeItem } from '../automation-treeview'; 4 | const path = require('path'); 5 | 6 | export const registerTerminalCommands = (context: vscode.ExtensionContext) => { 7 | vscode.commands.registerCommand('powershell-universal.openTerminal', openTerminalCommand); 8 | } 9 | 10 | 11 | export const openTerminalCommand = async (terminal: TerminalTreeItem, context: vscode.ExtensionContext) => { 12 | const writeEmitter = new vscode.EventEmitter(); 13 | 14 | var str = ''; 15 | var terminalInstanceId = 0; 16 | var readOnly = true; 17 | const pty: vscode.Pseudoterminal = { 18 | onDidWrite: writeEmitter.event, 19 | open: async () => { 20 | writeEmitter.fire(`Starting terminal...\r\n`); 21 | var terminalInstance = await Container.universal.newTerminalInstance(terminal.terminal); 22 | terminalInstanceId = terminalInstance.id; 23 | var output = await Container.universal.executeTerminalCommand(terminalInstanceId, 'prompt'); 24 | writeEmitter.fire(output.replace(/\r\n\r\n$/, '')); 25 | readOnly = false; 26 | }, 27 | close: () => { 28 | Container.universal.stopTerminalInstance(terminalInstanceId); 29 | }, 30 | handleInput: async data => { 31 | if (readOnly) { return }; 32 | 33 | if (data.charCodeAt(0) === 127) { 34 | str = str.slice(0, -1); 35 | writeEmitter.fire('\b \b'); 36 | return; 37 | } 38 | else { 39 | writeEmitter.fire(data); 40 | str += data; 41 | } 42 | 43 | if (data === '\r') { 44 | writeEmitter.fire('\r\n'); 45 | readOnly = true; 46 | var output = await Container.universal.executeTerminalCommand(terminalInstanceId, str); 47 | writeEmitter.fire(output); 48 | var output = await Container.universal.executeTerminalCommand(terminalInstanceId, 'prompt'); 49 | writeEmitter.fire(output.replace(/\r\n\r\n$/, '')); 50 | str = ''; 51 | readOnly = false; 52 | } 53 | } 54 | }; 55 | const terminalInstance = vscode.window.createTerminal({ name: `Terminal (${terminal.terminal.name})`, pty }); 56 | terminalInstance.show(); 57 | }; -------------------------------------------------------------------------------- /src/commands/debugger.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { DebugProtocol } from '@vscode/debugprotocol'; 3 | import { RunspaceTreeItem } from '../platform-treeview'; 4 | import { Container } from '../container'; 5 | 6 | export const registerDebuggerCommands = (context: vscode.ExtensionContext) => { 7 | vscode.commands.registerCommand('powershell-universal.attachRunspace', (item) => attachRunspace(item, context)); 8 | vscode.commands.registerCommand('powershell-universal.connectDebugger', _ => Container.universal.connectDebugger()); 9 | vscode.commands.registerCommand('powershell-universal.disconnectDebugger', _ => Container.universal.disconnectDebugger()); 10 | 11 | vscode.debug.registerDebugAdapterDescriptorFactory('powershelluniversal', { 12 | createDebugAdapterDescriptor: (_session) => { 13 | return new vscode.DebugAdapterInlineImplementation(new UniversalDebugAdapter()); 14 | } 15 | }); 16 | } 17 | 18 | 19 | export const attachRunspace = async (runspace: RunspaceTreeItem, context: vscode.ExtensionContext) => { 20 | await vscode.debug.startDebugging(undefined, { 21 | name: "PowerShell Universal", 22 | type: "powershelluniversal", 23 | request: "attach", 24 | processId: runspace.process.processId, 25 | runspaceId: runspace.runspace.runspaceId 26 | }); 27 | }; 28 | 29 | export class UniversalDebugAdapter implements vscode.DebugAdapter { 30 | constructor() { 31 | Container.universal.registerDebugAdapter(this); 32 | } 33 | 34 | private sendMessage = new vscode.EventEmitter(); 35 | 36 | readonly onDidSendMessage: vscode.Event = this.sendMessage.event; 37 | 38 | handleMessage(message: DebugProtocol.ProtocolMessage): void { 39 | switch (message.type) { 40 | case 'request': 41 | var request = message as DebugProtocol.Request; 42 | if (request.command === 'disconnect') { 43 | Container.universal.unregisterDebugAdapter(); 44 | } else { 45 | Container.universal.sendDebuggerMessage(request); 46 | } 47 | break; 48 | case 'response': 49 | this.sendMessage.fire(message); 50 | break; 51 | case 'event': 52 | this.sendMessage.fire(message); 53 | break; 54 | } 55 | } 56 | 57 | dispose() { 58 | Container.universal.unregisterDebugAdapter(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/connection-treeview.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { IConnection, load } from './settings'; 3 | import { LocalDevConfig } from './types'; 4 | 5 | export class ConnectionTreeViewProvider implements vscode.TreeDataProvider { 6 | 7 | private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); 8 | readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; 9 | 10 | private context: vscode.ExtensionContext; 11 | constructor(context: vscode.ExtensionContext) { 12 | this.context = context; 13 | } 14 | 15 | getTreeItem(element: vscode.TreeItem): vscode.TreeItem | Thenable { 16 | return element; 17 | } 18 | 19 | async getChildren(element?: vscode.TreeItem | undefined) { 20 | if (!element) { 21 | const settings = load(); 22 | const connectionName = this.context.globalState.get("universal.connection"); 23 | 24 | var items = settings.connections.map(m => new ConnectionTreeItem(m, m.name === connectionName)); 25 | if (settings.appToken && settings.appToken !== '') { 26 | items.push(new ConnectionTreeItem({ 27 | name: "Default", 28 | appToken: settings.appToken, 29 | url: settings.url, 30 | allowInvalidCertificate: false, 31 | windowsAuth: false 32 | }, !connectionName || connectionName === 'Default')); 33 | } 34 | 35 | var localDevConfig = this.context.globalState.get("psu.dev.config"); 36 | 37 | if (localDevConfig) { 38 | items.push(new ConnectionTreeItem({ 39 | name: "Local Development", 40 | url: `http://localhost:${localDevConfig.browserPort || 5000}`, 41 | }, !connectionName || connectionName === 'Local Development')); 42 | } 43 | 44 | return items; 45 | } 46 | } 47 | 48 | refresh(node?: vscode.TreeItem): void { 49 | this._onDidChangeTreeData.fire(node); 50 | } 51 | } 52 | 53 | export class ConnectionTreeItem extends vscode.TreeItem { 54 | public connection: IConnection; 55 | public connected: boolean; 56 | 57 | constructor(connection: IConnection, connected: boolean) { 58 | super(connection.name, vscode.TreeItemCollapsibleState.None); 59 | 60 | this.connection = connection; 61 | this.connected = connected; 62 | const themeIcon = this.connected ? new vscode.ThemeIcon("check") : new vscode.ThemeIcon("close"); 63 | this.iconPath = themeIcon; 64 | this.contextValue = this.connected ? "connection-connected" : "connection"; 65 | } 66 | } -------------------------------------------------------------------------------- /src/configuration-treeview.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { Container } from './container'; 3 | 4 | export class ConfigTreeViewProvider implements vscode.TreeDataProvider { 5 | 6 | private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); 7 | readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; 8 | 9 | getTreeItem(element: vscode.TreeItem): vscode.TreeItem | Thenable { 10 | return element; 11 | } 12 | 13 | async getChildren(element?: ConfigTreeItem | undefined) { 14 | if (element == null) { 15 | try { 16 | var version = await Container.universal.getVersion(); 17 | if (version.startsWith("3") || version.startsWith("4") || version.startsWith("5")) { 18 | const configs = await Container.universal.getFiles(""); 19 | var configTree: ConfigTreeItem[] = []; 20 | if (!configs.forEach) return []; 21 | configs.forEach(c => configTree.push(new ConfigTreeItem(c.name, c.fullName, c.isLeaf, c.content))); 22 | return configTree; 23 | } else { 24 | const configs = await Container.universal.getConfigurations(); 25 | var configTree: ConfigTreeItem[] = []; 26 | if (!configs.forEach) return []; 27 | configs.forEach(c => configTree.push(new ConfigTreeItem(c, c, false, ""))); 28 | return configTree; 29 | } 30 | 31 | } 32 | catch (err) { 33 | Container.universal.showConnectionError("Failed to query configuration files. " + err); 34 | return []; 35 | } 36 | } else { 37 | const configs = await Container.universal.getFiles(element.fileName); 38 | var configTree: ConfigTreeItem[] = []; 39 | if (!configs.forEach) return []; 40 | configs.forEach(c => configTree.push(new ConfigTreeItem(c.name, c.fullName, c.isLeaf, c.content))); 41 | return configTree; 42 | } 43 | } 44 | 45 | refresh(node?: vscode.TreeItem): void { 46 | this._onDidChangeTreeData.fire(node); 47 | } 48 | } 49 | 50 | export class ConfigTreeItem extends vscode.TreeItem { 51 | public fileName: string; 52 | public leaf: boolean; 53 | public content: string; 54 | 55 | constructor(name: string, fileName: string, leaf: boolean, content: string) { 56 | super(name, leaf ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed); 57 | 58 | this.description = fileName; 59 | 60 | this.fileName = fileName; 61 | const themeIcon = leaf ? new vscode.ThemeIcon('file-code') : new vscode.ThemeIcon('folder'); 62 | this.iconPath = themeIcon; 63 | this.leaf = leaf; 64 | this.content = content; 65 | this.contextValue = leaf ? "configFile" : "configFolder"; 66 | } 67 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowerShell Universal 2 | 3 | PowerShell Universal is a single of pane of glass to manage your automation environment. This extension helps in the development of tools using PowerShell Universal. 4 | 5 | - [Documentation](https://docs.powershelluniversal.com) 6 | - [Forums](https://forums.ironmansoftware.com) 7 | - [Pricing](https://powershelluniversal.com/pricing) 8 | 9 | ## Issues 10 | 11 | Please open issues on the [PowerShell Universal issue repository](https://github.com/ironmansoftware/powershell-universal). 12 | 13 | ## Features 14 | 15 | ### APIs 16 | 17 | ![PowerShell Universal APIs](https://github.com/ironmansoftware/universal-code/raw/master/images/apis.png) 18 | 19 | - View APIs 20 | - Automatically insert `Invoke-RestMethod` to call APIs 21 | - Edit APIs 22 | 23 | ### Apps 24 | 25 | ![PowerShell Universal Apps](https://github.com/ironmansoftware/universal-code/raw/master/images/dashboards.png) 26 | 27 | - View Apps 28 | - Open App scripts 29 | - Restart Apps 30 | - View App log 31 | - Debug App Process 32 | 33 | ### Scripts 34 | 35 | ![PowerShell Universal Scripts](https://github.com/ironmansoftware/universal-code/raw/master/images/scripts.png) 36 | 37 | - View scripts 38 | - Edit scripts 39 | - Run scripts and receive notifications on job status 40 | 41 | ### Configuration 42 | 43 | ![PowerShell Universal Configuration](https://github.com/ironmansoftware/universal-code/raw/master/images/config.png) 44 | 45 | - Edit configuration scripts 46 | 47 | ## Requirements 48 | 49 | - Windows, Linux or Mac 50 | - PowerShell v5.1 or later 51 | - Modern Web Browser 52 | 53 | ## Extension Configuration 54 | 55 | This extension requires an app token generated in PowerShell Universal before using it. You can do so by navigating to your PowerShell Universal admin console and logging in. 56 | 57 | Next, click your user name in the top right corner and select Tokens. Create a new token with the role you wish to grant to the token. Tokens with the Administrator role will have access to all features of the platform. Once created, copy the contents of the token. 58 | 59 | Within Visual Studio Code, open the command palette (Ctrl+Shift+P) and type "Preferences: Open Settings (UI)". Search for PowerShell Universal and fill in the following values: 60 | 61 | - App Token - The contents of the token you created in PowerShell Universal. Use the Administrator role to provide full access. 62 | - URL - The URL to your PowerShell Universal server (e.g. http://localhost:5000) 63 | 64 | Once connected, click the PowerShell Universal icon in the Activity Bar on the left side of the window. You can now start using the extension. 65 | 66 | For more information, visit the [PowerShell Universal documentation](https://docs.powershelluniversal.com/development/visual-studio-code-extension). 67 | 68 | ## Extension Settings 69 | 70 | This extension contributes the following settings: 71 | 72 | * `powershellUniversal.appToken`: An app token for communicating with the Universal REST API. An app token will be granted the first time the extension starts up. 73 | * `powershellUniversal.url`: The URL to your PowerShell Universal server. 74 | * `powershellUniversal.localEditing`: Whether to edit local configuration files or using the REST API 75 | * `powershellUniversal.connections`: An array of connections. 76 | * `powershellUniversal.checkModules`: Ensure that the latest version of the PowerShell Universal module is installed. 77 | -------------------------------------------------------------------------------- /src/commands/config.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { ConfigTreeItem } from './../configuration-treeview'; 3 | import { Container } from '../container'; 4 | const path = require('path'); 5 | import * as fs from 'fs'; // In NodeJS: 'const fs = require('fs')' 6 | import { load } from '../settings'; 7 | import { tmpdir } from './utils'; 8 | 9 | 10 | export const registerConfigCommands = (context: vscode.ExtensionContext) => { 11 | vscode.commands.registerCommand('powershell-universal.openConfigFile', openConfigCommand); 12 | vscode.commands.registerCommand('powershell-universal.newConfigFile', newConfigFileCommand); 13 | vscode.commands.registerCommand('powershell-universal.newConfigFolder', newConfigFolderCommand); 14 | vscode.commands.registerCommand('powershell-universal.reloadConfig', refreshConfig); 15 | vscode.workspace.onDidSaveTextDocument(async (file) => { 16 | if (file.fileName.includes('.universal.code.configuration')) { 17 | const codePath = path.join(tmpdir(), '.universal.code.configuration'); 18 | const fileName = file.fileName.toLocaleLowerCase().replace(codePath.toLocaleLowerCase(), "").substring(1); 19 | await Container.universal.saveFileContent(fileName, file.getText()); 20 | } 21 | }); 22 | }; 23 | 24 | export const newConfigFileCommand = async (item: ConfigTreeItem) => { 25 | const fileName = await vscode.window.showInputBox({ 26 | prompt: "Enter a file name" 27 | }); 28 | 29 | await Container.universal.newFile(item.fileName + "/" + fileName); 30 | Container.ConfigFileTreeView.refresh(); 31 | } 32 | 33 | export const newConfigFolderCommand = async (item: ConfigTreeItem) => { 34 | const fileName = await vscode.window.showInputBox({ 35 | prompt: "Enter a folder name" 36 | }); 37 | 38 | await Container.universal.newFolder(item.fileName + "/" + fileName); 39 | Container.ConfigFileTreeView.refresh(); 40 | } 41 | 42 | export const openConfigCommand = async (item: ConfigTreeItem) => { 43 | var settings = load(); 44 | if (settings.localEditing) { 45 | await openConfigLocal(item); 46 | } 47 | else { 48 | await openConfigRemote(item); 49 | } 50 | } 51 | 52 | export const openConfigRemote = async (item: ConfigTreeItem) => { 53 | const filePath = path.join(tmpdir(), '.universal.code.configuration', item.fileName); 54 | const codePath = path.join(tmpdir(), '.universal.code.configuration'); 55 | if (!fs.existsSync(codePath)) { 56 | fs.mkdirSync(codePath); 57 | } 58 | 59 | const config = await Container.universal.getFileContent(item.fileName); 60 | 61 | const directory = path.dirname(filePath); 62 | if (!fs.existsSync(directory)) { 63 | fs.mkdirSync(directory, { recursive: true }); 64 | } 65 | 66 | fs.writeFileSync(filePath, config.content); 67 | 68 | const textDocument = await vscode.workspace.openTextDocument(filePath); 69 | 70 | vscode.window.showTextDocument(textDocument); 71 | 72 | return textDocument; 73 | } 74 | 75 | export const openConfigLocal = async (item: ConfigTreeItem) => { 76 | const settings = await Container.universal.getSettings(); 77 | const filePath = path.join(settings.repositoryPath, '.universal', item.fileName); 78 | 79 | if (!fs.existsSync(filePath)) { 80 | fs.writeFileSync(filePath, ''); 81 | } 82 | 83 | const textDocument = await vscode.workspace.openTextDocument(filePath); 84 | 85 | vscode.window.showTextDocument(textDocument); 86 | } 87 | 88 | 89 | 90 | export const refreshConfig = async () => { 91 | try { 92 | await Container.universal.refreshConfig(); 93 | } catch (error) { 94 | vscode.window.showErrorMessage(error as string); 95 | return; 96 | } 97 | 98 | vscode.window.showInformationMessage("Configuration reloaded."); 99 | } 100 | 101 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type Dashboard = { 2 | id: number; 3 | name: string; 4 | baseUrl: string; 5 | status: DashboardStatus; 6 | processId: number; 7 | filePath: string; 8 | content: string; 9 | moduleContent: string; 10 | } 11 | 12 | export type DashboardPage = { 13 | modelId: number; 14 | name: string; 15 | url?: string; 16 | content?: string; 17 | dashboardId: number; 18 | } 19 | 20 | 21 | export type DashboardLog = { 22 | log: string; 23 | } 24 | 25 | export type DashboardLogItem = { 26 | Data: string; 27 | Timestamp: string; 28 | } 29 | 30 | export enum DashboardStatus { 31 | Stopped, 32 | Started, 33 | StartFailed, 34 | Starting, 35 | Debugging 36 | } 37 | 38 | 39 | export type DashboardEndpoint = { 40 | id: string; 41 | } 42 | 43 | export type DashboardSession = { 44 | id: string; 45 | lastTouched: string; 46 | userName: string; 47 | endpoints: Array; 48 | pages: Array; 49 | } 50 | 51 | export type DashboardDiagnostics = { 52 | memory: number; 53 | cpu: number; 54 | sessions: Array; 55 | endpoints: Array; 56 | } 57 | 58 | export type Endpoint = { 59 | id: number; 60 | url: string; 61 | method: string | Array; 62 | authentication: boolean; 63 | scriptBlock: string; 64 | } 65 | 66 | export type Job = { 67 | id: number; 68 | status: JobStatus; 69 | script: Script; 70 | scriptFullPath: string; 71 | } 72 | 73 | export type JobLog = { 74 | log: string; 75 | } 76 | 77 | export type JobPagedViewModel = { 78 | page: Array; 79 | } 80 | 81 | export enum JobStatus { 82 | Queued, 83 | Running, 84 | Completed, 85 | Failed, 86 | WaitingOnFeedback, 87 | Canceled, 88 | Canceling, 89 | Historical, 90 | Active 91 | } 92 | 93 | export interface Identity { 94 | name: string; 95 | } 96 | 97 | export interface Module { 98 | id: number; 99 | name: string; 100 | version: string; 101 | source: ModuleSource; 102 | extension: boolean; 103 | readOnly: boolean; 104 | content: string; 105 | } 106 | 107 | export enum ModuleSource { 108 | Local, 109 | Gallery 110 | } 111 | 112 | export type Repository = { 113 | name: string; 114 | url: string; 115 | }; 116 | 117 | export type Folder = { 118 | id: number; 119 | name: string; 120 | path: string; 121 | }; 122 | 123 | export type Script = { 124 | id: number; 125 | name: string; 126 | fullPath: string; 127 | content: string; 128 | }; 129 | 130 | export type ScriptParameter = { 131 | id: number; 132 | }; 133 | 134 | export type Settings = { 135 | repositoryPath: string; 136 | } 137 | 138 | export interface Terminal { 139 | name: string; 140 | description: string; 141 | environment: string; 142 | } 143 | 144 | export interface TerminalInstance { 145 | id: number; 146 | processId: number; 147 | identity: Identity; 148 | status: TerminalStatus; 149 | } 150 | 151 | export enum TerminalStatus { 152 | Connecting, 153 | Connected, 154 | Idle, 155 | Terminated 156 | } 157 | 158 | export type FileSystemItem = { 159 | name: string; 160 | fullName: string; 161 | items: Array; 162 | isLeaf: boolean; 163 | content: string; 164 | } 165 | 166 | export type Process = { 167 | id: number; 168 | processName: string; 169 | description: string; 170 | processId: number; 171 | environment: Environment; 172 | computer: string; 173 | }; 174 | 175 | export type Environment = { 176 | name: string; 177 | description: string; 178 | }; 179 | 180 | export type Runspace = { 181 | id: number; 182 | runspaceId: number; 183 | state: string; 184 | availability: string; 185 | processId: number; 186 | } 187 | 188 | export type LocalDevConfig = { 189 | version: string; 190 | databaseType: string | null; 191 | databaseConnectionString: string | null; 192 | env?: { [key: string]: string | null }; 193 | browserPort?: number; 194 | }; 195 | 196 | export type XMLHttpRequestResponseType = {} -------------------------------------------------------------------------------- /src/webviews/welcome.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | function getWebviewOptions(extensionUri: vscode.Uri): vscode.WebviewOptions { 4 | return { 5 | // Enable javascript in the webview 6 | enableScripts: true, 7 | 8 | // And restrict the webview to only loading content from our extension's `media` directory. 9 | localResourceRoots: [vscode.Uri.joinPath(extensionUri, 'media')] 10 | }; 11 | } 12 | 13 | export default class WelcomePanel { 14 | /** 15 | * Track the currently panel. Only allow a single panel to exist at a time. 16 | */ 17 | public static currentPanel: WelcomePanel | undefined; 18 | 19 | public static readonly viewType = 'universalWelcome'; 20 | 21 | private readonly _panel: vscode.WebviewPanel; 22 | private readonly _extensionUri: vscode.Uri; 23 | private _disposables: vscode.Disposable[] = []; 24 | 25 | public static createOrShow(extensionUri: vscode.Uri) { 26 | const column = vscode.window.activeTextEditor 27 | ? vscode.window.activeTextEditor.viewColumn 28 | : undefined; 29 | 30 | // If we already have a panel, show it. 31 | if (WelcomePanel.currentPanel) { 32 | WelcomePanel.currentPanel._panel.reveal(column); 33 | return; 34 | } 35 | 36 | // Otherwise, create a new panel. 37 | const panel = vscode.window.createWebviewPanel( 38 | WelcomePanel.viewType, 39 | 'PowerShell Universal - Welcome', 40 | column || vscode.ViewColumn.One, 41 | getWebviewOptions(extensionUri), 42 | ); 43 | 44 | WelcomePanel.currentPanel = new WelcomePanel(panel, extensionUri); 45 | } 46 | 47 | public static revive(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) { 48 | WelcomePanel.currentPanel = new WelcomePanel(panel, extensionUri); 49 | } 50 | 51 | private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) { 52 | this._panel = panel; 53 | this._extensionUri = extensionUri; 54 | 55 | const bootstrapCss = panel.webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media', 'css', 'bootstrap.min.css')); 56 | const js = panel.webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media', 'js')); 57 | const images = panel.webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media', 'images')); 58 | 59 | // Set the webview's initial html content 60 | const htmlPath = vscode.Uri.joinPath(this._extensionUri, 'media', 'welcome.html'); 61 | var fs = require('fs'); 62 | var content = fs.readFileSync(htmlPath.fsPath, 'utf8'); 63 | 64 | const format = (a: string, args: string[]) => { 65 | for (var k in args) { 66 | a = a.replace(new RegExp("\\{" + k + "\\}", 'g'), args[k]); 67 | } 68 | return a 69 | } 70 | 71 | panel.webview.onDidReceiveMessage( 72 | message => { 73 | switch (message) { 74 | case 'walkthrough': 75 | vscode.commands.executeCommand('powershell-universal.walkthrough') 76 | return; 77 | } 78 | }, 79 | undefined 80 | ); 81 | 82 | let theme = panel.webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media', 'css', 'theme.css')); 83 | if (vscode.window.activeColorTheme.kind == vscode.ColorThemeKind.Dark) { 84 | theme = panel.webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'media', 'css', 'theme.dark.css')); 85 | } 86 | 87 | panel.webview.html = format(content, [bootstrapCss.toString(), js.toString(), images.toString(), theme.toString()]); 88 | 89 | // Listen for when the panel is disposed 90 | // This happens when the user closes the panel or when the panel is closed programmatically 91 | this._panel.onDidDispose(() => this.dispose(), null, this._disposables); 92 | } 93 | 94 | public dispose() { 95 | WelcomePanel.currentPanel = undefined; 96 | 97 | // Clean up our resources 98 | this._panel.dispose(); 99 | 100 | while (this._disposables.length) { 101 | const x = this._disposables.pop(); 102 | if (x) { 103 | x.dispose(); 104 | } 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /src/commands/endpoints.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { load } from './../settings'; 3 | import { EndpointTreeItem } from './../api-treeview'; 4 | import { Container } from '../container'; 5 | const path = require('path'); 6 | import * as fs from 'fs'; 7 | import { tmpdir } from './utils'; 8 | 9 | let files: Array = []; 10 | 11 | export const registerEndpointCommands = (context: vscode.ExtensionContext) => { 12 | vscode.commands.registerCommand('powershell-universal.openEndpointScriptBlock', openEndpointScriptBlockCommand); 13 | vscode.commands.registerCommand('powershell-universal.openEndpointConfigFile', openEndpointConfigFileCommand); 14 | vscode.commands.registerCommand('powershell-universal.insertRestMethod', (item) => insertInvokeRestMethodCommand(item, context)); 15 | vscode.commands.registerCommand('powershell-universal.manageEndpoints', () => manageEndpointsCommand(context)); 16 | 17 | vscode.workspace.onDidSaveTextDocument(async (file) => { 18 | if (file.fileName.includes('.universal.code.endpoints')) { 19 | const info = files.find(x => x.filePath.toLowerCase() === file.fileName.toLowerCase()); 20 | Container.universal.getEndpoint(info).then((endpoint) => { 21 | endpoint.scriptBlock = file.getText(); 22 | Container.universal.saveEndpoint(endpoint); 23 | }); 24 | } 25 | }); 26 | 27 | vscode.workspace.onDidCloseTextDocument((file) => { 28 | files = files.filter(x => x.filePath !== file.fileName); 29 | }); 30 | } 31 | 32 | export const openEndpointScriptBlockCommand = async (node: EndpointTreeItem) => { 33 | var settings = load(); 34 | if (settings.localEditing) { 35 | vscode.window.showErrorMessage('Local editing is not supported for this command'); 36 | } 37 | else { 38 | const os = require('os'); 39 | 40 | const filePath = path.join(tmpdir(), '.universal.code.endpoints', `${node.endpoint.id}.ps1`); 41 | const codePath = path.join(tmpdir(), '.universal.code.endpoints'); 42 | const config = await Container.universal.getEndpoint(node.endpoint); 43 | if (!fs.existsSync(codePath)) { 44 | fs.mkdirSync(codePath); 45 | } 46 | fs.writeFileSync(filePath, config.scriptBlock); 47 | 48 | const textDocument = await vscode.workspace.openTextDocument(filePath); 49 | vscode.window.showTextDocument(textDocument); 50 | 51 | files.push({ 52 | id: node.endpoint.id, 53 | filePath: filePath 54 | }); 55 | } 56 | 57 | } 58 | 59 | export const openEndpointConfigFileCommand = async () => { 60 | var settings = load(); 61 | if (settings.localEditing) { 62 | const psuSettings = await Container.universal.getSettings(); 63 | const filePath = path.join(psuSettings.repositoryPath, '.universal', 'endpoints.ps1'); 64 | const textDocument = await vscode.workspace.openTextDocument(filePath); 65 | vscode.window.showTextDocument(textDocument); 66 | } 67 | else { 68 | const os = require('os'); 69 | 70 | const filePath = path.join(tmpdir(), '.universal.code.configuration', 'endpoints.ps1'); 71 | const codePath = path.join(tmpdir(), '.universal.code.configuration'); 72 | const config = await Container.universal.getConfiguration('endpoints.ps1'); 73 | if (!fs.existsSync(codePath)) { 74 | fs.mkdirSync(codePath); 75 | } 76 | fs.writeFileSync(filePath, config); 77 | 78 | const textDocument = await vscode.workspace.openTextDocument(filePath); 79 | vscode.window.showTextDocument(textDocument); 80 | } 81 | 82 | } 83 | 84 | export const insertInvokeRestMethodCommand = async (endpoint: EndpointTreeItem, context: vscode.ExtensionContext) => { 85 | 86 | const settings = load(); 87 | 88 | const connectionName = context.globalState.get("universal.connection"); 89 | var url = settings.url; 90 | 91 | if (connectionName && connectionName !== 'Default') { 92 | const connection = settings.connections.find(m => m.name === connectionName); 93 | if (connection) { 94 | url = connection.url; 95 | } 96 | } 97 | 98 | var terminal = vscode.window.terminals.find(x => x.name === "PowerShell Extension"); 99 | 100 | terminal?.sendText(`Invoke-RestMethod -Uri "${url}${endpoint.endpoint.url.replace(':', '$')}" -Method ${endpoint.endpoint.method}`, false); 101 | } 102 | 103 | export const manageEndpointsCommand = async (context: vscode.ExtensionContext) => { 104 | const settings = load(); 105 | 106 | const connectionName = context.globalState.get("universal.connection"); 107 | var url = settings.url; 108 | 109 | if (connectionName && connectionName !== 'Default') { 110 | const connection = settings.connections.find(m => m.name === connectionName); 111 | if (connection) { 112 | url = connection.url; 113 | } 114 | } 115 | 116 | vscode.env.openExternal(vscode.Uri.parse(`${url}/admin/apis/endpoints`)); 117 | } -------------------------------------------------------------------------------- /media/css/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v5.1.3 (https://getbootstrap.com/) 3 | * Copyright 2011-2021 The Bootstrap Authors 4 | * Copyright 2011-2021 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-body-color-rgb:33,37,41;--bs-body-bg-rgb:255,255,255;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-bg:#fff}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::-webkit-file-upload-button{font:inherit}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important} 8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /media/css/bootstrap-reboot.rtl.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v5.1.3 (https://getbootstrap.com/) 3 | * Copyright 2011-2021 The Bootstrap Authors 4 | * Copyright 2011-2021 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-body-color-rgb:33,37,41;--bs-body-bg-rgb:255,255,255;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-bg:#fff}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-right:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-right:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:right}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:right;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:right}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}[type=email],[type=number],[type=tel],[type=url]{direction:ltr}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::-webkit-file-upload-button{font:inherit}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important} 8 | /*# sourceMappingURL=bootstrap-reboot.rtl.min.css.map */ -------------------------------------------------------------------------------- /src/platform-treeview.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { IConnection, load } from './settings'; 3 | import ParentTreeItem from './parentTreeItem'; 4 | import { Container } from './container'; 5 | import { Module, Process, Runspace } from './types'; 6 | 7 | export class PlatformTreeViewProvider implements vscode.TreeDataProvider { 8 | 9 | private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); 10 | readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; 11 | 12 | getTreeItem(element: vscode.TreeItem): vscode.TreeItem | Thenable { 13 | return element; 14 | } 15 | 16 | async getChildren(element?: vscode.TreeItem | undefined) { 17 | if (element == null) { 18 | return [ 19 | new ModulesTreeItem(), 20 | new ProcessesTreeItem() 21 | ] 22 | } 23 | 24 | if (element instanceof ParentTreeItem) { 25 | var parentTreeItem = element as ParentTreeItem; 26 | return parentTreeItem.getChildren(); 27 | } 28 | } 29 | 30 | refresh(node?: vscode.TreeItem): void { 31 | this._onDidChangeTreeData.fire(node); 32 | } 33 | } 34 | 35 | export class ProcessesTreeItem extends ParentTreeItem { 36 | async getChildren(): Promise { 37 | try { 38 | const processes = await Container.universal.getProcesses(); 39 | return processes.map(x => new ProcessTreeItem(x)); 40 | } 41 | catch (err) { 42 | Container.universal.showConnectionError("Failed to query modules. " + err); 43 | return []; 44 | } 45 | } 46 | constructor() { 47 | super("Processes", vscode.TreeItemCollapsibleState.Collapsed); 48 | 49 | const themeIcon = new vscode.ThemeIcon("server-process"); 50 | this.iconPath = themeIcon; 51 | } 52 | } 53 | 54 | export class ProcessTreeItem extends ParentTreeItem { 55 | public process: Process; 56 | async getChildren(): Promise { 57 | try { 58 | const runspaces = await Container.universal.getRunspaces(this.process.id); 59 | return runspaces.map(x => new RunspaceTreeItem(x, this.process)); 60 | } 61 | catch (err) { 62 | Container.universal.showConnectionError("Failed to query modules. " + err); 63 | return []; 64 | } 65 | } 66 | constructor(process: Process) { 67 | super(`${process.description} (${process.computer} - ${process.processId})`, vscode.TreeItemCollapsibleState.Collapsed); 68 | 69 | this.process = process; 70 | 71 | const themeIcon = new vscode.ThemeIcon("server-process"); 72 | this.iconPath = themeIcon; 73 | } 74 | } 75 | 76 | 77 | export class RunspaceTreeItem extends vscode.TreeItem { 78 | public runspace: Runspace; 79 | public process: Process; 80 | constructor(runspace: Runspace, process: Process) { 81 | super(`Runspace ${runspace.runspaceId.toString()}`, vscode.TreeItemCollapsibleState.None); 82 | 83 | this.description = runspace.availability; 84 | this.tooltip = runspace.state; 85 | 86 | this.runspace = runspace; 87 | this.process = process; 88 | 89 | const themeIcon = new vscode.ThemeIcon("circuit-board"); 90 | this.iconPath = themeIcon; 91 | } 92 | 93 | contextValue = 'runspace'; 94 | } 95 | 96 | export class ModulesTreeItem extends ParentTreeItem { 97 | async getChildren(): Promise { 98 | return [ 99 | new CustomModules(), 100 | new RepositoriesTreeViewItem() 101 | ] 102 | } 103 | constructor() { 104 | super("Modules", vscode.TreeItemCollapsibleState.Collapsed); 105 | 106 | const themeIcon = new vscode.ThemeIcon("package"); 107 | this.iconPath = themeIcon; 108 | } 109 | 110 | contextValue = 'modules'; 111 | } 112 | 113 | export class CustomModules extends ParentTreeItem { 114 | async getChildren(): Promise { 115 | try { 116 | const modules = await Container.universal.getModules(); 117 | return modules.filter(m => !m.readOnly).map(x => new CustomModule(x)); 118 | } 119 | catch (err) { 120 | Container.universal.showConnectionError("Failed to query modules. " + err); 121 | return []; 122 | } 123 | } 124 | 125 | constructor() { 126 | super("Modules", vscode.TreeItemCollapsibleState.Collapsed); 127 | 128 | const themeIcon = new vscode.ThemeIcon("folder"); 129 | this.iconPath = themeIcon; 130 | } 131 | 132 | contextValue = 'customModules'; 133 | } 134 | 135 | export class RepositoriesTreeViewItem extends ParentTreeItem { 136 | async getChildren(): Promise { 137 | try { 138 | const repos = await Container.universal.getRepositories(); 139 | return repos.map(x => { 140 | const ti = new vscode.TreeItem(x.name, vscode.TreeItemCollapsibleState.None); 141 | ti.tooltip = x.url; 142 | const themeIcon = new vscode.ThemeIcon("repo"); 143 | ti.iconPath = themeIcon; 144 | return ti; 145 | }); 146 | } 147 | catch (err) { 148 | Container.universal.showConnectionError("Failed to query repositories. " + err); 149 | return []; 150 | } 151 | } 152 | 153 | constructor() { 154 | super("Repositories", vscode.TreeItemCollapsibleState.Collapsed); 155 | 156 | const themeIcon = new vscode.ThemeIcon("repo-forked"); 157 | this.iconPath = themeIcon; 158 | } 159 | } 160 | 161 | export class PowerShellUniversalModule extends vscode.TreeItem { 162 | constructor(module: Module) { 163 | super(module.name, vscode.TreeItemCollapsibleState.None); 164 | 165 | const themeIcon = new vscode.ThemeIcon("archive"); 166 | this.iconPath = themeIcon; 167 | 168 | this.tooltip = `${module.version}`; 169 | } 170 | } 171 | 172 | export class CustomModule extends vscode.TreeItem { 173 | public module: Module; 174 | constructor(module: Module) { 175 | super(module.name, vscode.TreeItemCollapsibleState.None); 176 | 177 | const themeIcon = new vscode.ThemeIcon("archive"); 178 | this.iconPath = themeIcon; 179 | 180 | this.tooltip = `${module.version}`; 181 | this.module = module; 182 | } 183 | 184 | contextValue = 'customModule'; 185 | } -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { Universal } from './universal'; 3 | import { Container } from './container'; 4 | import { DashboardTreeViewProvider } from './dashboard-treeview'; 5 | import { InfoTreeViewProvider } from './info-treeview'; 6 | import help from './commands/helpCommand'; 7 | import { load } from './settings'; 8 | import { registerDashboardCommands } from './commands/dashboards'; 9 | import { ApiTreeViewProvider } from './api-treeview'; 10 | import { registerEndpointCommands } from './commands/endpoints'; 11 | import { AutomationTreeViewProvider } from './automation-treeview'; 12 | import { registerScriptCommands } from './commands/scripts'; 13 | import { registerConfigCommands } from './commands/config'; 14 | import { ConfigTreeViewProvider } from './configuration-treeview'; 15 | import { registerConnectCommands } from './commands/connect'; 16 | import { ConnectionTreeViewProvider } from './connection-treeview'; 17 | import { registerWelcomeCommands } from './commands/welcomeCommand'; 18 | import { registerWalkthroughCommands } from './commands/walkthrough'; 19 | import { registerTerminalCommands } from './commands/terminals'; 20 | import { PlatformTreeViewProvider } from './platform-treeview'; 21 | import { registerModuleCommands } from './commands/modules'; 22 | import { registerDebuggerCommands } from './commands/debugger'; 23 | import { registerLocalDevCommands } from './commands/localDev'; 24 | import { LocalDevConfig } from './types'; 25 | 26 | export async function activate(context: vscode.ExtensionContext) { 27 | registerConnectCommands(context); 28 | 29 | const universal = new Universal(context); 30 | Container.initialize(context, universal); 31 | 32 | let settings = load(); 33 | if (settings.appToken === "" && settings.connections.length === 0) { 34 | vscode.commands.executeCommand('powershell-universal.welcome') 35 | 36 | vscode.window.showInformationMessage("You need to configure the PowerShell Universal extension. If you haven't installed PowerShell Universal, you should download it. If you have PowerShell Universal running, you can connect.", "Download", "Settings").then(result => { 37 | if (result === "Download") { 38 | vscode.env.openExternal(vscode.Uri.parse("https://ironmansoftware.com/downloads")); 39 | } 40 | 41 | if (result === "Settings") { 42 | vscode.commands.executeCommand('workbench.action.openSettings', "PowerShell Universal"); 43 | } 44 | }); 45 | } 46 | 47 | vscode.window.registerUriHandler({ 48 | handleUri: (uri: vscode.Uri): vscode.ProviderResult => { 49 | if (uri.path.startsWith('/debug')) { 50 | const querystring = require('querystring'); 51 | const query = querystring.parse(uri.query); 52 | 53 | Container.universal.sendTerminalCommand(`if ($PID -ne ${query.PID}) {Enter-PSHostProcess -ID ${query.PID} }`); 54 | Container.universal.sendTerminalCommand(`Debug-Runspace -ID ${query.RS}`); 55 | } 56 | 57 | if (uri.path.startsWith('/connect')) { 58 | var atob = require('atob'); 59 | const querystring = require('querystring'); 60 | const query = querystring.parse(uri.query); 61 | const url = atob(query.CB); 62 | 63 | Container.universal.connectUniversal(url); 64 | 65 | vscode.commands.executeCommand('powershell-universal.refreshAllTreeViews'); 66 | } 67 | } 68 | }); 69 | 70 | const connectionProvider = new ConnectionTreeViewProvider(context); 71 | const moduleProvider = new DashboardTreeViewProvider(); 72 | const infoProvider = new InfoTreeViewProvider(); 73 | const endpointProvider = new ApiTreeViewProvider(); 74 | const scriptProvider = new AutomationTreeViewProvider(); 75 | const configProvider = new ConfigTreeViewProvider(); 76 | const platformProvider = new PlatformTreeViewProvider(); 77 | 78 | vscode.window.createTreeView('universalConnectionProviderView', { treeDataProvider: connectionProvider }); 79 | vscode.window.createTreeView('universalDashboardProviderView', { treeDataProvider: moduleProvider }); 80 | vscode.window.createTreeView('universalEndpointProviderView', { treeDataProvider: endpointProvider }); 81 | vscode.window.createTreeView('universalScriptProviderView', { treeDataProvider: scriptProvider }); 82 | vscode.window.createTreeView('universalConfigProviderView', { treeDataProvider: configProvider }); 83 | vscode.window.createTreeView('universalInfoProviderView', { treeDataProvider: infoProvider }); 84 | vscode.window.createTreeView('universalPlatformProviderView', { treeDataProvider: platformProvider }); 85 | 86 | Container.ConfigFileTreeView = configProvider; 87 | 88 | vscode.commands.registerCommand('powershell-universal.refreshTreeView', () => moduleProvider.refresh()); 89 | vscode.commands.registerCommand('powershell-universal.refreshEndpointTreeView', () => endpointProvider.refresh()); 90 | vscode.commands.registerCommand('powershell-universal.refreshScriptTreeView', () => scriptProvider.refresh()); 91 | vscode.commands.registerCommand('powershell-universal.refreshConfigurationTreeView', () => configProvider.refresh()); 92 | vscode.commands.registerCommand('powershell-universal.refreshConnectionTreeView', () => connectionProvider.refresh()); 93 | vscode.commands.registerCommand('powershell-universal.refreshPlatformTreeView', () => platformProvider.refresh()); 94 | 95 | vscode.commands.registerCommand('powershell-universal.refreshAllTreeViews', () => { 96 | vscode.commands.executeCommand('powershell-universal.refreshTreeView'); 97 | vscode.commands.executeCommand('powershell-universal.refreshEndpointTreeView'); 98 | vscode.commands.executeCommand('powershell-universal.refreshScriptTreeView'); 99 | vscode.commands.executeCommand('powershell-universal.refreshConfigurationTreeView'); 100 | vscode.commands.executeCommand('powershell-universal.refreshConnectionTreeView'); 101 | vscode.commands.executeCommand('powershell-universal.refreshPlatformTreeView'); 102 | }); 103 | 104 | registerLocalDevCommands(context); 105 | help(); 106 | registerDashboardCommands(context); 107 | registerEndpointCommands(context); 108 | registerScriptCommands(context); 109 | registerConfigCommands(context); 110 | registerWelcomeCommands(context); 111 | registerWalkthroughCommands(context); 112 | registerTerminalCommands(context); 113 | registerModuleCommands(context); 114 | registerDebuggerCommands(context); 115 | 116 | var localDevConfig = context.globalState.get("psu.dev.config"); 117 | 118 | if (Container.universal.hasConnection() && !localDevConfig) { 119 | if (await Container.universal.waitForAlive()) { 120 | await Container.universal.installAndLoadModule(); 121 | } 122 | } 123 | } 124 | 125 | // this method is called when your extension is deactivated 126 | export function deactivate() { } 127 | -------------------------------------------------------------------------------- /src/commands/localDev.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { LocalDevConfig } from '../types'; 3 | import { Container } from '../container'; 4 | const os = require('os'); 5 | const https = require('https'); 6 | const fs = require('fs'); 7 | const temp = require('temp'); 8 | var AdmZip = require('adm-zip'); 9 | const path = require('path'); 10 | 11 | export const registerLocalDevCommands = (context: vscode.ExtensionContext) => { 12 | context.subscriptions.push(vscode.commands.registerCommand('powershell-universal.downloadUniversal', downloadUniversal)); 13 | context.subscriptions.push(vscode.commands.registerCommand('powershell-universal.startUniversal', () => startPowerShellUniversal(context))); 14 | context.subscriptions.push(vscode.commands.registerCommand('powershell-universal.clearLocalDatabase', clearLocalDatabase)); 15 | context.subscriptions.push(vscode.commands.registerCommand('powershell-universal.connectLocalDevModule', () => connectModule(context))); 16 | }; 17 | 18 | const startPowerShellUniversal = async (context: vscode.ExtensionContext) => { 19 | const config = getPsuDevConfig(); 20 | if (!config) { 21 | return; 22 | } 23 | 24 | const psuPath = path.join(process.env.USERPROFILE, ".psu"); 25 | if (!fs.existsSync(psuPath)) { 26 | await downloadUniversal(); 27 | } 28 | 29 | let exe = 'Universal.Server.exe'; 30 | if (os.platform() === 'win32') { 31 | exe = 'Universal.Server.exe'; 32 | } else { 33 | exe = 'Universal.Server'; 34 | } 35 | 36 | const universalPath = path.join(psuPath, config.version, exe); 37 | 38 | if (vscode.workspace.workspaceFolders === undefined) { 39 | vscode.window.showErrorMessage("No workspace folder is open. Please open a workspace folder and define a psu.dev.config to download Universal."); 40 | return; 41 | } 42 | 43 | config.databaseType = config.databaseType || "SQLite"; 44 | let connectionString = config.databaseConnectionString || null; 45 | if (connectionString === null) { 46 | const databasePath = path.join(process.env.USERPROFILE, ".psu", "databases", vscode.workspace.name, "psu.db"); 47 | connectionString = `Data Source=${databasePath}`; 48 | } 49 | 50 | vscode.window.createTerminal({ 51 | name: `PowerShell Universal ${config.version}`, 52 | shellPath: universalPath, 53 | shellArgs: ["Mode", "Dev"], 54 | env: { 55 | "Data__RepositoryPath": vscode.workspace.workspaceFolders[0].uri.fsPath, 56 | "Data__ConnectionString": connectionString, 57 | "Plugin__0": config.databaseType, 58 | "PSUDefaultAdminPassword": "admin", 59 | "PSUDefaultAdminName": "admin", 60 | ...config.env, 61 | } 62 | }).show(); 63 | 64 | if (config.browserPort) { 65 | vscode.env.openExternal(vscode.Uri.parse(`http://localhost:${config.browserPort || 5000}`)); 66 | } 67 | 68 | context.globalState.update("psu.dev.config", config); 69 | 70 | context.globalState.update("universal.connection", "Local Development"); 71 | vscode.commands.executeCommand('powershell-universal.refreshAllTreeViews'); 72 | }; 73 | 74 | const clearLocalDatabase = () => { 75 | const dbPath = path.join(process.env.USERPROFILE, ".psu", "databases", vscode.workspace.name, "psu.db"); 76 | if (fs.existsSync(dbPath)) { 77 | fs.unlinkSync(dbPath); 78 | 79 | vscode.window.showInformationMessage(`Local development database cleared at.${dbPath}`); 80 | } 81 | }; 82 | 83 | const downloadUniversal = async () => { 84 | vscode.window.withProgress({ 85 | location: vscode.ProgressLocation.Notification, 86 | title: "Downloading Universal" 87 | }, async (progress) => { 88 | temp.track(); 89 | 90 | let platform = ''; 91 | switch (os.platform()) { 92 | case 'darwin': 93 | platform = 'osx'; 94 | break; 95 | case 'linux': 96 | platform = 'linux'; 97 | break; 98 | case 'win32': 99 | platform = 'win'; 100 | break; 101 | default: 102 | vscode.window.showErrorMessage("Unsupported platform"); 103 | return; 104 | } 105 | 106 | const config = getPsuDevConfig(); 107 | if (!config) { 108 | return; 109 | } 110 | 111 | progress.report({ increment: 75, message: `Extracting PowerShell Universal ${config.version}` }); 112 | 113 | var tempPath = path.join(temp.dir, `Universal.${platform}-x64.${config.version}.zip`); 114 | await downloadFile(`https://imsreleases.blob.core.windows.net/universal/production/${config.version}/Universal.${platform}-x64.${config.version}.zip`, tempPath); 115 | 116 | const universalPath = path.join(process.env.USERPROFILE, ".psu", config.version); 117 | 118 | var zip = new AdmZip(tempPath); 119 | zip.extractAllTo(universalPath, true); 120 | 121 | progress.report({ increment: 100, message: `PowerShell Universal ${config.version} downloaded and extracted to ${universalPath}` }); 122 | }); 123 | }; 124 | 125 | const downloadFile = (url: string, dest: string) => { 126 | return new Promise((resolve, reject) => { 127 | const file = fs.createWrite(dest); 128 | file.on('finish', () => { 129 | file.close(resolve); 130 | }); 131 | 132 | file.on('error', (err: any) => { 133 | fs.unlink(dest, () => reject(err)); 134 | }); 135 | 136 | https.get(url, (response: any) => { 137 | if (response.statusCode !== 200) { 138 | return reject(new Error(`Failed to download file: ${response.statusCode}`)); 139 | } 140 | response.pipe(file); 141 | }).on('error', (err: any) => { 142 | fs.unlink(dest, () => reject(err)); 143 | }); 144 | }); 145 | }; 146 | 147 | const getPsuDevConfig = (): LocalDevConfig | null => { 148 | if (vscode.workspace.workspaceFolders === undefined) { 149 | vscode.window.showErrorMessage("No workspace folder is open. Please open a workspace folder and define a psu.dev.config to download Universal."); 150 | return null; 151 | } 152 | 153 | const devDevPath = path.join(vscode.workspace.workspaceFolders[0].uri.fsPath, "psu.dev.json"); 154 | if (!fs.existsSync(devDevPath)) { 155 | vscode.window.showErrorMessage("No psu.dev.json file found in the workspace. Please create one to start PowerShell Universal."); 156 | return null; 157 | } 158 | 159 | const devJson = fs.readFileSync(devDevPath, 'utf8'); 160 | return JSON.parse(devJson); 161 | }; 162 | 163 | const connectModule = async (context: vscode.ExtensionContext) => { 164 | 165 | const config = getPsuDevConfig(); 166 | 167 | if (!config) { 168 | vscode.window.showErrorMessage("No psu.dev.json file found in the workspace. Please create one to connect to PowerShell Universal."); 169 | return; 170 | } 171 | 172 | Container.universal.sendTerminalCommand(`Import-Module (Join-Path '${__dirname}' 'Universal.VSCode.psm1')`); 173 | Container.universal.sendTerminalCommand(`Import-LocalDevelopmentModule -Version '${config.version}' -Port '${config.browserPort || 5000}'`); 174 | } -------------------------------------------------------------------------------- /src/automation-treeview.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { Container } from './container'; 3 | import { Folder, Job, JobStatus, Script, Terminal } from './types'; 4 | import ParentTreeItem from './parentTreeItem'; 5 | import compareVersions = require('compare-versions'); 6 | 7 | export class AutomationTreeViewProvider implements vscode.TreeDataProvider { 8 | 9 | private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); 10 | readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; 11 | 12 | getTreeItem(element: vscode.TreeItem): vscode.TreeItem | Thenable { 13 | return element; 14 | } 15 | 16 | async getChildren(element?: vscode.TreeItem | undefined) { 17 | if (element == null) { 18 | return [ 19 | new ScriptsTreeItem(), 20 | new JobsTreeItem(), 21 | new TerminalsTreeItem() 22 | ] 23 | } 24 | 25 | if (element instanceof ParentTreeItem) { 26 | var parentTreeItem = element as ParentTreeItem; 27 | return parentTreeItem.getChildren(); 28 | } 29 | } 30 | 31 | refresh(node?: vscode.TreeItem): void { 32 | this._onDidChangeTreeData.fire(node); 33 | } 34 | } 35 | 36 | export class ScriptsTreeItem extends ParentTreeItem { 37 | constructor() { 38 | super("Scripts", vscode.TreeItemCollapsibleState.Collapsed); 39 | 40 | this.iconPath = new vscode.ThemeIcon('files'); 41 | } 42 | 43 | async getChildren(): Promise { 44 | let treeItems = [] as vscode.TreeItem[]; 45 | 46 | try { 47 | treeItems = await Container.universal.getRootFolders().then(x => x.sort((a, b) => (a.name > b.name) ? 1 : -1).map(y => new FolderTreeItem(y))); 48 | } 49 | catch (err) { 50 | Container.universal.showConnectionError("Failed to query scripts. " + err); 51 | return []; 52 | } 53 | 54 | try { 55 | var scripts = await Container.universal.getRootScripts().then(x => x.sort((a, b) => (a.name > b.name) ? 1 : -1).map(y => new ScriptTreeItem(y))); 56 | treeItems = treeItems.concat(scripts); 57 | } 58 | catch (err) { 59 | Container.universal.showConnectionError("Failed to query scripts. " + err); 60 | return []; 61 | } 62 | 63 | return treeItems; 64 | } 65 | } 66 | 67 | export class FolderTreeItem extends ParentTreeItem { 68 | public folder: Folder; 69 | 70 | constructor(folder: Folder) { 71 | super(folder.name, vscode.TreeItemCollapsibleState.Collapsed); 72 | 73 | this.folder = folder; 74 | const themeIcon = new vscode.ThemeIcon('folder'); 75 | this.iconPath = themeIcon; 76 | } 77 | 78 | async getChildren(): Promise { 79 | 80 | let treeItems = [] as vscode.TreeItem[]; 81 | 82 | const version = await Container.universal.getVersion(); 83 | 84 | if (compareVersions(version, "5.5.0") < 0) { 85 | var scripts = await Container.universal.getScripts().then(x => x.sort((a, b) => (a.name > b.name) ? 1 : -1).map(y => new ScriptTreeItem(y))); 86 | treeItems = treeItems.concat(scripts); 87 | return treeItems; 88 | } 89 | 90 | try { 91 | treeItems = await Container.universal.getFoldersInFolder(this.folder).then(x => x.sort((a, b) => (a.name > b.name) ? 1 : -1).map(y => new FolderTreeItem(y))); 92 | } 93 | catch (err) { 94 | Container.universal.showConnectionError("Failed to query folders. " + err); 95 | return []; 96 | } 97 | 98 | try { 99 | var scripts = await Container.universal.getScriptsInFolder(this.folder).then(x => x.sort((a, b) => (a.name > b.name) ? 1 : -1).map(y => new ScriptTreeItem(y))); 100 | treeItems = treeItems.concat(scripts); 101 | } 102 | catch (err) { 103 | Container.universal.showConnectionError("Failed to query scripts. " + err); 104 | return []; 105 | } 106 | 107 | return treeItems; 108 | } 109 | 110 | contextValue = "folder"; 111 | } 112 | 113 | export class ScriptTreeItem extends vscode.TreeItem { 114 | public script: Script; 115 | 116 | constructor(script: Script) { 117 | super(script.name, vscode.TreeItemCollapsibleState.None); 118 | 119 | this.script = script; 120 | const themeIcon = new vscode.ThemeIcon('file-code'); 121 | this.iconPath = themeIcon; 122 | } 123 | 124 | contextValue = "script"; 125 | } 126 | 127 | export class TerminalsTreeItem extends ParentTreeItem { 128 | constructor() { 129 | super("Terminals", vscode.TreeItemCollapsibleState.Collapsed); 130 | 131 | this.iconPath = new vscode.ThemeIcon('terminal'); 132 | } 133 | 134 | async getChildren(): Promise { 135 | try { 136 | return await Container.universal.getTerminals().then(x => x.sort((a, b) => (a.name > b.name) ? 1 : -1).map(y => new TerminalTreeItem(y))); 137 | } 138 | catch (err) { 139 | Container.universal.showConnectionError("Failed to query scripts. " + err); 140 | return []; 141 | } 142 | } 143 | } 144 | 145 | export class TerminalTreeItem extends vscode.TreeItem { 146 | public terminal: Terminal; 147 | 148 | constructor(terminal: Terminal) { 149 | super(terminal.name, vscode.TreeItemCollapsibleState.None); 150 | 151 | this.terminal = terminal; 152 | const themeIcon = new vscode.ThemeIcon('terminal'); 153 | this.iconPath = themeIcon; 154 | } 155 | 156 | contextValue = "terminal"; 157 | } 158 | 159 | 160 | export class JobsTreeItem extends ParentTreeItem { 161 | constructor() { 162 | super("Jobs", vscode.TreeItemCollapsibleState.Collapsed); 163 | 164 | this.iconPath = new vscode.ThemeIcon('checklist'); 165 | } 166 | 167 | async getChildren(): Promise { 168 | try { 169 | return await Container.universal.getJobs().then(x => x.page.sort((a, b) => (a.id < b.id) ? 1 : -1).map(y => new JobTreeItem(y))); 170 | } 171 | catch (err) { 172 | Container.universal.showConnectionError("Failed to query jobs. " + err); 173 | return []; 174 | } 175 | } 176 | } 177 | 178 | export class JobTreeItem extends vscode.TreeItem { 179 | public job: Job; 180 | 181 | constructor(job: Job) { 182 | super(job.scriptFullPath, vscode.TreeItemCollapsibleState.None); 183 | 184 | this.job = job; 185 | 186 | if (job.status == JobStatus.Completed) { 187 | this.iconPath = new vscode.ThemeIcon('check'); 188 | this.tooltip = 'Completed successfully'; 189 | } 190 | if (job.status == JobStatus.Running) { 191 | this.iconPath = new vscode.ThemeIcon('play'); 192 | this.tooltip = 'Running'; 193 | } 194 | if (job.status == JobStatus.Failed) { 195 | this.iconPath = new vscode.ThemeIcon('error'); 196 | this.tooltip = 'Failed'; 197 | } 198 | if (job.status == JobStatus.WaitingOnFeedback) { 199 | this.iconPath = new vscode.ThemeIcon('question'); 200 | this.tooltip = 'Waiting on feedback'; 201 | } 202 | 203 | this.description = job.id.toString(); 204 | } 205 | 206 | contextValue = "job"; 207 | } -------------------------------------------------------------------------------- /src/commands/scripts.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { load } from './../settings'; 3 | import { JobTreeItem, ScriptTreeItem } from '../automation-treeview'; 4 | import { Container } from '../container'; 5 | import { trackJob } from '../job-tracker'; 6 | const path = require('path'); 7 | const os = require('os'); 8 | import * as fs from 'fs'; 9 | import { tmpdir } from './utils'; 10 | import { ConfigTreeItem } from './../configuration-treeview'; 11 | 12 | function normalizeDriveLetter(path: string): string { 13 | if (hasDriveLetter(path)) { 14 | return path.charAt(0).toUpperCase() + path.slice(1); 15 | } 16 | 17 | return path; 18 | } 19 | 20 | function hasDriveLetter(path: string): boolean { 21 | if (os.platform() == 'win32') { 22 | return isWindowsDriveLetter(path.charCodeAt(0)) && path.charCodeAt(1) === 58; 23 | } 24 | 25 | return false; 26 | } 27 | 28 | function isWindowsDriveLetter(char0: number): boolean { 29 | return char0 >= 65 && char0 <= 90 || char0 >= 97 && char0 <= 122; 30 | } 31 | 32 | export const registerScriptCommands = (context: vscode.ExtensionContext) => { 33 | vscode.commands.registerCommand('powershell-universal.openScriptConfigFile', openScriptConfigFileCommand); 34 | vscode.commands.registerCommand('powershell-universal.invokeScript', item => invokeScriptCommand(item, context)); 35 | vscode.commands.registerCommand('powershell-universal.manageScripts', () => manageScriptsCommand(context)); 36 | vscode.commands.registerCommand('powershell-universal.editScript', editScriptCommand); 37 | vscode.commands.registerCommand('powershell-universal.viewJobLog', viewJobLogCommand); 38 | vscode.commands.registerCommand('powershell-universal.viewJob', (item) => viewJobCommand(item, context)); 39 | vscode.commands.registerCommand('powershell-universal.getJobPipelineOutput', getJobPipelineOutputCommand); 40 | 41 | 42 | vscode.workspace.onDidSaveTextDocument(async (file) => { 43 | if (file.fileName.includes('.universal.code.script')) { 44 | const codePath = path.join(tmpdir(), '.universal.code.script'); 45 | const normCodePath = normalizeDriveLetter(codePath); 46 | const normFileName = normalizeDriveLetter(file.fileName); 47 | const fileName = normFileName.replace(normCodePath, "").replace(/^\\*/, "").replace(/^\/*/, ""); 48 | try { 49 | var script = await Container.universal.getScriptFilePath(fileName); 50 | script.content = file.getText(); 51 | script = await Container.universal.saveScript(script); 52 | if (script.content && script.content !== file.getText()) { 53 | throw "Failed to save script!"; 54 | } 55 | } 56 | catch (e) { 57 | vscode.window.showErrorMessage(e); 58 | } 59 | } 60 | }); 61 | } 62 | 63 | export const openScriptConfigFileCommand = async () => { 64 | await vscode.commands.executeCommand('powershell-universal.openConfigFile', new ConfigTreeItem("scripts", ".universal\\scripts.ps1", true, "")); 65 | } 66 | 67 | export const editScriptCommand = async (item: ScriptTreeItem) => { 68 | var settings = load(); 69 | if (settings.localEditing) { 70 | await editScriptLocal(item); 71 | } 72 | else { 73 | await editScriptRemote(item); 74 | } 75 | } 76 | 77 | export const editScriptRemote = async (item: ScriptTreeItem) => { 78 | //https://stackoverflow.com/a/56620552 79 | 80 | const filePath = path.join(tmpdir(), '.universal.code.script', item.script.fullPath); 81 | const codePath = path.join(tmpdir(), '.universal.code.script'); 82 | const script = await Container.universal.getScript(item.script.id); 83 | if (!fs.existsSync(codePath)) { 84 | fs.mkdirSync(codePath); 85 | } 86 | fs.mkdirSync(path.dirname(filePath), { "recursive": true }); 87 | fs.writeFileSync(filePath, script.content); 88 | 89 | const textDocument = await vscode.workspace.openTextDocument(filePath); 90 | 91 | vscode.window.showTextDocument(textDocument); 92 | } 93 | 94 | export const editScriptLocal = async (item: ScriptTreeItem) => { 95 | const settings = await Container.universal.getSettings(); 96 | const filePath = path.join(settings.repositoryPath, item.script.fullPath); 97 | 98 | if (!fs.existsSync(filePath)) { 99 | await vscode.window.showErrorMessage(`Failed to find file ${filePath}. If you have local editing on and are accessing a remote file, you may need to turn off local editing.`); 100 | return 101 | } 102 | 103 | const textDocument = await vscode.workspace.openTextDocument(filePath); 104 | 105 | vscode.window.showTextDocument(textDocument); 106 | } 107 | 108 | export const invokeScriptCommand = async (item: ScriptTreeItem, context: vscode.ExtensionContext) => { 109 | const settings = load(); 110 | 111 | const connectionName = context.globalState.get("universal.connection"); 112 | var url = settings.url; 113 | 114 | if (connectionName && connectionName !== 'Default') { 115 | const connection = settings.connections.find(m => m.name === connectionName); 116 | if (connection) { 117 | url = connection.url; 118 | } 119 | } 120 | 121 | const parameters = await Container.universal.getScriptParameters(item.script.id); 122 | if (parameters && parameters.length > 0) { 123 | const result = await vscode.window.showWarningMessage(`Script has parameters and cannot be run from VS Code.`, "View Script"); 124 | 125 | if (result === "View Script") { 126 | vscode.env.openExternal(vscode.Uri.parse(`${url}/admin/automation/scripts/${item.script.fullPath}`)); 127 | } 128 | } else { 129 | const jobId = await Container.universal.runScript(item.script.id); 130 | const result = await vscode.window.showInformationMessage(`Job ${jobId} started.`, "View Job"); 131 | 132 | if (result === "View Job") { 133 | vscode.env.openExternal(vscode.Uri.parse(`${url}/admin/automation/jobs/${jobId}`)); 134 | } 135 | 136 | trackJob(jobId); 137 | } 138 | 139 | 140 | } 141 | 142 | export const manageScriptsCommand = async (context: vscode.ExtensionContext) => { 143 | const settings = load(); 144 | 145 | const connectionName = context.globalState.get("universal.connection"); 146 | var url = settings.url; 147 | 148 | if (connectionName && connectionName !== 'Default') { 149 | const connection = settings.connections.find(m => m.name === connectionName); 150 | if (connection) { 151 | url = connection.url; 152 | } 153 | } 154 | 155 | vscode.env.openExternal(vscode.Uri.parse(`${url}/admin/automation/scripts`)); 156 | } 157 | 158 | let jobLogChannel = vscode.window.createOutputChannel("PowerShell Universal - Job"); 159 | 160 | export const viewJobLogCommand = async (jobItem: JobTreeItem) => { 161 | 162 | jobLogChannel.clear(); 163 | jobLogChannel.show(); 164 | jobLogChannel.append(`Loading log for job ${jobItem.job.id}...`); 165 | 166 | Container.universal.getJobLog(jobItem.job.id).then((log) => { 167 | jobLogChannel.clear(); 168 | jobLogChannel.appendLine(log.log); 169 | }); 170 | } 171 | 172 | export const viewJobCommand = async (jobItem: JobTreeItem, context: vscode.ExtensionContext) => { 173 | const settings = load(); 174 | 175 | const connectionName = context.globalState.get("universal.connection"); 176 | var url = settings.url; 177 | 178 | if (connectionName && connectionName !== 'Default') { 179 | const connection = settings.connections.find(m => m.name === connectionName); 180 | if (connection) { 181 | url = connection.url; 182 | } 183 | } 184 | 185 | vscode.env.openExternal(vscode.Uri.parse(`${url}/admin/automation/jobs/${jobItem.job.id}`)); 186 | } 187 | 188 | export const getJobPipelineOutputCommand = async (jobItem: JobTreeItem) => { 189 | Container.universal.sendTerminalCommand(`Get-PSUJobPipelineOutput -JobId ${jobItem.job.id}`); 190 | }; -------------------------------------------------------------------------------- /src/dashboard-treeview.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { Container } from './container'; 3 | import { Dashboard, DashboardEndpoint, DashboardPage, DashboardSession, DashboardStatus } from './types'; 4 | import ParentTreeItem from './parentTreeItem'; 5 | export class DashboardTreeViewProvider implements vscode.TreeDataProvider { 6 | 7 | private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); 8 | readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; 9 | 10 | getTreeItem(element: vscode.TreeItem): vscode.TreeItem | Thenable { 11 | return element; 12 | } 13 | 14 | async getChildren(element?: vscode.TreeItem | undefined) { 15 | if (element == null) { 16 | return [ 17 | new DashboardsTreeItem() 18 | ] 19 | } 20 | 21 | if (element instanceof ParentTreeItem) { 22 | var parentTreeItem = element as ParentTreeItem; 23 | return parentTreeItem.getChildren(); 24 | } 25 | } 26 | 27 | refresh(node?: vscode.TreeItem): void { 28 | this._onDidChangeTreeData.fire(node); 29 | } 30 | } 31 | 32 | export class DashboardsTreeItem extends ParentTreeItem { 33 | constructor() { 34 | super("Apps", vscode.TreeItemCollapsibleState.Collapsed); 35 | 36 | this.iconPath = new vscode.ThemeIcon('dashboard'); 37 | } 38 | async getChildren(): Promise { 39 | try { 40 | const dashboards = await Container.universal.getDashboards(); 41 | return dashboards.map(y => new DashboardTreeItem(y)); 42 | } 43 | catch (err) { 44 | Container.universal.showConnectionError("Failed to query apps. " + err); 45 | return []; 46 | } 47 | } 48 | } 49 | 50 | export class DashboardTreeItem extends ParentTreeItem { 51 | async getChildren(): Promise { 52 | try { 53 | return [ 54 | new DashboardPagesTreeItem(this.dashboard), 55 | new DashboardSessionsTreeItem(this.dashboard), 56 | new DashboardModuleTreeItem(this.dashboard), 57 | ]; 58 | } 59 | catch (err) { 60 | Container.universal.showConnectionError("Failed to query apps pages. " + err); 61 | return []; 62 | } 63 | } 64 | 65 | public dashboard: Dashboard; 66 | private logIndex: number; 67 | 68 | constructor(dashboard: Dashboard) { 69 | super(dashboard.name, vscode.TreeItemCollapsibleState.Collapsed); 70 | this.dashboard = dashboard; 71 | const icon = dashboard.status === DashboardStatus.Started ? "debug-start" : "debug-stop"; 72 | const themeIcon = new vscode.ThemeIcon(icon); 73 | this.iconPath = themeIcon; 74 | this.logIndex = 0; 75 | } 76 | 77 | async reloadLog() { 78 | const log = await Container.universal.getDashboardLog(this.dashboard.name); 79 | const logChannel = Container.getPanel(`App (${this.dashboard.name})`); 80 | logChannel.clear(); 81 | logChannel.appendLine(log); 82 | } 83 | 84 | async clearLog() { 85 | const logChannel = Container.getPanel(`App (${this.dashboard.name})`); 86 | logChannel.clear(); 87 | this.logIndex = 0; 88 | } 89 | 90 | async showLog() { 91 | const logChannel = Container.getPanel(`App (${this.dashboard.name})`); 92 | await this.reloadLog(); 93 | logChannel.show(); 94 | } 95 | 96 | contextValue = "dashboard"; 97 | } 98 | 99 | 100 | export class DashboardModuleTreeItem extends vscode.TreeItem { 101 | public dashboard: Dashboard; 102 | 103 | constructor(dashboard: Dashboard) { 104 | super("Module", vscode.TreeItemCollapsibleState.None); 105 | this.dashboard = dashboard; 106 | const themeIcon = new vscode.ThemeIcon('code'); 107 | this.iconPath = themeIcon; 108 | } 109 | 110 | contextValue = "dashboardModule"; 111 | } 112 | 113 | export class DashboardPagesTreeItem extends ParentTreeItem { 114 | 115 | private dashboard: Dashboard; 116 | 117 | constructor(dashboard: Dashboard) { 118 | super("Pages", vscode.TreeItemCollapsibleState.Collapsed); 119 | 120 | this.dashboard = dashboard; 121 | this.iconPath = new vscode.ThemeIcon('files'); 122 | } 123 | async getChildren(): Promise { 124 | try { 125 | const pages = await Container.universal.getDashboardPages(this.dashboard.id); 126 | return pages.map(y => new DashboardPageTreeItem(y)); 127 | } 128 | catch (err) { 129 | Container.universal.showConnectionError("Failed to query apps. " + err); 130 | return []; 131 | } 132 | } 133 | 134 | contextValue = "dashboardPages"; 135 | } 136 | 137 | export class DashboardPageTreeItem extends vscode.TreeItem { 138 | public page: DashboardPage; 139 | 140 | constructor(page: DashboardPage) { 141 | super(page.name, vscode.TreeItemCollapsibleState.None); 142 | this.page = page; 143 | const themeIcon = new vscode.ThemeIcon('file'); 144 | this.iconPath = themeIcon; 145 | } 146 | 147 | contextValue = "dashboardPage"; 148 | } 149 | 150 | export class DashboardSessionsTreeItem extends ParentTreeItem { 151 | 152 | private dashboard: Dashboard; 153 | 154 | constructor(dashboard: Dashboard) { 155 | super("Sessions", vscode.TreeItemCollapsibleState.Collapsed); 156 | 157 | this.dashboard = dashboard; 158 | this.iconPath = new vscode.ThemeIcon('debug-disconnect'); 159 | } 160 | async getChildren(): Promise { 161 | try { 162 | const diagnostics = await Container.universal.getDashboardDiagnostics(this.dashboard.id); 163 | return diagnostics.sessions.map(y => new DashboardSessionTreeItem(this.dashboard.id, y)); 164 | } 165 | catch (err) { 166 | Container.universal.showConnectionError("Failed to query apps. " + err); 167 | return []; 168 | } 169 | } 170 | 171 | contextValue = "dashboardPages"; 172 | } 173 | 174 | export class DashboardSessionTreeItem extends ParentTreeItem { 175 | async getChildren(): Promise { 176 | return [ 177 | new DashboardSessionPagesTreeItem(this.dashboardId, this.session) 178 | ] 179 | } 180 | public session: DashboardSession; 181 | private dashboardId: number; 182 | 183 | 184 | constructor(dashboardId: number, session: DashboardSession) { 185 | super(`${session.userName} (${session.id})`, vscode.TreeItemCollapsibleState.Collapsed); 186 | this.session = session; 187 | this.dashboardId = dashboardId; 188 | const themeIcon = new vscode.ThemeIcon('file'); 189 | this.iconPath = themeIcon; 190 | this.tooltip = session.lastTouched; 191 | } 192 | 193 | contextValue = "dashboardSession"; 194 | } 195 | 196 | export class DashboardSessionPagesTreeItem extends ParentTreeItem { 197 | 198 | private session: DashboardSession; 199 | private dashboardId: number; 200 | 201 | constructor(dashboardId: number, session: DashboardSession) { 202 | super("Tabs", vscode.TreeItemCollapsibleState.Collapsed); 203 | 204 | this.session = session; 205 | this.iconPath = new vscode.ThemeIcon('browser'); 206 | this.dashboardId = dashboardId; 207 | } 208 | async getChildren(): Promise { 209 | try { 210 | return this.session.pages.map(y => new DashboardSessionPageTreeItem(y, this.session.id, this.dashboardId)); 211 | } 212 | catch (err) { 213 | Container.universal.showConnectionError("Failed to query session pages. " + err); 214 | return []; 215 | } 216 | } 217 | 218 | contextValue = "dashboardPages"; 219 | } 220 | 221 | export class DashboardSessionPageTreeItem extends vscode.TreeItem { 222 | public pageId: string; 223 | public sessionId: string; 224 | public dashboardId: number; 225 | 226 | constructor(pageId: string, sessionId: string, dashboardId: number) { 227 | super(pageId, vscode.TreeItemCollapsibleState.None); 228 | const themeIcon = new vscode.ThemeIcon('browser'); 229 | this.iconPath = themeIcon; 230 | 231 | this.pageId = pageId; 232 | this.sessionId = sessionId; 233 | this.dashboardId = dashboardId; 234 | } 235 | 236 | contextValue = "dashboardSessionPage"; 237 | } 238 | 239 | 240 | 241 | export class DashboardEndpointsTreeItem extends ParentTreeItem { 242 | public endpoints: Array; 243 | 244 | constructor(endpoints: Array) { 245 | super("Endpoints", vscode.TreeItemCollapsibleState.Collapsed); 246 | 247 | this.endpoints = endpoints; 248 | } 249 | 250 | getChildren(): Promise { 251 | if (!this.endpoints) { 252 | return new Promise((resolve) => resolve([])); 253 | } 254 | 255 | 256 | return new Promise((resolve) => resolve(this.endpoints.map(x => new DashboardEndpointTreeItem(x)))); 257 | } 258 | } 259 | 260 | export class DashboardEndpointTreeItem extends vscode.TreeItem { 261 | 262 | public endpoint: DashboardEndpoint; 263 | 264 | constructor(endpoint: DashboardEndpoint) { 265 | super(endpoint.id); 266 | 267 | this.endpoint = endpoint; 268 | } 269 | 270 | contextValue = 'endpoint'; 271 | iconPath = '$(code)'; 272 | 273 | } -------------------------------------------------------------------------------- /media/css/bootstrap-reboot.rtl.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v5.1.3 (https://getbootstrap.com/) 3 | * Copyright 2011-2021 The Bootstrap Authors 4 | * Copyright 2011-2021 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */ 8 | :root { 9 | --bs-blue: #0d6efd; 10 | --bs-indigo: #6610f2; 11 | --bs-purple: #6f42c1; 12 | --bs-pink: #d63384; 13 | --bs-red: #dc3545; 14 | --bs-orange: #fd7e14; 15 | --bs-yellow: #ffc107; 16 | --bs-green: #198754; 17 | --bs-teal: #20c997; 18 | --bs-cyan: #0dcaf0; 19 | --bs-white: #fff; 20 | --bs-gray: #6c757d; 21 | --bs-gray-dark: #343a40; 22 | --bs-gray-100: #f8f9fa; 23 | --bs-gray-200: #e9ecef; 24 | --bs-gray-300: #dee2e6; 25 | --bs-gray-400: #ced4da; 26 | --bs-gray-500: #adb5bd; 27 | --bs-gray-600: #6c757d; 28 | --bs-gray-700: #495057; 29 | --bs-gray-800: #343a40; 30 | --bs-gray-900: #212529; 31 | --bs-primary: #0d6efd; 32 | --bs-secondary: #6c757d; 33 | --bs-success: #198754; 34 | --bs-info: #0dcaf0; 35 | --bs-warning: #ffc107; 36 | --bs-danger: #dc3545; 37 | --bs-light: #f8f9fa; 38 | --bs-dark: #212529; 39 | --bs-primary-rgb: 13, 110, 253; 40 | --bs-secondary-rgb: 108, 117, 125; 41 | --bs-success-rgb: 25, 135, 84; 42 | --bs-info-rgb: 13, 202, 240; 43 | --bs-warning-rgb: 255, 193, 7; 44 | --bs-danger-rgb: 220, 53, 69; 45 | --bs-light-rgb: 248, 249, 250; 46 | --bs-dark-rgb: 33, 37, 41; 47 | --bs-white-rgb: 255, 255, 255; 48 | --bs-black-rgb: 0, 0, 0; 49 | --bs-body-color-rgb: 33, 37, 41; 50 | --bs-body-bg-rgb: 255, 255, 255; 51 | --bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 52 | --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 53 | --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); 54 | --bs-body-font-family: var(--bs-font-sans-serif); 55 | --bs-body-font-size: 1rem; 56 | --bs-body-font-weight: 400; 57 | --bs-body-line-height: 1.5; 58 | --bs-body-color: #212529; 59 | --bs-body-bg: #fff; 60 | } 61 | 62 | *, 63 | *::before, 64 | *::after { 65 | box-sizing: border-box; 66 | } 67 | 68 | @media (prefers-reduced-motion: no-preference) { 69 | :root { 70 | scroll-behavior: smooth; 71 | } 72 | } 73 | 74 | body { 75 | margin: 0; 76 | font-family: var(--bs-body-font-family); 77 | font-size: var(--bs-body-font-size); 78 | font-weight: var(--bs-body-font-weight); 79 | line-height: var(--bs-body-line-height); 80 | color: var(--bs-body-color); 81 | text-align: var(--bs-body-text-align); 82 | background-color: var(--bs-body-bg); 83 | -webkit-text-size-adjust: 100%; 84 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 85 | } 86 | 87 | hr { 88 | margin: 1rem 0; 89 | color: inherit; 90 | background-color: currentColor; 91 | border: 0; 92 | opacity: 0.25; 93 | } 94 | 95 | hr:not([size]) { 96 | height: 1px; 97 | } 98 | 99 | h6, h5, h4, h3, h2, h1 { 100 | margin-top: 0; 101 | margin-bottom: 0.5rem; 102 | font-weight: 500; 103 | line-height: 1.2; 104 | } 105 | 106 | h1 { 107 | font-size: calc(1.375rem + 1.5vw); 108 | } 109 | @media (min-width: 1200px) { 110 | h1 { 111 | font-size: 2.5rem; 112 | } 113 | } 114 | 115 | h2 { 116 | font-size: calc(1.325rem + 0.9vw); 117 | } 118 | @media (min-width: 1200px) { 119 | h2 { 120 | font-size: 2rem; 121 | } 122 | } 123 | 124 | h3 { 125 | font-size: calc(1.3rem + 0.6vw); 126 | } 127 | @media (min-width: 1200px) { 128 | h3 { 129 | font-size: 1.75rem; 130 | } 131 | } 132 | 133 | h4 { 134 | font-size: calc(1.275rem + 0.3vw); 135 | } 136 | @media (min-width: 1200px) { 137 | h4 { 138 | font-size: 1.5rem; 139 | } 140 | } 141 | 142 | h5 { 143 | font-size: 1.25rem; 144 | } 145 | 146 | h6 { 147 | font-size: 1rem; 148 | } 149 | 150 | p { 151 | margin-top: 0; 152 | margin-bottom: 1rem; 153 | } 154 | 155 | abbr[title], 156 | abbr[data-bs-original-title] { 157 | -webkit-text-decoration: underline dotted; 158 | text-decoration: underline dotted; 159 | cursor: help; 160 | -webkit-text-decoration-skip-ink: none; 161 | text-decoration-skip-ink: none; 162 | } 163 | 164 | address { 165 | margin-bottom: 1rem; 166 | font-style: normal; 167 | line-height: inherit; 168 | } 169 | 170 | ol, 171 | ul { 172 | padding-right: 2rem; 173 | } 174 | 175 | ol, 176 | ul, 177 | dl { 178 | margin-top: 0; 179 | margin-bottom: 1rem; 180 | } 181 | 182 | ol ol, 183 | ul ul, 184 | ol ul, 185 | ul ol { 186 | margin-bottom: 0; 187 | } 188 | 189 | dt { 190 | font-weight: 700; 191 | } 192 | 193 | dd { 194 | margin-bottom: 0.5rem; 195 | margin-right: 0; 196 | } 197 | 198 | blockquote { 199 | margin: 0 0 1rem; 200 | } 201 | 202 | b, 203 | strong { 204 | font-weight: bolder; 205 | } 206 | 207 | small { 208 | font-size: 0.875em; 209 | } 210 | 211 | mark { 212 | padding: 0.2em; 213 | background-color: #fcf8e3; 214 | } 215 | 216 | sub, 217 | sup { 218 | position: relative; 219 | font-size: 0.75em; 220 | line-height: 0; 221 | vertical-align: baseline; 222 | } 223 | 224 | sub { 225 | bottom: -0.25em; 226 | } 227 | 228 | sup { 229 | top: -0.5em; 230 | } 231 | 232 | a { 233 | color: #0d6efd; 234 | text-decoration: underline; 235 | } 236 | a:hover { 237 | color: #0a58ca; 238 | } 239 | 240 | a:not([href]):not([class]), a:not([href]):not([class]):hover { 241 | color: inherit; 242 | text-decoration: none; 243 | } 244 | 245 | pre, 246 | code, 247 | kbd, 248 | samp { 249 | font-family: var(--bs-font-monospace); 250 | font-size: 1em; 251 | direction: ltr ; 252 | unicode-bidi: bidi-override; 253 | } 254 | 255 | pre { 256 | display: block; 257 | margin-top: 0; 258 | margin-bottom: 1rem; 259 | overflow: auto; 260 | font-size: 0.875em; 261 | } 262 | pre code { 263 | font-size: inherit; 264 | color: inherit; 265 | word-break: normal; 266 | } 267 | 268 | code { 269 | font-size: 0.875em; 270 | color: #d63384; 271 | word-wrap: break-word; 272 | } 273 | a > code { 274 | color: inherit; 275 | } 276 | 277 | kbd { 278 | padding: 0.2rem 0.4rem; 279 | font-size: 0.875em; 280 | color: #fff; 281 | background-color: #212529; 282 | border-radius: 0.2rem; 283 | } 284 | kbd kbd { 285 | padding: 0; 286 | font-size: 1em; 287 | font-weight: 700; 288 | } 289 | 290 | figure { 291 | margin: 0 0 1rem; 292 | } 293 | 294 | img, 295 | svg { 296 | vertical-align: middle; 297 | } 298 | 299 | table { 300 | caption-side: bottom; 301 | border-collapse: collapse; 302 | } 303 | 304 | caption { 305 | padding-top: 0.5rem; 306 | padding-bottom: 0.5rem; 307 | color: #6c757d; 308 | text-align: right; 309 | } 310 | 311 | th { 312 | text-align: inherit; 313 | text-align: -webkit-match-parent; 314 | } 315 | 316 | thead, 317 | tbody, 318 | tfoot, 319 | tr, 320 | td, 321 | th { 322 | border-color: inherit; 323 | border-style: solid; 324 | border-width: 0; 325 | } 326 | 327 | label { 328 | display: inline-block; 329 | } 330 | 331 | button { 332 | border-radius: 0; 333 | } 334 | 335 | button:focus:not(:focus-visible) { 336 | outline: 0; 337 | } 338 | 339 | input, 340 | button, 341 | select, 342 | optgroup, 343 | textarea { 344 | margin: 0; 345 | font-family: inherit; 346 | font-size: inherit; 347 | line-height: inherit; 348 | } 349 | 350 | button, 351 | select { 352 | text-transform: none; 353 | } 354 | 355 | [role=button] { 356 | cursor: pointer; 357 | } 358 | 359 | select { 360 | word-wrap: normal; 361 | } 362 | select:disabled { 363 | opacity: 1; 364 | } 365 | 366 | [list]::-webkit-calendar-picker-indicator { 367 | display: none; 368 | } 369 | 370 | button, 371 | [type=button], 372 | [type=reset], 373 | [type=submit] { 374 | -webkit-appearance: button; 375 | } 376 | button:not(:disabled), 377 | [type=button]:not(:disabled), 378 | [type=reset]:not(:disabled), 379 | [type=submit]:not(:disabled) { 380 | cursor: pointer; 381 | } 382 | 383 | ::-moz-focus-inner { 384 | padding: 0; 385 | border-style: none; 386 | } 387 | 388 | textarea { 389 | resize: vertical; 390 | } 391 | 392 | fieldset { 393 | min-width: 0; 394 | padding: 0; 395 | margin: 0; 396 | border: 0; 397 | } 398 | 399 | legend { 400 | float: right; 401 | width: 100%; 402 | padding: 0; 403 | margin-bottom: 0.5rem; 404 | font-size: calc(1.275rem + 0.3vw); 405 | line-height: inherit; 406 | } 407 | @media (min-width: 1200px) { 408 | legend { 409 | font-size: 1.5rem; 410 | } 411 | } 412 | legend + * { 413 | clear: right; 414 | } 415 | 416 | ::-webkit-datetime-edit-fields-wrapper, 417 | ::-webkit-datetime-edit-text, 418 | ::-webkit-datetime-edit-minute, 419 | ::-webkit-datetime-edit-hour-field, 420 | ::-webkit-datetime-edit-day-field, 421 | ::-webkit-datetime-edit-month-field, 422 | ::-webkit-datetime-edit-year-field { 423 | padding: 0; 424 | } 425 | 426 | ::-webkit-inner-spin-button { 427 | height: auto; 428 | } 429 | 430 | [type=search] { 431 | outline-offset: -2px; 432 | -webkit-appearance: textfield; 433 | } 434 | 435 | [type="tel"], 436 | [type="url"], 437 | [type="email"], 438 | [type="number"] { 439 | direction: ltr; 440 | } 441 | ::-webkit-search-decoration { 442 | -webkit-appearance: none; 443 | } 444 | 445 | ::-webkit-color-swatch-wrapper { 446 | padding: 0; 447 | } 448 | 449 | ::-webkit-file-upload-button { 450 | font: inherit; 451 | } 452 | 453 | ::file-selector-button { 454 | font: inherit; 455 | } 456 | 457 | ::-webkit-file-upload-button { 458 | font: inherit; 459 | -webkit-appearance: button; 460 | } 461 | 462 | output { 463 | display: inline-block; 464 | } 465 | 466 | iframe { 467 | border: 0; 468 | } 469 | 470 | summary { 471 | display: list-item; 472 | cursor: pointer; 473 | } 474 | 475 | progress { 476 | vertical-align: baseline; 477 | } 478 | 479 | [hidden] { 480 | display: none !important; 481 | } 482 | /*# sourceMappingURL=bootstrap-reboot.rtl.css.map */ -------------------------------------------------------------------------------- /media/css/bootstrap-reboot.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v5.1.3 (https://getbootstrap.com/) 3 | * Copyright 2011-2021 The Bootstrap Authors 4 | * Copyright 2011-2021 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */ 8 | :root { 9 | --bs-blue: #0d6efd; 10 | --bs-indigo: #6610f2; 11 | --bs-purple: #6f42c1; 12 | --bs-pink: #d63384; 13 | --bs-red: #dc3545; 14 | --bs-orange: #fd7e14; 15 | --bs-yellow: #ffc107; 16 | --bs-green: #198754; 17 | --bs-teal: #20c997; 18 | --bs-cyan: #0dcaf0; 19 | --bs-white: #fff; 20 | --bs-gray: #6c757d; 21 | --bs-gray-dark: #343a40; 22 | --bs-gray-100: #f8f9fa; 23 | --bs-gray-200: #e9ecef; 24 | --bs-gray-300: #dee2e6; 25 | --bs-gray-400: #ced4da; 26 | --bs-gray-500: #adb5bd; 27 | --bs-gray-600: #6c757d; 28 | --bs-gray-700: #495057; 29 | --bs-gray-800: #343a40; 30 | --bs-gray-900: #212529; 31 | --bs-primary: #0d6efd; 32 | --bs-secondary: #6c757d; 33 | --bs-success: #198754; 34 | --bs-info: #0dcaf0; 35 | --bs-warning: #ffc107; 36 | --bs-danger: #dc3545; 37 | --bs-light: #f8f9fa; 38 | --bs-dark: #212529; 39 | --bs-primary-rgb: 13, 110, 253; 40 | --bs-secondary-rgb: 108, 117, 125; 41 | --bs-success-rgb: 25, 135, 84; 42 | --bs-info-rgb: 13, 202, 240; 43 | --bs-warning-rgb: 255, 193, 7; 44 | --bs-danger-rgb: 220, 53, 69; 45 | --bs-light-rgb: 248, 249, 250; 46 | --bs-dark-rgb: 33, 37, 41; 47 | --bs-white-rgb: 255, 255, 255; 48 | --bs-black-rgb: 0, 0, 0; 49 | --bs-body-color-rgb: 33, 37, 41; 50 | --bs-body-bg-rgb: 255, 255, 255; 51 | --bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 52 | --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 53 | --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); 54 | --bs-body-font-family: var(--bs-font-sans-serif); 55 | --bs-body-font-size: 1rem; 56 | --bs-body-font-weight: 400; 57 | --bs-body-line-height: 1.5; 58 | --bs-body-color: #212529; 59 | --bs-body-bg: #fff; 60 | } 61 | 62 | *, 63 | *::before, 64 | *::after { 65 | box-sizing: border-box; 66 | } 67 | 68 | @media (prefers-reduced-motion: no-preference) { 69 | :root { 70 | scroll-behavior: smooth; 71 | } 72 | } 73 | 74 | body { 75 | margin: 0; 76 | font-family: var(--bs-body-font-family); 77 | font-size: var(--bs-body-font-size); 78 | font-weight: var(--bs-body-font-weight); 79 | line-height: var(--bs-body-line-height); 80 | color: var(--bs-body-color); 81 | text-align: var(--bs-body-text-align); 82 | background-color: var(--bs-body-bg); 83 | -webkit-text-size-adjust: 100%; 84 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 85 | } 86 | 87 | hr { 88 | margin: 1rem 0; 89 | color: inherit; 90 | background-color: currentColor; 91 | border: 0; 92 | opacity: 0.25; 93 | } 94 | 95 | hr:not([size]) { 96 | height: 1px; 97 | } 98 | 99 | h6, h5, h4, h3, h2, h1 { 100 | margin-top: 0; 101 | margin-bottom: 0.5rem; 102 | font-weight: 500; 103 | line-height: 1.2; 104 | } 105 | 106 | h1 { 107 | font-size: calc(1.375rem + 1.5vw); 108 | } 109 | @media (min-width: 1200px) { 110 | h1 { 111 | font-size: 2.5rem; 112 | } 113 | } 114 | 115 | h2 { 116 | font-size: calc(1.325rem + 0.9vw); 117 | } 118 | @media (min-width: 1200px) { 119 | h2 { 120 | font-size: 2rem; 121 | } 122 | } 123 | 124 | h3 { 125 | font-size: calc(1.3rem + 0.6vw); 126 | } 127 | @media (min-width: 1200px) { 128 | h3 { 129 | font-size: 1.75rem; 130 | } 131 | } 132 | 133 | h4 { 134 | font-size: calc(1.275rem + 0.3vw); 135 | } 136 | @media (min-width: 1200px) { 137 | h4 { 138 | font-size: 1.5rem; 139 | } 140 | } 141 | 142 | h5 { 143 | font-size: 1.25rem; 144 | } 145 | 146 | h6 { 147 | font-size: 1rem; 148 | } 149 | 150 | p { 151 | margin-top: 0; 152 | margin-bottom: 1rem; 153 | } 154 | 155 | abbr[title], 156 | abbr[data-bs-original-title] { 157 | -webkit-text-decoration: underline dotted; 158 | text-decoration: underline dotted; 159 | cursor: help; 160 | -webkit-text-decoration-skip-ink: none; 161 | text-decoration-skip-ink: none; 162 | } 163 | 164 | address { 165 | margin-bottom: 1rem; 166 | font-style: normal; 167 | line-height: inherit; 168 | } 169 | 170 | ol, 171 | ul { 172 | padding-left: 2rem; 173 | } 174 | 175 | ol, 176 | ul, 177 | dl { 178 | margin-top: 0; 179 | margin-bottom: 1rem; 180 | } 181 | 182 | ol ol, 183 | ul ul, 184 | ol ul, 185 | ul ol { 186 | margin-bottom: 0; 187 | } 188 | 189 | dt { 190 | font-weight: 700; 191 | } 192 | 193 | dd { 194 | margin-bottom: 0.5rem; 195 | margin-left: 0; 196 | } 197 | 198 | blockquote { 199 | margin: 0 0 1rem; 200 | } 201 | 202 | b, 203 | strong { 204 | font-weight: bolder; 205 | } 206 | 207 | small { 208 | font-size: 0.875em; 209 | } 210 | 211 | mark { 212 | padding: 0.2em; 213 | background-color: #fcf8e3; 214 | } 215 | 216 | sub, 217 | sup { 218 | position: relative; 219 | font-size: 0.75em; 220 | line-height: 0; 221 | vertical-align: baseline; 222 | } 223 | 224 | sub { 225 | bottom: -0.25em; 226 | } 227 | 228 | sup { 229 | top: -0.5em; 230 | } 231 | 232 | a { 233 | color: #0d6efd; 234 | text-decoration: underline; 235 | } 236 | a:hover { 237 | color: #0a58ca; 238 | } 239 | 240 | a:not([href]):not([class]), a:not([href]):not([class]):hover { 241 | color: inherit; 242 | text-decoration: none; 243 | } 244 | 245 | pre, 246 | code, 247 | kbd, 248 | samp { 249 | font-family: var(--bs-font-monospace); 250 | font-size: 1em; 251 | direction: ltr /* rtl:ignore */; 252 | unicode-bidi: bidi-override; 253 | } 254 | 255 | pre { 256 | display: block; 257 | margin-top: 0; 258 | margin-bottom: 1rem; 259 | overflow: auto; 260 | font-size: 0.875em; 261 | } 262 | pre code { 263 | font-size: inherit; 264 | color: inherit; 265 | word-break: normal; 266 | } 267 | 268 | code { 269 | font-size: 0.875em; 270 | color: #d63384; 271 | word-wrap: break-word; 272 | } 273 | a > code { 274 | color: inherit; 275 | } 276 | 277 | kbd { 278 | padding: 0.2rem 0.4rem; 279 | font-size: 0.875em; 280 | color: #fff; 281 | background-color: #212529; 282 | border-radius: 0.2rem; 283 | } 284 | kbd kbd { 285 | padding: 0; 286 | font-size: 1em; 287 | font-weight: 700; 288 | } 289 | 290 | figure { 291 | margin: 0 0 1rem; 292 | } 293 | 294 | img, 295 | svg { 296 | vertical-align: middle; 297 | } 298 | 299 | table { 300 | caption-side: bottom; 301 | border-collapse: collapse; 302 | } 303 | 304 | caption { 305 | padding-top: 0.5rem; 306 | padding-bottom: 0.5rem; 307 | color: #6c757d; 308 | text-align: left; 309 | } 310 | 311 | th { 312 | text-align: inherit; 313 | text-align: -webkit-match-parent; 314 | } 315 | 316 | thead, 317 | tbody, 318 | tfoot, 319 | tr, 320 | td, 321 | th { 322 | border-color: inherit; 323 | border-style: solid; 324 | border-width: 0; 325 | } 326 | 327 | label { 328 | display: inline-block; 329 | } 330 | 331 | button { 332 | border-radius: 0; 333 | } 334 | 335 | button:focus:not(:focus-visible) { 336 | outline: 0; 337 | } 338 | 339 | input, 340 | button, 341 | select, 342 | optgroup, 343 | textarea { 344 | margin: 0; 345 | font-family: inherit; 346 | font-size: inherit; 347 | line-height: inherit; 348 | } 349 | 350 | button, 351 | select { 352 | text-transform: none; 353 | } 354 | 355 | [role=button] { 356 | cursor: pointer; 357 | } 358 | 359 | select { 360 | word-wrap: normal; 361 | } 362 | select:disabled { 363 | opacity: 1; 364 | } 365 | 366 | [list]::-webkit-calendar-picker-indicator { 367 | display: none; 368 | } 369 | 370 | button, 371 | [type=button], 372 | [type=reset], 373 | [type=submit] { 374 | -webkit-appearance: button; 375 | } 376 | button:not(:disabled), 377 | [type=button]:not(:disabled), 378 | [type=reset]:not(:disabled), 379 | [type=submit]:not(:disabled) { 380 | cursor: pointer; 381 | } 382 | 383 | ::-moz-focus-inner { 384 | padding: 0; 385 | border-style: none; 386 | } 387 | 388 | textarea { 389 | resize: vertical; 390 | } 391 | 392 | fieldset { 393 | min-width: 0; 394 | padding: 0; 395 | margin: 0; 396 | border: 0; 397 | } 398 | 399 | legend { 400 | float: left; 401 | width: 100%; 402 | padding: 0; 403 | margin-bottom: 0.5rem; 404 | font-size: calc(1.275rem + 0.3vw); 405 | line-height: inherit; 406 | } 407 | @media (min-width: 1200px) { 408 | legend { 409 | font-size: 1.5rem; 410 | } 411 | } 412 | legend + * { 413 | clear: left; 414 | } 415 | 416 | ::-webkit-datetime-edit-fields-wrapper, 417 | ::-webkit-datetime-edit-text, 418 | ::-webkit-datetime-edit-minute, 419 | ::-webkit-datetime-edit-hour-field, 420 | ::-webkit-datetime-edit-day-field, 421 | ::-webkit-datetime-edit-month-field, 422 | ::-webkit-datetime-edit-year-field { 423 | padding: 0; 424 | } 425 | 426 | ::-webkit-inner-spin-button { 427 | height: auto; 428 | } 429 | 430 | [type=search] { 431 | outline-offset: -2px; 432 | -webkit-appearance: textfield; 433 | } 434 | 435 | /* rtl:raw: 436 | [type="tel"], 437 | [type="url"], 438 | [type="email"], 439 | [type="number"] { 440 | direction: ltr; 441 | } 442 | */ 443 | ::-webkit-search-decoration { 444 | -webkit-appearance: none; 445 | } 446 | 447 | ::-webkit-color-swatch-wrapper { 448 | padding: 0; 449 | } 450 | 451 | ::-webkit-file-upload-button { 452 | font: inherit; 453 | } 454 | 455 | ::file-selector-button { 456 | font: inherit; 457 | } 458 | 459 | ::-webkit-file-upload-button { 460 | font: inherit; 461 | -webkit-appearance: button; 462 | } 463 | 464 | output { 465 | display: inline-block; 466 | } 467 | 468 | iframe { 469 | border: 0; 470 | } 471 | 472 | summary { 473 | display: list-item; 474 | cursor: pointer; 475 | } 476 | 477 | progress { 478 | vertical-align: baseline; 479 | } 480 | 481 | [hidden] { 482 | display: none !important; 483 | } 484 | 485 | /*# sourceMappingURL=bootstrap-reboot.css.map */ -------------------------------------------------------------------------------- /media/js/fontawesome.js: -------------------------------------------------------------------------------- 1 | window.FontAwesomeKitConfig = {"asyncLoading":{"enabled":false},"autoA11y":{"enabled":true},"baseUrl":"https://ka-f.fontawesome.com","baseUrlKit":"https://kit.fontawesome.com","detectConflictsUntil":null,"iconUploads":{},"id":13007148,"license":"free","method":"css","minify":{"enabled":true},"token":"970829473e","v4FontFaceShim":{"enabled":true},"v4shim":{"enabled":true},"v5FontFaceShim":{"enabled":false},"version":"5.15.4"}; 2 | !function(t){"function"==typeof define&&define.amd?define("kit-loader",t):t()}((function(){"use strict";function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}function e(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function n(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(t);e&&(o=o.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,o)}return n}function o(t){for(var o=1;ot.length)&&(e=t.length);for(var n=0,o=new Array(e);n2&&void 0!==arguments[2]?arguments[2]:function(){},r=e.document||r,i=u.bind(u,r,["fa","fab","fas","far","fal","fad","fak"]),f=Object.keys(t.iconUploads||{}).length>0;t.autoA11y.enabled&&n(i);var s=[{id:"fa-main",addOn:void 0}];t.v4shim&&t.v4shim.enabled&&s.push({id:"fa-v4-shims",addOn:"-v4-shims"}),t.v5FontFaceShim&&t.v5FontFaceShim.enabled&&s.push({id:"fa-v5-font-face",addOn:"-v5-font-face"}),t.v4FontFaceShim&&t.v4FontFaceShim.enabled&&s.push({id:"fa-v4-font-face",addOn:"-v4-font-face"}),f&&s.push({id:"fa-kit-upload",customCss:!0});var d=s.map((function(n){return new _((function(r,i){F(n.customCss?a(t):c(t,{addOn:n.addOn,minify:t.minify.enabled}),e).then((function(i){r(U(i,o(o({},e),{},{baseUrl:t.baseUrl,version:t.version,id:n.id,contentFilter:function(t,e){return P(t,e.baseUrl,e.version)}})))})).catch(i)}))}));return _.all(d)}function U(t,e){var n=e.contentFilter||function(t,e){return t},o=document.createElement("style"),r=document.createTextNode(n(t,e));return o.appendChild(r),o.media="all",e.id&&o.setAttribute("id",e.id),e&&e.detectingConflicts&&e.detectionIgnoreAttr&&o.setAttributeNode(document.createAttribute(e.detectionIgnoreAttr)),o}function k(t,e){e.autoA11y=t.autoA11y.enabled,"pro"===t.license&&(e.autoFetchSvg=!0,e.fetchSvgFrom=t.baseUrl+"/releases/"+("latest"===t.version?"latest":"v".concat(t.version))+"/svgs",e.fetchUploadedSvgFrom=t.uploadsUrl);var n=[];return t.v4shim.enabled&&n.push(new _((function(n,r){F(c(t,{addOn:"-v4-shims",minify:t.minify.enabled}),e).then((function(t){n(I(t,o(o({},e),{},{id:"fa-v4-shims"})))})).catch(r)}))),n.push(new _((function(n,r){F(c(t,{minify:t.minify.enabled}),e).then((function(t){var r=I(t,o(o({},e),{},{id:"fa-main"}));n(function(t,e){var n=e&&void 0!==e.autoFetchSvg?e.autoFetchSvg:void 0,o=e&&void 0!==e.autoA11y?e.autoA11y:void 0;void 0!==o&&t.setAttribute("data-auto-a11y",o?"true":"false");n&&(t.setAttributeNode(document.createAttribute("data-auto-fetch-svg")),t.setAttribute("data-fetch-svg-from",e.fetchSvgFrom),t.setAttribute("data-fetch-uploaded-svg-from",e.fetchUploadedSvgFrom));return t}(r,e))})).catch(r)}))),_.all(n)}function I(t,e){var n=document.createElement("SCRIPT"),o=document.createTextNode(t);return n.appendChild(o),n.referrerPolicy="strict-origin",e.id&&n.setAttribute("id",e.id),e&&e.detectingConflicts&&e.detectionIgnoreAttr&&n.setAttributeNode(document.createAttribute(e.detectionIgnoreAttr)),n}function L(t){var e,n=[],o=document,r=o.documentElement.doScroll,i=(r?/^loaded|^c/:/^loaded|^i|^c/).test(o.readyState);i||o.addEventListener("DOMContentLoaded",e=function(){for(o.removeEventListener("DOMContentLoaded",e),i=1;e=n.shift();)e()}),i?setTimeout(t,0):n.push(t)}function T(t){"undefined"!=typeof MutationObserver&&new MutationObserver(t).observe(document,{childList:!0,subtree:!0})}try{if(window.FontAwesomeKitConfig){var x=window.FontAwesomeKitConfig,M={detectingConflicts:x.detectConflictsUntil&&new Date<=new Date(x.detectConflictsUntil),detectionIgnoreAttr:"data-fa-detection-ignore",fetch:window.fetch,token:x.token,XMLHttpRequest:window.XMLHttpRequest,document:document},D=document.currentScript,N=D?D.parentElement:document.head;(function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return"js"===t.method?k(t,e):"css"===t.method?C(t,e,(function(t){L(t),T(t)})):void 0})(x,M).then((function(t){t.map((function(t){try{N.insertBefore(t,D?D.nextSibling:null)}catch(e){N.appendChild(t)}})),M.detectingConflicts&&D&&L((function(){D.setAttributeNode(document.createAttribute(M.detectionIgnoreAttr));var t=function(t,e){var n=document.createElement("script");return e&&e.detectionIgnoreAttr&&n.setAttributeNode(document.createAttribute(e.detectionIgnoreAttr)),n.src=c(t,{baseFilename:"conflict-detection",fileSuffix:"js",subdir:"js",minify:t.minify.enabled}),n}(x,M);document.body.appendChild(t)}))})).catch((function(t){console.error("".concat("Font Awesome Kit:"," ").concat(t))}))}}catch(t){console.error("".concat("Font Awesome Kit:"," ").concat(t))}})); 3 | -------------------------------------------------------------------------------- /src/commands/dashboards.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { load } from './../settings'; 3 | import { DashboardModuleTreeItem, DashboardPageTreeItem, DashboardSessionPageTreeItem, DashboardTreeItem } from './../dashboard-treeview'; 4 | import { Container } from '../container'; 5 | const path = require('path'); 6 | import * as fs from 'fs'; 7 | import { tmpdir } from './utils'; 8 | 9 | let files: Array = []; 10 | 11 | export const registerDashboardCommands = (context: vscode.ExtensionContext) => { 12 | vscode.commands.registerCommand('powershell-universal.manageDashboards', () => manageDashboardsCommand(context)); 13 | vscode.commands.registerCommand('powershell-universal.viewDashboard', (item) => viewDashboardCommand(item, context)); 14 | vscode.commands.registerCommand('powershell-universal.stopDashboard', stopDashboardCommand); 15 | vscode.commands.registerCommand('powershell-universal.addDashboardPage', addDashboardPageCommand); 16 | vscode.commands.registerCommand('powershell-universal.deleteDashboardPage', deleteDashboardPageCommand); 17 | vscode.commands.registerCommand('powershell-universal.openDashboardTerminal', openDashboardTerminalCommand); 18 | vscode.commands.registerCommand('powershell-universal.startDashboard', startDashboardCommand); 19 | vscode.commands.registerCommand('powershell-universal.restartDashboard', restartDashboardCommand); 20 | vscode.commands.registerCommand('powershell-universal.openDashboardFile', openFileCommand); 21 | vscode.commands.registerCommand('powershell-universal.openDashboardPageFile', openPageFile); 22 | vscode.commands.registerCommand('powershell-universal.openDashboardConfigFile', openDashboardConfigFileCommand); 23 | vscode.commands.registerCommand('powershell-universal.openDashboardModuleFile', openDashboardModuleFileCommand); 24 | vscode.commands.registerCommand('powershell-universal.viewDashboardLog', viewDashboardLogCommand); 25 | 26 | vscode.workspace.onDidSaveTextDocument(async (file) => { 27 | if (file.fileName.includes('.universal.code.dashboardPage')) { 28 | const info = files.find(x => x.filePath.toLowerCase() === file.fileName.toLowerCase()); 29 | 30 | if (!info) { 31 | vscode.window.showErrorMessage(`File from a previous session. Re-open file from the Activity Bar.`); 32 | return; 33 | } 34 | 35 | const dashboards = await Container.universal.getDashboards(); 36 | const dashboard = dashboards.find(x => x.name === info.dashboardName); 37 | if (!dashboard) { 38 | vscode.window.showErrorMessage(`Dashboard ${info.dashboardName} not found.`); 39 | return; 40 | } 41 | 42 | const pages = await Container.universal.getDashboardPages(dashboard.id); 43 | const page = pages.find(x => x.name === info.name); 44 | 45 | if (!page) { 46 | vscode.window.showErrorMessage(`Page ${info.name} not found.`); 47 | return; 48 | } else { 49 | const codePath = path.join(tmpdir(), '.universal.code.dashboardPage'); 50 | const fileName = file.fileName.toLocaleLowerCase().replace(codePath.toLocaleLowerCase(), "").substring(1); 51 | const filePath = path.join("dashboards", info.dashboardName, "pages", info.name + ".ps1"); 52 | await Container.universal.saveFileContent(filePath, file.getText()); 53 | await Container.universal.deployApp(dashboard.id); 54 | } 55 | } 56 | else if (file.fileName.includes('.universal.code.dashboardModule')) { 57 | const info = files.find(x => x.filePath.toLowerCase() === file.fileName.toLowerCase()); 58 | 59 | if (!info) { 60 | vscode.window.showErrorMessage(`File from a previous session. Re-open file from the Activity Bar.`); 61 | return; 62 | } 63 | 64 | const dashboards = await Container.universal.getDashboards(); 65 | const dashboard = dashboards.find(x => x.name === info.name); 66 | 67 | if (dashboard) { 68 | dashboard.moduleContent = file.getText(); 69 | Container.universal.saveDashboard(dashboard.id, dashboard); 70 | } else { 71 | vscode.window.showErrorMessage(`Dashboard ${info.name} not found.`); 72 | } 73 | } 74 | else if (file.fileName.includes('.universal.code.dashboard')) { 75 | const info = files.find(x => x.filePath.toLowerCase() === file.fileName.toLowerCase()); 76 | 77 | if (!info) { 78 | vscode.window.showErrorMessage(`File from a previous session. Re-open file from the Activity Bar.`); 79 | return; 80 | } 81 | 82 | const dashboards = await Container.universal.getDashboards(); 83 | const dashboard = dashboards.find(x => x.name === info.name); 84 | 85 | if (dashboard) { 86 | dashboard.content = file.getText(); 87 | Container.universal.saveDashboard(dashboard.id, dashboard); 88 | } else { 89 | vscode.window.showErrorMessage(`Dashboard ${info.name} not found.`); 90 | } 91 | } 92 | }); 93 | 94 | vscode.workspace.onDidCloseTextDocument((file) => { 95 | files = files.filter(x => x.filePath !== file.fileName); 96 | }); 97 | } 98 | 99 | export const manageDashboardsCommand = async (context: vscode.ExtensionContext) => { 100 | const settings = load(); 101 | 102 | const connectionName = context.globalState.get("universal.connection"); 103 | var url = settings.url; 104 | 105 | if (connectionName && connectionName !== 'Default') { 106 | const connection = settings.connections.find(m => m.name === connectionName); 107 | if (connection) { 108 | url = connection.url; 109 | } 110 | } 111 | 112 | vscode.env.openExternal(vscode.Uri.parse(`${url}/admin/apps`)); 113 | } 114 | 115 | export const openDashboardTerminalCommand = async (pageInfo: DashboardSessionPageTreeItem, context: vscode.ExtensionContext) => { 116 | const writeEmitter = new vscode.EventEmitter(); 117 | 118 | var str = ''; 119 | const pty: vscode.Pseudoterminal = { 120 | onDidWrite: writeEmitter.event, 121 | open: async () => { 122 | var output = await Container.universal.executeDashboardTerminal(pageInfo.dashboardId, pageInfo.sessionId, pageInfo.pageId, 'prompt'); 123 | writeEmitter.fire(output.replace(/\r\n$/, '')); 124 | }, 125 | close: () => { }, 126 | handleInput: async data => { 127 | 128 | if (data.charCodeAt(0) === 127) { 129 | str = str.slice(0, -1); 130 | writeEmitter.fire('\b \b'); 131 | return; 132 | } 133 | else { 134 | writeEmitter.fire(data); 135 | str += data; 136 | } 137 | 138 | if (data === '\r') { 139 | writeEmitter.fire('\r\n'); 140 | var output = await Container.universal.executeDashboardTerminal(pageInfo.dashboardId, pageInfo.sessionId, pageInfo.pageId, str); 141 | writeEmitter.fire(output.replace("\r", '\r\n')); 142 | var output = await Container.universal.executeDashboardTerminal(pageInfo.dashboardId, pageInfo.sessionId, pageInfo.pageId, 'prompt'); 143 | writeEmitter.fire(output.replace(/\r\n$/, '')); 144 | str = ''; 145 | } 146 | } 147 | }; 148 | const terminal = vscode.window.createTerminal({ name: `App Terminal (${pageInfo.sessionId} \\ ${pageInfo.pageId})`, pty }); 149 | terminal.show(); 150 | }; 151 | 152 | export const viewDashboardCommand = async (dashboard: DashboardTreeItem, context: vscode.ExtensionContext) => { 153 | const settings = load(); 154 | 155 | const connectionName = context.globalState.get("universal.connection"); 156 | var url = settings.url; 157 | 158 | if (connectionName && connectionName !== 'Default') { 159 | const connection = settings.connections.find(m => m.name === connectionName); 160 | if (connection) { 161 | url = connection.url; 162 | } 163 | } 164 | 165 | if (!dashboard.dashboard.baseUrl.startsWith('/')) { 166 | dashboard.dashboard.baseUrl = '/' + dashboard.dashboard.baseUrl; 167 | } 168 | 169 | vscode.env.openExternal(vscode.Uri.parse(`${url}${dashboard.dashboard.baseUrl}`)); 170 | } 171 | 172 | export const addDashboardPageCommand = async (dashboard: DashboardTreeItem) => { 173 | var name = await vscode.window.showInputBox({ 174 | prompt: "Enter the name of the page", 175 | }); 176 | 177 | if (name) { 178 | await Container.universal.addDashboardPage(dashboard.dashboard.id, { 179 | name, 180 | modelId: 0, 181 | dashboardId: dashboard.dashboard.id, 182 | content: "" 183 | }); 184 | vscode.commands.executeCommand('powershell-universal.refreshTreeView'); 185 | } 186 | } 187 | 188 | export const deleteDashboardPageCommand = async (page: DashboardPageTreeItem) => { 189 | var result = await vscode.window.showQuickPick(["Yes", "No"], { 190 | placeHolder: `Are you sure you want to delete ${page.page.name}?` 191 | }); 192 | 193 | if (result === "Yes") { 194 | await Container.universal.deleteDashboardPage(page.page.dashboardId, page.page.modelId); 195 | vscode.commands.executeCommand('powershell-universal.refreshTreeView'); 196 | } 197 | } 198 | 199 | export const stopDashboardCommand = async (dashboard: DashboardTreeItem) => { 200 | await Container.universal.stopDashboard(dashboard.dashboard.id); 201 | } 202 | 203 | export const startDashboardCommand = async (dashboard: DashboardTreeItem) => { 204 | await Container.universal.startDashboard(dashboard.dashboard.id); 205 | } 206 | 207 | export const restartDashboardCommand = async (dashboard: DashboardTreeItem) => { 208 | await Container.universal.stopDashboard(dashboard.dashboard.id); 209 | await Container.universal.startDashboard(dashboard.dashboard.id); 210 | 211 | const d = await Container.universal.getDashboard(dashboard.dashboard.id); 212 | 213 | vscode.commands.executeCommand('powershell-universal.refreshTreeView'); 214 | 215 | vscode.window.showInformationMessage(`Dashboard restarted. Process ID: ${d.processId}`); 216 | 217 | await dashboard.clearLog(); 218 | } 219 | 220 | export const openFileCommand = async (dashboard: DashboardTreeItem) => { 221 | await openFileRemote(dashboard); 222 | } 223 | 224 | export const openFileRemote = async (dashboard: DashboardTreeItem) => { 225 | const os = require('os'); 226 | const codePath = path.join(tmpdir(), '.universal.code.dashboard'); 227 | //Use the id in the path so that we can save the dashboard 228 | const codePathId = path.join(codePath, dashboard.dashboard.id.toString()); 229 | const filePath = path.join(codePathId, dashboard.dashboard.name + ".ps1"); 230 | 231 | const dashboardFile = await Container.universal.getDashboard(dashboard.dashboard.id); 232 | var dirName = path.dirname(filePath); 233 | if (!fs.existsSync(dirName)) { 234 | fs.mkdirSync(dirName, { recursive: true }); 235 | } 236 | 237 | fs.writeFileSync(filePath, dashboardFile.content); 238 | 239 | files.push({ 240 | id: dashboard.dashboard.id, 241 | name: dashboard.dashboard.name, 242 | filePath: filePath 243 | }); 244 | 245 | const textDocument = await vscode.workspace.openTextDocument(filePath); 246 | 247 | vscode.window.showTextDocument(textDocument); 248 | } 249 | 250 | export const openPageFile = async (page: DashboardPageTreeItem) => { 251 | const codePath = path.join(tmpdir(), '.universal.code.dashboardPage'); 252 | //Use the id in the path so that we can save the dashboard 253 | const codePathId = path.join(codePath, page.page.modelId.toString()); 254 | const filePath = path.join(codePathId, page.page.name + ".ps1"); 255 | 256 | const dashboard = await Container.universal.getDashboard(page.page.dashboardId); 257 | var dirName = path.dirname(filePath); 258 | if (!fs.existsSync(dirName)) { 259 | fs.mkdirSync(dirName, { recursive: true }); 260 | } 261 | 262 | const content = await Container.universal.getFileContent(path.join("dashboards", dashboard.name, "pages", page.page.name + ".ps1")); 263 | 264 | fs.writeFileSync(filePath, content.content); 265 | 266 | files.push({ 267 | id: page.page.modelId, 268 | name: page.page.name, 269 | dashboardName: dashboard.name, 270 | dashboardId: page.page.dashboardId, 271 | filePath: filePath 272 | }); 273 | 274 | const textDocument = await vscode.workspace.openTextDocument(filePath); 275 | 276 | vscode.window.showTextDocument(textDocument); 277 | } 278 | 279 | export const openDashboardModuleFileCommand = async (item: DashboardModuleTreeItem) => { 280 | const os = require('os'); 281 | const codePath = path.join(tmpdir(), '.universal.code.dashboardModule'); 282 | 283 | const codePathId = path.join(codePath, item.dashboard.name); 284 | const filePath = path.join(codePathId, item.dashboard.name + ".psm1"); 285 | 286 | const dashboard = await Container.universal.getDashboard(item.dashboard.id); 287 | var dirName = path.dirname(filePath); 288 | if (!fs.existsSync(dirName)) { 289 | fs.mkdirSync(dirName, { recursive: true }); 290 | } 291 | 292 | fs.writeFileSync(filePath, dashboard.moduleContent); 293 | 294 | files.push({ 295 | name: dashboard.name, 296 | dashboardName: dashboard.name, 297 | dashboardId: dashboard.id, 298 | filePath: filePath 299 | }); 300 | 301 | const textDocument = await vscode.workspace.openTextDocument(filePath); 302 | 303 | vscode.window.showTextDocument(textDocument); 304 | } 305 | 306 | export const openDashboardConfigFileCommand = async () => { 307 | const os = require('os'); 308 | 309 | const filePath = path.join(tmpdir(), '.universal.code.configuration', 'dashboards.ps1'); 310 | const codePath = path.join(tmpdir(), '.universal.code.configuration'); 311 | const config = await Container.universal.getConfiguration('dashboards.ps1'); 312 | if (!fs.existsSync(codePath)) { 313 | fs.mkdirSync(codePath); 314 | } 315 | fs.writeFileSync(filePath, config); 316 | 317 | const textDocument = await vscode.workspace.openTextDocument(filePath); 318 | vscode.window.showTextDocument(textDocument); 319 | } 320 | 321 | export const viewDashboardLogCommand = async (item: DashboardTreeItem) => { 322 | await item.showLog(); 323 | } -------------------------------------------------------------------------------- /media/welcome.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 16 | 17 | 18 | 19 | 20 |
21 |
22 |

  PowerShell Universal Extension for VS Code

23 | Getting Started Walkthrough 25 | 26 | Documentation 27 | 28 | Community 29 |
30 | 31 |
32 | 33 |

About PowerShell Universal

34 | 35 |

PowerShell Universal provides an extensive list of features that will help you or your 36 | organization 37 | centralize and secure your automation environment and empower your end users.

38 | 39 |
40 |
41 |

APIs

42 |

Expose scripts as RESTful HTTP APIs for integration from any platform.

43 |
44 |
45 | 46 |
47 | 68 |
69 |
70 | 71 |
72 |
73 |
74 | 75 | 76 |
77 | 78 |
79 |
80 |

Automation

81 |

Execute, schedule, secure and audit scripts in an easy-to-use, web-interface.

82 |
83 |
84 | 85 |
86 | 109 |
110 |
111 | 112 |
113 |
114 |
115 | 116 |
117 | 118 |
119 |
120 |

User Interfaces

121 |

Build web-based tools for internal users with highly interactive user interfaces that 122 | run 123 | your scripts.

124 |
125 |
126 | 127 | 162 | 163 |
164 | 165 |
166 |
167 |

Hosting

168 |

PowerShell Universal is cross-platform and can be hosted on-premise, in the cloud or 169 | even on 170 | a Raspberry Pi.

171 |
172 |
173 | 174 |
175 |
176 | 190 |
191 |
192 |
193 | 194 |
195 |
196 |
197 | 198 |
199 | 200 |
201 |
202 |

Security

203 |

Grant role-based access to different aspects of your automation environment with your 204 | choice 205 | of authentication and authorization integrations.

206 |
207 |
208 | 209 |
210 | 236 |
237 |
238 | 239 |
240 |
241 |
242 | 243 |
244 | 245 | 246 |
247 |
248 |

Development

249 |

Take advantage of rich development tools such as IntelliSense, code formatting, error 250 | checking and debugger integration without leaving your browser.

251 |
252 |
253 | 254 | 285 | 286 |
287 | 288 | 289 |
290 |
291 |

Platform

292 |

Configure the platform to meet the needs of your environment.

293 |
294 |
295 | 296 |
297 | 316 |
317 |
318 | 319 |
320 |
321 |
322 | 323 |
324 | 325 |
326 |
327 |

Community

328 |

Join the growing community of users managing their automation environments with 329 | PowerShell 330 | Universal.

331 |
332 |
333 | 334 |
335 |
336 | 344 |
345 |
346 |
347 | 348 |
349 |
350 |
351 |
352 | 353 | 354 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "powershell-universal", 3 | "displayName": "PowerShell Universal", 4 | "description": "Visual Studio Code tools for PowerShell Universal", 5 | "publisher": "ironmansoftware", 6 | "version": "5.6.2", 7 | "engines": { 8 | "vscode": "^1.72.0" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/ironmansoftware/universal-code" 13 | }, 14 | "categories": [ 15 | "Other" 16 | ], 17 | "icon": "logo.png", 18 | "qna": "marketplace", 19 | "homepage": "https://ironmansoftware.com/powershell-universal", 20 | "bugs": { 21 | "url": "https://github.com/ironmansoftware/universal-code/issues" 22 | }, 23 | "keywords": [ 24 | "powershell", 25 | "universal dashboard", 26 | "powershell universal", 27 | "universal automation" 28 | ], 29 | "activationEvents": [ 30 | "onUri" 31 | ], 32 | "main": "./out/extension.js", 33 | "contributes": { 34 | "commands": [ 35 | { 36 | "command": "powershell-universal.welcome", 37 | "title": "Welcome" 38 | }, 39 | { 40 | "command": "powershell-universal.walkthrough", 41 | "title": "PowerShell Universal: Getting Started Walkthrough" 42 | }, 43 | { 44 | "command": "powershell-universal.attachRunspace", 45 | "title": "Attach Runspace", 46 | "icon": "$(debug-alt)" 47 | }, 48 | { 49 | "command": "powershell-universal.addConnection", 50 | "title": "Add Connection", 51 | "icon": "$(add)" 52 | }, 53 | { 54 | "command": "powershell-universal.connection", 55 | "title": "Connect to Instance", 56 | "icon": "$(remote)" 57 | }, 58 | { 59 | "command": "powershell-universal.reconnect", 60 | "title": "Reconnect to Instance", 61 | "icon": "$(refresh)" 62 | }, 63 | { 64 | "command": "powershell-universal.reloadConfig", 65 | "title": "Reload Configuration", 66 | "icon": "$(debug-restart)" 67 | }, 68 | { 69 | "command": "powershell-universal.refreshAllTreeViews", 70 | "title": "Refresh", 71 | "icon": "$(refresh)" 72 | }, 73 | { 74 | "command": "powershell-universal.refreshTreeView", 75 | "title": "Refresh", 76 | "icon": "$(refresh)" 77 | }, 78 | { 79 | "command": "powershell-universal.refreshEndpointTreeView", 80 | "title": "Refresh", 81 | "icon": "$(refresh)" 82 | }, 83 | { 84 | "command": "powershell-universal.refreshScriptTreeView", 85 | "title": "Refresh", 86 | "icon": "$(refresh)" 87 | }, 88 | { 89 | "command": "powershell-universal.refreshConfigurationTreeView", 90 | "title": "Refresh", 91 | "icon": "$(refresh)" 92 | }, 93 | { 94 | "command": "powershell-universal.refreshConnectionTreeView", 95 | "title": "Refresh", 96 | "icon": "$(refresh)" 97 | }, 98 | { 99 | "command": "powershell-universal.refreshPlatformTreeView", 100 | "title": "Refresh", 101 | "icon": "$(refresh)" 102 | }, 103 | { 104 | "command": "powershell-universal.viewDashboard", 105 | "title": "View", 106 | "icon": "$(open-preview)" 107 | }, 108 | { 109 | "command": "powershell-universal.restartDashboard", 110 | "title": "Restart", 111 | "icon": "$(debug-restart)" 112 | }, 113 | { 114 | "command": "powershell-universal.openDashboardFile", 115 | "title": "Open App File", 116 | "icon": "$(go-to-file)" 117 | }, 118 | { 119 | "command": "powershell-universal.addDashboardPage", 120 | "title": "Add Page", 121 | "icon": "$(add)" 122 | }, 123 | { 124 | "command": "powershell-universal.deleteDashboardPage", 125 | "title": "Delete Page", 126 | "icon": "$(trash)" 127 | }, 128 | { 129 | "command": "powershell-universal.openDashboardPageFile", 130 | "title": "Open App Page File", 131 | "icon": "$(go-to-file)" 132 | }, 133 | { 134 | "command": "powershell-universal.downloadUniversal", 135 | "title": "PowerShell Universal: Download", 136 | "icon": "$(desktop-download)" 137 | }, 138 | { 139 | "command": "powershell-universal.viewDashboardLog", 140 | "title": "View Log", 141 | "icon": "$(file)" 142 | }, 143 | { 144 | "command": "powershell-universal.openDashboardTerminal", 145 | "title": "Open Terminal", 146 | "icon": "$(debug-console)" 147 | }, 148 | { 149 | "command": "powershell-universal.openTerminal", 150 | "title": "Open Terminal", 151 | "icon": "$(debug-console)" 152 | }, 153 | { 154 | "command": "powershell-universal.startUniversal", 155 | "title": "PowerShell Universal: Start Local Development", 156 | "icon": "$(play)" 157 | }, 158 | { 159 | "command": "powershell-universal.clearLocalDatabase", 160 | "title": "PowerShell Universal: Clear Local Development Database", 161 | "icon": "$(delete)" 162 | }, 163 | { 164 | "command": "powershell-universal.connectLocalDevModule", 165 | "title": "PowerShell Universal: Connect Local Development Module" 166 | }, 167 | { 168 | "command": "powershell-universal.manageDashboards", 169 | "title": "PowerShell Universal: Manage Apps", 170 | "icon": "$(link-external)" 171 | }, 172 | { 173 | "command": "powershell-universal.help", 174 | "title": "Help", 175 | "icon": "$(book)" 176 | }, 177 | { 178 | "command": "powershell-universal.openEndpointScriptBlock", 179 | "title": "Edit Endpoint", 180 | "icon": "$(go-to-file)" 181 | }, 182 | { 183 | "command": "powershell-universal.openEndpointConfigFile", 184 | "title": "Open endpoints.ps1", 185 | "icon": "$(go-to-file)" 186 | }, 187 | { 188 | "command": "powershell-universal.openDashboardConfigFile", 189 | "title": "Open dashboards.ps1", 190 | "icon": "$(go-to-file)" 191 | }, 192 | { 193 | "command": "powershell-universal.openDashboardModuleFile", 194 | "title": "Open App Module", 195 | "icon": "$(go-to-file)" 196 | }, 197 | { 198 | "command": "powershell-universal.openScriptConfigFile", 199 | "title": "Open scripts.ps1", 200 | "icon": "$(go-to-file)" 201 | }, 202 | { 203 | "command": "powershell-universal.openConfigFile", 204 | "title": "Open file", 205 | "icon": "$(go-to-file)" 206 | }, 207 | { 208 | "command": "powershell-universal.insertRestMethod", 209 | "title": "Insert Invoke-RestMethod to Console", 210 | "icon": "$(terminal)" 211 | }, 212 | { 213 | "command": "powershell-universal.invokeScript", 214 | "title": "Run Script", 215 | "icon": "$(play)" 216 | }, 217 | { 218 | "command": "powershell-universal.manageEndpoints", 219 | "title": "PowerShell Universal: Manage APIs", 220 | "icon": "$(link-external)" 221 | }, 222 | { 223 | "command": "powershell-universal.manageScripts", 224 | "title": "PowerShell Universal: Manage Scripts", 225 | "icon": "$(link-external)" 226 | }, 227 | { 228 | "command": "powershell-universal.editScript", 229 | "title": "PowerShell Universal: Edit Script", 230 | "icon": "$(go-to-file)" 231 | }, 232 | { 233 | "command": "powershell-universal.connect", 234 | "title": "Connect", 235 | "icon": "$(debug-disconnect)" 236 | }, 237 | { 238 | "command": "powershell-universal.viewJob", 239 | "title": "View Job", 240 | "icon": "$(link-external)" 241 | }, 242 | { 243 | "command": "powershell-universal.viewJobLog", 244 | "title": "View Job Log", 245 | "icon": "$(book)" 246 | }, 247 | { 248 | "command": "powershell-universal.getJobPipelineOutput", 249 | "title": "Get Job Pipeline Output", 250 | "icon": "$(terminal-powershell)" 251 | }, 252 | { 253 | "command": "powershell-universal.newConfigFile", 254 | "title": "New File", 255 | "icon": "$(new-file)" 256 | }, 257 | { 258 | "command": "powershell-universal.newConfigFolder", 259 | "title": "New Folder", 260 | "icon": "$(new-folder)" 261 | }, 262 | { 263 | "command": "powershell-universal.newModule", 264 | "title": "New Module", 265 | "icon": "$(add)" 266 | }, 267 | { 268 | "command": "powershell-universal.editModule", 269 | "title": "Edit Module", 270 | "icon": "$(go-to-file)" 271 | }, 272 | { 273 | "command": "powershell-universal.connectDebugger", 274 | "title": "Connect Debugger", 275 | "icon": "$(debug)" 276 | }, 277 | { 278 | "command": "powershell-universal.connectDebugger", 279 | "title": "Connect Debugger", 280 | "icon": "$(debug)" 281 | }, 282 | { 283 | "command": "powershell-universal.disconnectDebugger", 284 | "title": "Disconnect Debugger", 285 | "icon": "$(debug-disconnect)" 286 | } 287 | ], 288 | "debuggers": [ 289 | { 290 | "type": "powershelluniversal", 291 | "label": "PowerShell Universal", 292 | "initialConfigurations": [ 293 | { 294 | "type": "powershelluniversal", 295 | "request": "attach", 296 | "name": "Attach PowerShell Universal Debugger" 297 | } 298 | ] 299 | } 300 | ], 301 | "viewsContainers": { 302 | "activitybar": [ 303 | { 304 | "id": "powershellUniversal", 305 | "title": "PowerShell Universal", 306 | "icon": "media/logo.svg" 307 | } 308 | ] 309 | }, 310 | "views": { 311 | "powershellUniversal": [ 312 | { 313 | "id": "universalConnectionProviderView", 314 | "name": "Connections" 315 | }, 316 | { 317 | "id": "universalEndpointProviderView", 318 | "name": "APIs" 319 | }, 320 | { 321 | "id": "universalScriptProviderView", 322 | "name": "Automation" 323 | }, 324 | { 325 | "id": "universalDashboardProviderView", 326 | "name": "Apps" 327 | }, 328 | { 329 | "id": "universalPlatformProviderView", 330 | "name": "Platform" 331 | }, 332 | { 333 | "id": "universalConfigProviderView", 334 | "name": "Configuration" 335 | }, 336 | { 337 | "id": "universalInfoProviderView", 338 | "name": "Help and Information" 339 | } 340 | ] 341 | }, 342 | "configuration": { 343 | "type": "object", 344 | "title": "PowerShell Universal", 345 | "properties": { 346 | "powerShellUniversal.url": { 347 | "type": "string", 348 | "default": "http://localhost:5000", 349 | "description": "The URL of the PowerShell Universal server. To specify multiple connections, use the Connections setting." 350 | }, 351 | "powerShellUniversal.appToken": { 352 | "type": "string", 353 | "default": "", 354 | "description": "The App Token used to connect to PowerShell Universal. To specify multiple connections, use the Connections setting." 355 | }, 356 | "powerShellUniversal.checkModules": { 357 | "type": "boolean", 358 | "default": true, 359 | "description": "Checks module versions and updates them when starting the extension." 360 | }, 361 | "powerShellUniversal.connections": { 362 | "type": "array", 363 | "description": "Specifies an array of connections to PowerShell Universal instances.", 364 | "scope": "machine", 365 | "uniqueItems": true, 366 | "items": { 367 | "type": "object", 368 | "required": [ 369 | "name", 370 | "url" 371 | ], 372 | "properties": { 373 | "name": { 374 | "type": "string", 375 | "description": "The name of this connection." 376 | }, 377 | "url": { 378 | "type": "string", 379 | "description": "The URL of the PowerShell Universal instance." 380 | }, 381 | "appToken": { 382 | "type": "string", 383 | "description": "An app token used to access the PowerShell Universal instance." 384 | }, 385 | "allowInvalidCertificate": { 386 | "type": "boolean", 387 | "description": "Whether to allow an invalid certificate when connecting over HTTPS." 388 | } 389 | } 390 | } 391 | } 392 | } 393 | }, 394 | "menus": { 395 | "view/title": [ 396 | { 397 | "command": "powershell-universal.addConnection", 398 | "when": "view == universalConnectionProviderView", 399 | "group": "navigation" 400 | }, 401 | { 402 | "command": "powershell-universal.refreshConnectionTreeView", 403 | "when": "view == universalConnectionProviderView", 404 | "group": "navigation" 405 | }, 406 | { 407 | "command": "powershell-universal.refreshPlatformTreeView", 408 | "when": "view == universalPlatformProviderView", 409 | "group": "navigation" 410 | }, 411 | { 412 | "command": "powershell-universal.refreshTreeView", 413 | "when": "view == universalDashboardProviderView", 414 | "group": "navigation" 415 | }, 416 | { 417 | "command": "powershell-universal.openEndpointConfigFile", 418 | "when": "view == universalEndpointProviderView", 419 | "group": "navigation" 420 | }, 421 | { 422 | "command": "powershell-universal.openDashboardConfigFile", 423 | "when": "view == universalDashboardProviderView", 424 | "group": "navigation" 425 | }, 426 | { 427 | "command": "powershell-universal.openScriptConfigFile", 428 | "when": "view == universalScriptProviderView", 429 | "group": "navigation" 430 | }, 431 | { 432 | "command": "powershell-universal.refreshEndpointTreeView", 433 | "when": "view == universalEndpointProviderView", 434 | "group": "navigation" 435 | }, 436 | { 437 | "command": "powershell-universal.refreshScriptTreeView", 438 | "when": "view == universalScriptProviderView", 439 | "group": "navigation" 440 | }, 441 | { 442 | "command": "powershell-universal.refreshConfigurationTreeView", 443 | "when": "view == universalConfigProviderView", 444 | "group": "navigation" 445 | }, 446 | { 447 | "command": "powershell-universal.manageDashboards", 448 | "when": "view == universalDashboardProviderView", 449 | "group": "navigation" 450 | }, 451 | { 452 | "command": "powershell-universal.manageEndpoints", 453 | "when": "view == universalEndpointProviderView", 454 | "group": "navigation" 455 | }, 456 | { 457 | "command": "powershell-universal.manageScripts", 458 | "when": "view == universalScriptProviderView", 459 | "group": "navigation" 460 | }, 461 | { 462 | "command": "powershell-universal.reloadConfig", 463 | "when": "view == universalConfigProviderView", 464 | "group": "navigation" 465 | }, 466 | { 467 | "command": "powershell-universal.connectDebugger", 468 | "when": "view == universalPlatformProviderView && !powershell-universal.debuggerConnected", 469 | "group": "navigation" 470 | }, 471 | { 472 | "command": "powershell-universal.disconnectDebugger", 473 | "when": "view == universalPlatformProviderView && powershell-universal.debuggerConnected", 474 | "group": "navigation" 475 | } 476 | ], 477 | "view/item/context": [ 478 | { 479 | "command": "powershell-universal.attachRunspace", 480 | "when": "view == universalPlatformProviderView && viewItem == runspace", 481 | "group": "inline" 482 | }, 483 | { 484 | "command": "powershell-universal.connection", 485 | "when": "view == universalConnectionProviderView && viewItem == connection", 486 | "group": "inline" 487 | }, 488 | { 489 | "command": "powershell-universal.reconnect", 490 | "when": "view == universalConnectionProviderView && viewItem == connection-connected", 491 | "group": "inline" 492 | }, 493 | { 494 | "command": "powershell-universal.viewDashboard", 495 | "when": "view == universalDashboardProviderView && viewItem == dashboard", 496 | "group": "inline" 497 | }, 498 | { 499 | "command": "powershell-universal.addDashboardPage", 500 | "when": "view == universalDashboardProviderView && viewItem == dashboardPages", 501 | "group": "inline" 502 | }, 503 | { 504 | "command": "powershell-universal.deleteDashboardPage", 505 | "when": "view == universalDashboardProviderView && viewItem == dashboardPage", 506 | "group": "inline" 507 | }, 508 | { 509 | "command": "powershell-universal.restartDashboard", 510 | "when": "view == universalDashboardProviderView && viewItem == dashboard", 511 | "group": "inline" 512 | }, 513 | { 514 | "command": "powershell-universal.openDashboardFile", 515 | "when": "view == universalDashboardProviderView && viewItem == dashboard", 516 | "group": "inline" 517 | }, 518 | { 519 | "command": "powershell-universal.openDashboardModuleFile", 520 | "when": "view == universalDashboardProviderView && viewItem == dashboardModule", 521 | "group": "inline" 522 | }, 523 | { 524 | "command": "powershell-universal.openDashboardPageFile", 525 | "when": "view == universalDashboardProviderView && viewItem == dashboardPage", 526 | "group": "inline" 527 | }, 528 | { 529 | "command": "powershell-universal.viewDashboardLog", 530 | "when": "view == universalDashboardProviderView && viewItem == dashboard" 531 | }, 532 | { 533 | "command": "powershell-universal.openDashboardTerminal", 534 | "when": "view == universalDashboardProviderView && viewItem == dashboardSessionPage", 535 | "group": "inline" 536 | }, 537 | { 538 | "command": "powershell-universal.insertRestMethod", 539 | "when": "view == universalEndpointProviderView && viewItem == endpoint", 540 | "group": "inline" 541 | }, 542 | { 543 | "command": "powershell-universal.openEndpointScriptBlock", 544 | "when": "view == universalEndpointProviderView && viewItem == endpoint", 545 | "group": "inline" 546 | }, 547 | { 548 | "command": "powershell-universal.openTerminal", 549 | "when": "view == universalScriptProviderView && viewItem == terminal", 550 | "group": "inline" 551 | }, 552 | { 553 | "command": "powershell-universal.invokeScript", 554 | "when": "view == universalScriptProviderView && viewItem == script", 555 | "group": "inline" 556 | }, 557 | { 558 | "command": "powershell-universal.editScript", 559 | "when": "view == universalScriptProviderView && viewItem == script", 560 | "group": "inline" 561 | }, 562 | { 563 | "command": "powershell-universal.viewJob", 564 | "when": "view == universalScriptProviderView && viewItem == job", 565 | "group": "inline" 566 | }, 567 | { 568 | "command": "powershell-universal.getJobPipelineOutput", 569 | "when": "view == universalScriptProviderView && viewItem == job", 570 | "group": "inline" 571 | }, 572 | { 573 | "command": "powershell-universal.viewJobLog", 574 | "when": "view == universalScriptProviderView && viewItem == job", 575 | "group": "inline" 576 | }, 577 | { 578 | "command": "powershell-universal.openConfigFile", 579 | "when": "view == universalConfigProviderView && viewItem == configFile", 580 | "group": "inline" 581 | }, 582 | { 583 | "command": "powershell-universal.newConfigFile", 584 | "when": "view == universalConfigProviderView && viewItem == configFolder", 585 | "group": "inline" 586 | }, 587 | { 588 | "command": "powershell-universal.newConfigFolder", 589 | "when": "view == universalConfigProviderView && viewItem == configFolder", 590 | "group": "inline" 591 | }, 592 | { 593 | "command": "powershell-universal.newModule", 594 | "when": "view == universalPlatformProviderView && viewItem == customModules", 595 | "group": "inline" 596 | }, 597 | { 598 | "command": "powershell-universal.editModule", 599 | "when": "view == universalPlatformProviderView && viewItem == customModule", 600 | "group": "inline" 601 | } 602 | ] 603 | }, 604 | "walkthroughs": [ 605 | { 606 | "id": "universal.welcome", 607 | "title": "Get Started with PowerShell Universal", 608 | "description": "Install and configure PowerShell Universal", 609 | "steps": [ 610 | { 611 | "id": "universal.welcome.install", 612 | "title": "Install PowerShell Universal", 613 | "description": "Download and install the PowerShell Universal server to get started.", 614 | "media": { 615 | "markdown": "walkthroughs/getting-started/1-install.md" 616 | } 617 | }, 618 | { 619 | "id": "universal.welcome.login", 620 | "title": "Login to PowerShell Universal", 621 | "description": "Login to your PowerShell Universal instance.", 622 | "media": { 623 | "markdown": "walkthroughs/getting-started/2-login.md" 624 | } 625 | }, 626 | { 627 | "id": "universal.welcome.connect", 628 | "title": "Connect VS Code to PowerShell Universal", 629 | "description": "Connect the VS Code extension to your PowerShell Universal server.", 630 | "media": { 631 | "markdown": "walkthroughs/getting-started/3-connect.md" 632 | } 633 | } 634 | ] 635 | } 636 | ] 637 | }, 638 | "scripts": { 639 | "vscode:prepublish": "npm run compile", 640 | "compile": "tsc -p ./", 641 | "lint": "eslint src --ext ts", 642 | "watch": "tsc -watch -p ./", 643 | "pretest": "npm run compile && npm run lint", 644 | "test": "node ./out/test/runTest.js" 645 | }, 646 | "devDependencies": { 647 | "@types/glob": "^7.1.1", 648 | "@types/mocha": "^7.0.2", 649 | "@types/node": "^13.11.0", 650 | "@types/vscode": "^1.46.0", 651 | "@typescript-eslint/eslint-plugin": "^2.30.0", 652 | "@typescript-eslint/parser": "^2.30.0", 653 | "eslint": "^6.8.0", 654 | "glob": "^7.1.6", 655 | "mocha": "^7.1.2", 656 | "typescript": "^3.8.3", 657 | "vscode-test": "^1.3.0" 658 | }, 659 | "dependencies": { 660 | "@microsoft/signalr": "^8.0.7", 661 | "@vscode/debugprotocol": "^1.68.0", 662 | "adm-zip": "^0.4.16", 663 | "atob": "^2.1.2", 664 | "axios": "^0.19.2", 665 | "compare-versions": "^3.6.0", 666 | "progress": "^2.0.3", 667 | "semver": "^7.3.5", 668 | "temp": "^0.9.1", 669 | "yaml": "^1.10.0" 670 | } 671 | } --------------------------------------------------------------------------------