Display Function App nodes in explorer

This includes the basic 'Open In Portal' action
This commit is contained in:
Eric Jizba 2017-09-21 16:21:02 -07:00
Родитель 26543238ba
Коммит 71a8d1be9a
7 изменённых файлов: 89 добавлений и 45 удалений

Просмотреть файл

@ -38,6 +38,11 @@
"light": "resources/light/refresh.svg",
"dark": "resources/dark/refresh.svg"
}
},
{
"command": "azureFunctions.openInPortal",
"title": "Open In Portal",
"category": "Azure Functions"
}
],
"views": {
@ -55,6 +60,18 @@
"when": "view == azureFunctionsExplorer",
"group": "navigation@2"
}
],
"view/item/context": [
{
"command": "azureFunctions.refresh",
"when": "view == azureFunctionsExplorer && viewItem == azureFunctionsSubscription",
"group": "1_functionAppGeneralCommands@2"
},
{
"command": "azureFunctions.openInPortal",
"when": "view == azureFunctionsExplorer && viewItem == azureFunctionsFunctionApp",
"group": "1_subscriptionGeneralCommands@2"
}
]
}
},
@ -73,6 +90,7 @@
"dependencies": {
"archiver": "^2.0.3",
"azure-arm-resource": "^2.0.0-preview",
"azure-arm-website": "^1.0.0-preview",
"ms-rest": "^2.2.2",
"ms-rest-azure": "^2.3.1",
"opn": "^5.1.0",
@ -81,4 +99,4 @@
"extensionDependencies": [
"ms-vscode.azure-account"
]
}
}

Просмотреть файл

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.st0{fill:#2d2d30}</style><path class="icon-canvas-transparent" d="M16 0v16H0V0h16z" id="canvas"/><path class="st0" d="M13 4.201L11.202 6H9.914L13 2.914V2H6.882L4.854 6.056 3 4.201 0 7.202v2.596l3 2.999L5.298 10.5l-.5-.5h1.084l-2 4h2.532l5.894-5.894.394.394-2.001 2L13 12.797l3-2.999V7.202l-3-3.001z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M7.5 7H12l-6 6h-.5l2-4h-3l3-6h4l-4 4zM3 5.615L.116 8.5 3 11.383l.884-.883-2-2 2-2L3 5.615zm10 0l-.884.885 2 2-2 2 .884.883L15.884 8.5 13 5.615z" id="iconBg"/></svg>

После

Ширина:  |  Высота:  |  Размер: 670 B

Просмотреть файл

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-bg{fill:#424242}.st0{fill:#f6f6f6}</style><path class="icon-canvas-transparent" d="M16 0v16H0V0h16z" id="canvas"/><path class="st0" d="M13 4.201L11.202 6H9.914L13 2.914V2H6.882L4.854 6.056 3 4.201 0 7.202v2.596l3 2.999L5.298 10.5l-.5-.5h1.084l-2 4h2.532l5.894-5.894.394.394-2.001 2L13 12.797l3-2.999V7.202l-3-3.001z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M7.5 7H12l-6 6h-.5l2-4h-3l3-6h4l-4 4zM3 5.615L.116 8.5 3 11.383l.884-.883-2-2 2-2L3 5.615zm10 0l-.884.885 2 2-2 2 .884.883L15.884 8.5 13 5.615z" id="iconBg"/></svg>

После

Ширина:  |  Высота:  |  Размер: 670 B

16
src/commands.ts Normal file
Просмотреть файл

@ -0,0 +1,16 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as opn from 'opn';
import { INode } from './nodes';
export class AzureFunctionsCommands {
public static openInPortal(node?: INode) {
if (node && node.tenantId) {
opn(`https://portal.azure.com/${node.tenantId}/#resource${node.id}`);
}
}
}

Просмотреть файл

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { TreeDataProvider, Event, EventEmitter, TreeItem } from 'vscode';
import { LoadingNode, NoSubscriptionsNode, SignInToAzureNode, SubscriptionNode, INode } from './nodes';
import { GenericNode, SubscriptionNode, INode } from './nodes';
import { AzureAccount } from './azure-account.api';
export class AzureFunctionsExplorer implements TreeDataProvider<INode> {
@ -23,11 +23,11 @@ export class AzureFunctionsExplorer implements TreeDataProvider<INode> {
return node.getChildren ? await node.getChildren() : [];
} else { // Root of the explorer
if (this.azureAccount.status === "Initializing" || this.azureAccount.status === "LoggingIn") {
return [new LoadingNode()];
return [new GenericNode('azureFunctionsLoading', 'Loading...')];
} else if (this.azureAccount.status === "LoggedOut") {
return [new SignInToAzureNode()];
return [new GenericNode("azureFunctionsSignInToAzure", "Sign in to Azure...", 'azure-account.login')];
} else if (this.azureAccount.filters.length === 0) {
return [new NoSubscriptionsNode()];
return [new GenericNode("azureFunctionsNoSubscriptions", "No subscriptions found. Edit filters...", 'azure-account.selectSubscriptions')];
} else {
return this.azureAccount.filters.map(filter => new SubscriptionNode(filter))
}

Просмотреть файл

@ -12,6 +12,7 @@ import { AzureAccount } from './azure-account.api';
import { AzureFunctionsExplorer } from './explorer';
import { INode } from './nodes'
import { Reporter } from './telemetry';
import { AzureFunctionsCommands } from './commands';
export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(new Reporter(context));
@ -25,6 +26,7 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.window.registerTreeDataProvider('azureFunctionsExplorer', explorer));
initCommand(context, 'azureFunctions.refresh', (node?: INode) => explorer.refresh(node));
initCommand(context, 'azureFunctions.openInPortal', (node?: INode) => AzureFunctionsCommands.openInPortal(node));
} else {
vscode.window.showErrorMessage("The Azure Account Extension is required for the Azure Functions extension.");
}

Просмотреть файл

@ -4,25 +4,41 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as path from 'path';
import * as WebSiteModels from '../node_modules/azure-arm-website/lib/models';
import { AzureResourceFilter } from './azure-account.api';
import WebSiteManagementClient = require('azure-arm-website');
export interface INode extends vscode.TreeItem {
id: string;
tenantId?: string;
getChildren?(): Promise<INode[]>;
}
export class GenericNode implements INode {
readonly contextValue: string;
readonly command: vscode.Command;
constructor(readonly id: string, readonly label: string, commandId?: string) {
this.contextValue = id;
if (commandId) {
this.command = {
command: commandId,
title: ''
};
}
}
}
export class SubscriptionNode implements INode {
readonly contextValue: string = 'azureFunctionsSubscription';
readonly label: string;
readonly id: string;
readonly collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
constructor(private readonly subscriptionFilter: AzureResourceFilter) {
if (subscriptionFilter.subscription.displayName) {
this.label = subscriptionFilter.subscription.displayName;
} else {
// TODO
}
this.label = subscriptionFilter.subscription.displayName!;
this.id = subscriptionFilter.subscription.subscriptionId!;
}
get iconPath(): any {
@ -31,40 +47,30 @@ export class SubscriptionNode implements INode {
dark: path.join(__filename, '..', '..', '..', 'resources', 'dark', 'AzureSubscription.svg')
};
}
}
export class LoadingNode implements INode {
readonly contextValue: string = 'azureFunctionsLoading';
readonly label: string = "Loading...";
}
export class NoSubscriptionsNode implements INode {
readonly contextValue: string = 'azureFunctionsNoSubscriptions';
readonly label: string = "No subscriptions found. Edit filters...";
readonly command: vscode.Command = {
command: 'azure-account.selectSubscriptions',
title: ''
};
}
export class NoResourcesNode implements INode {
readonly contextValue: string = 'azureFunctionsNoResources';
readonly label: string = "No resources found.";
}
export class SignInToAzureNode implements INode {
readonly contextValue: string = 'azureFunctionsSignInToAzure';
readonly label: string = "Sign in to Azure...";
readonly command: vscode.Command = {
command: 'azure-account.login',
title: ''
};
}
export class ErrorNode implements INode {
readonly contextValue: string = 'azureFunctionsError';
readonly label: string;
constructor(errorMessage: string) {
this.label = `Error: ${errorMessage}`;
async getChildren(): Promise<INode[]> {
const client = new WebSiteManagementClient(this.subscriptionFilter.session.credentials, this.id);
const webApps = await client.webApps.list();
return webApps.filter(s => s.kind === "functionapp")
.sort((s1, s2) => s1.id!.localeCompare(s2.id!))
.map(s => new FunctionAppNode(s, this.subscriptionFilter.session.tenantId));
}
}
}
export class FunctionAppNode implements INode {
readonly contextValue: string = 'azureFunctionsFunctionApp';
readonly label: string;
readonly id: string;
constructor(readonly functionApp: WebSiteModels.Site, readonly tenantId?: string) {
this.label = `${functionApp.name} (${this.functionApp.resourceGroup})`;
this.id = functionApp.id!;
}
get iconPath(): any {
return {
light: path.join(__filename, '..', '..', '..', 'resources', 'light', 'AzureFunctionsApp.svg'),
dark: path.join(__filename, '..', '..', '..', 'resources', 'dark', 'AzureFunctionsApp.svg')
};
}
}