mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-14 18:46:34 -05:00
Custom Summary Page for NotebookWizard and notebook Cell with wizard variables (#10297)
* save not yet tested work * Merge from master. * Screeens Shared for Feeedback * Code complete * remove unneeded changes * remove unnecessary comma * remov wss * remove dead code * PR feedback * checkpoint fixes * PR & minor fixes * minor fix for feature of resourceType options being optional. * reverting experimental change * separating out changes for future featurework. * revert unneeded change * review feedback fixes * review feedback * rename InputFieldComponent to InputComponent * working version of custom summary page * add option to align items in a flex- container. * changes to support labelColor * save work , still pending issue with labelCSSStyles * Summary page and setting variabless in notebook. * minor fixes. * pr feedbck * fix formatting issues * pr feedback * pr feedback * pr feedback * fixing docs * summary page value setting fix * rename children of RowInfo to items * rename a method * rename summary_text to evaluated_text * rename properties of fieldInfo * revert inadvertent change * rename linked_texttext to hyperlinked_text and removing linking facility from readonly_text * pr feedback * fix setting tools variables in env and notebook * removing saving of originalValues for EvaluatedText * await on launchNotebookWithEdits * await on launchNotebookWithContent * merge RadioOptions & Options into 1 * merge ReadOnlyText, links & evaluatedText * Samples for new generic wizard features * fix comment * fix assertions * return type and comment for getClusterContext * fix inadvertent change * increase minimum required azdata version * remove unneeded environment variable settings * not leaking passwords in notebooks
This commit is contained in:
@@ -42,7 +42,7 @@ export interface DialogDeploymentProvider extends DeploymentProviderBase {
|
||||
}
|
||||
|
||||
export interface BdcWizardDeploymentProvider extends DeploymentProviderBase {
|
||||
bdcWizard: WizardInfo;
|
||||
bdcWizard: BdcWizardInfo;
|
||||
}
|
||||
|
||||
export interface NotebookWizardDeploymentProvider extends DeploymentProviderBase {
|
||||
@@ -50,7 +50,7 @@ export interface NotebookWizardDeploymentProvider extends DeploymentProviderBase
|
||||
}
|
||||
|
||||
export interface NotebookDeploymentProvider extends DeploymentProviderBase {
|
||||
notebook: string | NotebookInfo;
|
||||
notebook: string | NotebookPathInfo;
|
||||
}
|
||||
|
||||
export interface WebPageDeploymentProvider extends DeploymentProviderBase {
|
||||
@@ -100,31 +100,32 @@ export interface DeploymentProviderBase {
|
||||
|
||||
export type DeploymentProvider = DialogDeploymentProvider | BdcWizardDeploymentProvider | NotebookWizardDeploymentProvider | NotebookDeploymentProvider | WebPageDeploymentProvider | DownloadDeploymentProvider | CommandDeploymentProvider;
|
||||
|
||||
export interface WizardInfo {
|
||||
notebook: string | NotebookInfo;
|
||||
export interface BdcWizardInfo {
|
||||
notebook: string | NotebookPathInfo;
|
||||
type: BdcDeploymentType;
|
||||
}
|
||||
|
||||
export interface NotebookWizardInfo extends WizardInfoBase {
|
||||
notebook: string | NotebookInfo;
|
||||
notebook: string | NotebookPathInfo;
|
||||
runNotebook?: boolean;
|
||||
codeCellInsertionPosition?: number;
|
||||
pages: NotebookWizardPageInfo[]
|
||||
}
|
||||
|
||||
export interface WizardInfoBase extends SharedFieldAttributes {
|
||||
export interface WizardInfoBase extends FieldInfoBase {
|
||||
taskName?: string;
|
||||
type?: DeploymentType;
|
||||
runNotebook?: boolean;
|
||||
actionText?: string;
|
||||
title: string;
|
||||
pages: NotebookWizardPageInfo[];
|
||||
summaryPage: NotebookWizardPageInfo;
|
||||
generateSummaryPage: boolean;
|
||||
pages: PageInfoBase[];
|
||||
isSummaryPageAutoGenerated?: boolean
|
||||
}
|
||||
|
||||
export interface NotebookWizardPageInfo extends PageInfoBase {
|
||||
description?: string;
|
||||
}
|
||||
export interface NotebookBasedDialogInfo extends DialogInfoBase {
|
||||
notebook: string | NotebookInfo;
|
||||
notebook: string | NotebookPathInfo;
|
||||
runNotebook?: boolean;
|
||||
taskName?: string;
|
||||
}
|
||||
@@ -153,18 +154,39 @@ export interface DialogInfoBase {
|
||||
export interface DialogTabInfo extends PageInfoBase {
|
||||
}
|
||||
|
||||
export interface PageInfoBase extends SharedFieldAttributes {
|
||||
export interface PageInfoBase extends FieldInfoBase {
|
||||
title: string;
|
||||
isSummaryPage?: boolean;
|
||||
sections: SectionInfo[];
|
||||
}
|
||||
|
||||
export interface SharedFieldAttributes {
|
||||
export interface TextCSSStyles {
|
||||
fontStyle?: FontStyle | undefined;
|
||||
fontWeight?: FontWeight | undefined;
|
||||
color?: string;
|
||||
[key: string]: string | undefined;
|
||||
}
|
||||
|
||||
export type ComponentCSSStyles = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
|
||||
export interface OptionsInfo {
|
||||
values: string[] | azdata.CategoryValue[],
|
||||
defaultValue: string,
|
||||
optionsType?: OptionsType
|
||||
}
|
||||
|
||||
export interface FieldInfoBase {
|
||||
labelWidth?: string;
|
||||
inputWidth?: string;
|
||||
labelPosition?: LabelPosition; // Default value is top
|
||||
fieldWidth?: string;
|
||||
fieldHeight?: string;
|
||||
fieldAlignItems?: azdata.AlignItemsType;
|
||||
}
|
||||
export interface SectionInfo extends SharedFieldAttributes {
|
||||
export interface SectionInfo extends FieldInfoBase {
|
||||
title?: string;
|
||||
fields?: FieldInfo[]; // Use this if the dialog is not wide. All fields will be displayed in one column, label will be placed on top of the input component.
|
||||
rows?: RowInfo[]; // Use this for wide dialog or wizard. label will be placed to the left of the input component.
|
||||
@@ -174,7 +196,8 @@ export interface SectionInfo extends SharedFieldAttributes {
|
||||
}
|
||||
|
||||
export interface RowInfo {
|
||||
fields: FieldInfo[];
|
||||
cssStyles?: ComponentCSSStyles;
|
||||
items: FieldInfo[] | RowInfo[];
|
||||
}
|
||||
|
||||
export interface SubFieldInfo {
|
||||
@@ -182,7 +205,7 @@ export interface SubFieldInfo {
|
||||
variableName?: string;
|
||||
}
|
||||
|
||||
export interface FieldInfo extends SubFieldInfo, SharedFieldAttributes {
|
||||
export interface FieldInfo extends SubFieldInfo, FieldInfoBase {
|
||||
subFields?: SubFieldInfo[];
|
||||
type: FieldType;
|
||||
defaultValue?: string;
|
||||
@@ -194,22 +217,23 @@ export interface FieldInfo extends SubFieldInfo, SharedFieldAttributes {
|
||||
min?: number;
|
||||
max?: number;
|
||||
required?: boolean;
|
||||
options?: string[] | azdata.CategoryValue[];
|
||||
options?: string[] | azdata.CategoryValue[] | OptionsInfo;
|
||||
placeHolder?: string;
|
||||
userName?: string; // needed for sql server's password complexity requirement check, password can not include the login name.
|
||||
description?: string;
|
||||
fontStyle?: FontStyle;
|
||||
labelFontWeight?: FontWeight;
|
||||
textFontWeight?: FontWeight;
|
||||
labelCSSStyles?: TextCSSStyles;
|
||||
fontWeight?: FontWeight;
|
||||
links?: azdata.LinkArea[];
|
||||
editable?: boolean; // for editable drop-down,
|
||||
enabled?: boolean;
|
||||
isEvaluated?: boolean;
|
||||
}
|
||||
|
||||
export interface KubeClusterContextFieldInfo extends FieldInfo {
|
||||
configFileVariableName?: string;
|
||||
}
|
||||
export interface AzureAccountFieldInfo extends AzureLocationsFieldInfo {
|
||||
displaySubscriptionVariableName?: string;
|
||||
subscriptionVariableName?: string;
|
||||
resourceGroupVariableName?: string;
|
||||
}
|
||||
@@ -242,16 +266,20 @@ export enum FieldType {
|
||||
SQLPassword = 'sql_password',
|
||||
Password = 'password',
|
||||
Options = 'options',
|
||||
RadioOptions = 'radio_options',
|
||||
ReadonlyText = 'readonly_text',
|
||||
Checkbox = 'checkbox',
|
||||
AzureAccount = 'azure_account',
|
||||
AzureLocations = 'azure_locations',
|
||||
FilePicker = 'file_picker',
|
||||
KubeClusterContextPicker = 'kube_cluster_context_picker'
|
||||
KubeClusterContextPicker = 'kube_cluster_context_picker',
|
||||
}
|
||||
|
||||
export interface NotebookInfo {
|
||||
export enum OptionsType {
|
||||
Dropdown = 'dropdown',
|
||||
Radio = 'radio'
|
||||
}
|
||||
|
||||
export interface NotebookPathInfo {
|
||||
win32: string;
|
||||
darwin: string;
|
||||
linux: string;
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as path from 'path';
|
||||
import { isString } from 'util';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { NotebookInfo } from '../interfaces';
|
||||
import { NotebookPathInfo } from '../interfaces';
|
||||
import { getDateTimeString, getErrorMessage } from '../utils';
|
||||
import { IPlatformService } from './platformService';
|
||||
const localize = nls.loadMessageBundle();
|
||||
@@ -33,11 +33,13 @@ export interface NotebookExecutionResult {
|
||||
}
|
||||
|
||||
export interface INotebookService {
|
||||
launchNotebook(notebook: string | NotebookInfo): Thenable<azdata.nb.NotebookEditor>;
|
||||
launchNotebookWithContent(title: string, content: string): Thenable<azdata.nb.NotebookEditor>;
|
||||
getNotebook(notebook: string | NotebookInfo): Promise<Notebook>;
|
||||
launchNotebook(notebook: string | NotebookPathInfo): Promise<azdata.nb.NotebookEditor>;
|
||||
launchNotebookWithEdits(notebook: string | NotebookPathInfo, cellStatements: string[], insertionPosition?: number): Promise<void>;
|
||||
launchNotebookWithContent(title: string, content: string): Promise<azdata.nb.NotebookEditor>;
|
||||
getNotebook(notebook: string | NotebookPathInfo): Promise<Notebook>;
|
||||
getNotebookPath(notebook: string | NotebookPathInfo): string;
|
||||
executeNotebook(notebook: any, env?: NodeJS.ProcessEnv): Promise<NotebookExecutionResult>;
|
||||
backgroundExecuteNotebook(taskName: string | undefined, notebookInfo: string | NotebookInfo, tempNoteBookPrefix: string, platformService: IPlatformService): void;
|
||||
backgroundExecuteNotebook(taskName: string | undefined, notebookInfo: string | NotebookPathInfo | Notebook, tempNotebookPrefix: string, platformService: IPlatformService, env?: NodeJS.ProcessEnv): void;
|
||||
}
|
||||
|
||||
export class NotebookService implements INotebookService {
|
||||
@@ -48,9 +50,26 @@ export class NotebookService implements INotebookService {
|
||||
* Launch notebook with file path
|
||||
* @param notebook the path of the notebook
|
||||
*/
|
||||
launchNotebook(notebook: string | NotebookInfo): Thenable<azdata.nb.NotebookEditor> {
|
||||
return this.getNotebookFullPath(notebook).then(notebookPath => {
|
||||
return this.showNotebookAsUntitled(notebookPath);
|
||||
async launchNotebook(notebook: string | NotebookPathInfo): Promise<azdata.nb.NotebookEditor> {
|
||||
const notebookPath = await this.getNotebookFullPath(notebook);
|
||||
return await this.showNotebookAsUntitled(notebookPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts cell code given by {@param cellStatements} in an existing notebook given by {@param notebook} file path at the location
|
||||
* {@param insertionPosition} and then launches the edited notebook.
|
||||
*
|
||||
* @param notebook - the path to notebook that needs to be launched
|
||||
* @param cellStatements - array of statements to be inserted in a cell
|
||||
* @param insertionPosition - the position at which cells are inserted. Default is a new cell at the beginning of the notebook.
|
||||
*/
|
||||
async launchNotebookWithEdits(notebook: string, cellStatements: string[], insertionPosition: number = 0): Promise<void> {
|
||||
const openedNotebook = await this.launchNotebook(notebook);
|
||||
await openedNotebook.edit((editBuilder: azdata.nb.NotebookEditorEdit) => {
|
||||
editBuilder.insertCell({
|
||||
cell_type: 'code',
|
||||
source: cellStatements
|
||||
}, insertionPosition);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -59,9 +78,9 @@ export class NotebookService implements INotebookService {
|
||||
* @param title the title of the notebook
|
||||
* @param content the notebook content
|
||||
*/
|
||||
launchNotebookWithContent(title: string, content: string): Thenable<azdata.nb.NotebookEditor> {
|
||||
async launchNotebookWithContent(title: string, content: string): Promise<azdata.nb.NotebookEditor> {
|
||||
const uri: vscode.Uri = vscode.Uri.parse(`untitled:${title}`);
|
||||
return azdata.nb.showNotebookDocument(uri, {
|
||||
return await azdata.nb.showNotebookDocument(uri, {
|
||||
connectionProfile: undefined,
|
||||
preview: false,
|
||||
initialContent: content,
|
||||
@@ -70,7 +89,7 @@ export class NotebookService implements INotebookService {
|
||||
}
|
||||
|
||||
|
||||
async getNotebook(notebook: string | NotebookInfo): Promise<Notebook> {
|
||||
async getNotebook(notebook: string | NotebookPathInfo): Promise<Notebook> {
|
||||
const notebookPath = await this.getNotebookFullPath(notebook);
|
||||
return <Notebook>JSON.parse(await this.platformService.readTextFile(notebookPath));
|
||||
}
|
||||
@@ -83,7 +102,7 @@ export class NotebookService implements INotebookService {
|
||||
const outputFullPath = path.join(workingDirectory, `output-${fileName}`);
|
||||
const additionalEnvironmentVariables: NodeJS.ProcessEnv = env || {};
|
||||
// Set the azdata eula
|
||||
// Scenarios using the executeNotebook feature already have the EULA acceptted by the user before executing this.
|
||||
// Scenarios using the executeNotebook feature already have the EULA accepted by the user before executing this.
|
||||
additionalEnvironmentVariables['ACCEPT_EULA'] = 'yes';
|
||||
try {
|
||||
await this.platformService.saveTextFile(content, notebookFullPath);
|
||||
@@ -109,15 +128,17 @@ export class NotebookService implements INotebookService {
|
||||
}
|
||||
}
|
||||
|
||||
public backgroundExecuteNotebook(taskName: string | undefined, notebookInfo: string | NotebookInfo, tempNotebookPrefix: string, platformService: IPlatformService): void {
|
||||
backgroundExecuteNotebook(taskName: string = 'Executing notebook', notebookInfo: string | NotebookPathInfo | Notebook, tempNotebookPrefix: string, platformService: IPlatformService, env?: NodeJS.ProcessEnv): void {
|
||||
azdata.tasks.startBackgroundOperation({
|
||||
displayName: taskName!,
|
||||
description: taskName!,
|
||||
displayName: taskName,
|
||||
description: taskName,
|
||||
isCancelable: false,
|
||||
operation: async op => {
|
||||
op.updateStatus(azdata.TaskStatus.InProgress);
|
||||
const notebook = await this.getNotebook(notebookInfo);
|
||||
const result = await this.executeNotebook(notebook);
|
||||
const notebook = (typeof notebookInfo === 'object' && 'cells' in notebookInfo)
|
||||
? <Notebook>notebookInfo
|
||||
: await this.getNotebook(notebookInfo);
|
||||
const result = await this.executeNotebook(notebook, env);
|
||||
if (result.succeeded) {
|
||||
op.updateStatus(azdata.TaskStatus.Succeeded);
|
||||
} else {
|
||||
@@ -129,7 +150,7 @@ export class NotebookService implements INotebookService {
|
||||
platformService.logToOutputChannel(taskFailedMessage);
|
||||
if (selectedOption === viewErrorDetail) {
|
||||
try {
|
||||
this.launchNotebookWithContent(`${tempNotebookPrefix}-${getDateTimeString()}`, result.outputNotebook);
|
||||
await this.launchNotebookWithContent(`${tempNotebookPrefix}-${getDateTimeString()}`, result.outputNotebook);
|
||||
} catch (error) {
|
||||
const launchNotebookError = localize('resourceDeployment.FailedToOpenNotebook', "An error occurred launching the output notebook. {1}{2}.", EOL, getErrorMessage(error));
|
||||
platformService.logToOutputChannel(launchNotebookError);
|
||||
@@ -146,7 +167,7 @@ export class NotebookService implements INotebookService {
|
||||
});
|
||||
}
|
||||
|
||||
async getNotebookFullPath(notebook: string | NotebookInfo): Promise<string> {
|
||||
async getNotebookFullPath(notebook: string | NotebookPathInfo): Promise<string> {
|
||||
const notebookPath = this.getNotebookPath(notebook);
|
||||
let notebookExists = await this.platformService.fileExists(notebookPath);
|
||||
if (notebookExists) {
|
||||
@@ -168,7 +189,7 @@ export class NotebookService implements INotebookService {
|
||||
* get the notebook path for current platform
|
||||
* @param notebook the notebook path
|
||||
*/
|
||||
getNotebookPath(notebook: string | NotebookInfo): string {
|
||||
getNotebookPath(notebook: string | NotebookPathInfo): string {
|
||||
let notebookPath;
|
||||
if (notebook && !isString(notebook)) {
|
||||
const platform = this.platformService.platform();
|
||||
@@ -199,17 +220,16 @@ export class NotebookService implements INotebookService {
|
||||
return title;
|
||||
}
|
||||
|
||||
showNotebookAsUntitled(notebookPath: string): Thenable<azdata.nb.NotebookEditor> {
|
||||
async showNotebookAsUntitled(notebookPath: string): Promise<azdata.nb.NotebookEditor> {
|
||||
let targetFileName: string = this.findNextUntitledEditorName(notebookPath);
|
||||
const untitledFileName: vscode.Uri = vscode.Uri.parse(`untitled:${targetFileName}`);
|
||||
return vscode.workspace.openTextDocument(notebookPath).then((document) => {
|
||||
let initialContent = document.getText();
|
||||
return azdata.nb.showNotebookDocument(untitledFileName, {
|
||||
connectionProfile: undefined,
|
||||
preview: false,
|
||||
initialContent: initialContent,
|
||||
initialDirtyState: false
|
||||
});
|
||||
const document = await vscode.workspace.openTextDocument(notebookPath);
|
||||
let initialContent = document.getText();
|
||||
return await azdata.nb.showNotebookDocument(untitledFileName, {
|
||||
connectionProfile: undefined,
|
||||
preview: false,
|
||||
initialContent: initialContent,
|
||||
initialDirtyState: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import * as nls from 'vscode-nls';
|
||||
import { INotebookService } from './notebookService';
|
||||
import { IPlatformService } from './platformService';
|
||||
import { IToolsService } from './toolsService';
|
||||
import { ResourceType, ResourceTypeOption, NotebookInfo, DeploymentProvider, instanceOfWizardDeploymentProvider, instanceOfDialogDeploymentProvider, instanceOfNotebookDeploymentProvider, instanceOfDownloadDeploymentProvider, instanceOfWebPageDeploymentProvider, instanceOfCommandDeploymentProvider, instanceOfNotebookBasedDialogInfo, instanceOfNotebookWizardDeploymentProvider } from '../interfaces';
|
||||
import { ResourceType, ResourceTypeOption, NotebookPathInfo, DeploymentProvider, instanceOfWizardDeploymentProvider, instanceOfDialogDeploymentProvider, instanceOfNotebookDeploymentProvider, instanceOfDownloadDeploymentProvider, instanceOfWebPageDeploymentProvider, instanceOfCommandDeploymentProvider, instanceOfNotebookBasedDialogInfo, instanceOfNotebookWizardDeploymentProvider } from '../interfaces';
|
||||
import { DeployClusterWizard } from '../ui/deployClusterWizard/deployClusterWizard';
|
||||
import { DeploymentInputDialog } from '../ui/deploymentInputDialog';
|
||||
|
||||
@@ -77,7 +77,7 @@ export class ResourceTypeService implements IResourceTypeService {
|
||||
});
|
||||
}
|
||||
|
||||
private updateNotebookPath(objWithNotebookProperty: { notebook: string | NotebookInfo } | undefined, extensionPath: string): void {
|
||||
private updateNotebookPath(objWithNotebookProperty: { notebook: string | NotebookPathInfo } | undefined, extensionPath: string): void {
|
||||
if (objWithNotebookProperty && objWithNotebookProperty.notebook) {
|
||||
if (typeof objWithNotebookProperty.notebook === 'string') {
|
||||
objWithNotebookProperty.notebook = path.join(extensionPath, objWithNotebookProperty.notebook);
|
||||
@@ -239,10 +239,10 @@ export class ResourceTypeService implements IResourceTypeService {
|
||||
public startDeployment(provider: DeploymentProvider): void {
|
||||
const self = this;
|
||||
if (instanceOfWizardDeploymentProvider(provider)) {
|
||||
const wizard = new DeployClusterWizard(provider.bdcWizard, new KubeService(), new AzdataService(this.platformService), this.notebookService);
|
||||
const wizard = new DeployClusterWizard(provider.bdcWizard, new KubeService(), new AzdataService(this.platformService), this.notebookService, this.toolsService);
|
||||
wizard.open();
|
||||
} else if (instanceOfNotebookWizardDeploymentProvider(provider)) {
|
||||
const wizard = new NotebookWizard(provider.notebookWizard, this.notebookService, this.platformService);
|
||||
const wizard = new NotebookWizard(provider.notebookWizard, this.notebookService, this.platformService, this.toolsService);
|
||||
wizard.open();
|
||||
} else if (instanceOfDialogDeploymentProvider(provider)) {
|
||||
const dialog = new DeploymentInputDialog(this.notebookService, this.platformService, provider.dialog);
|
||||
|
||||
@@ -11,10 +11,12 @@ import { IPlatformService } from './platformService';
|
||||
|
||||
export interface IToolsService {
|
||||
getToolByName(toolName: string): ITool | undefined;
|
||||
toolsForCurrentProvider: ITool[];
|
||||
}
|
||||
|
||||
export class ToolsService implements IToolsService {
|
||||
private supportedTools: Map<string, ITool>;
|
||||
private currentTools: ITool[] = [];
|
||||
|
||||
constructor(private _platformService: IPlatformService) {
|
||||
this.supportedTools = new Map<string, ITool>(
|
||||
@@ -30,4 +32,12 @@ export class ToolsService implements IToolsService {
|
||||
getToolByName(toolName: string): ITool | undefined {
|
||||
return this.supportedTools.get(toolName);
|
||||
}
|
||||
|
||||
get toolsForCurrentProvider(): ITool[] {
|
||||
return this.currentTools;
|
||||
}
|
||||
|
||||
set toolsForCurrentProvider(tools: ITool[]) {
|
||||
this.currentTools = tools;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as TypeMoq from 'typemoq';
|
||||
import 'mocha';
|
||||
import { NotebookService } from '../services/notebookService';
|
||||
import assert = require('assert');
|
||||
import { NotebookInfo } from '../interfaces';
|
||||
import { NotebookPathInfo } from '../interfaces';
|
||||
import { IPlatformService } from '../services/platformService';
|
||||
|
||||
suite('Notebook Service Tests', function (): void {
|
||||
@@ -35,7 +35,7 @@ suite('Notebook Service Tests', function (): void {
|
||||
const notebookDarwin = 'test-notebook-darwin.ipynb';
|
||||
const notebookLinux = 'test-notebook-linux.ipynb';
|
||||
|
||||
const notebookInput: NotebookInfo = {
|
||||
const notebookInput: NotebookPathInfo = {
|
||||
darwin: notebookDarwin,
|
||||
win32: notebookWin32,
|
||||
linux: notebookLinux
|
||||
|
||||
@@ -4,28 +4,33 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { SummaryPage } from './pages/summaryPage';
|
||||
import { WizardBase } from '../wizardBase';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { WizardInfo, BdcDeploymentType } from '../../interfaces';
|
||||
import { WizardPageBase } from '../wizardPageBase';
|
||||
import { AzureSettingsPage } from './pages/azureSettingsPage';
|
||||
import { ClusterSettingsPage } from './pages/clusterSettingsPage';
|
||||
import { ServiceSettingsPage } from './pages/serviceSettingsPage';
|
||||
import { TargetClusterContextPage } from './pages/targetClusterPage';
|
||||
import { IKubeService } from '../../services/kubeService';
|
||||
import { IAzdataService } from '../../services/azdataService';
|
||||
import { DeploymentProfilePage } from './pages/deploymentProfilePage';
|
||||
import { INotebookService } from '../../services/notebookService';
|
||||
import { DeployClusterWizardModel, AuthenticationMode } from './deployClusterWizardModel';
|
||||
import * as VariableNames from './constants';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import { join } from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { BdcDeploymentType, BdcWizardInfo } from '../../interfaces';
|
||||
import { IAzdataService } from '../../services/azdataService';
|
||||
import { IKubeService } from '../../services/kubeService';
|
||||
import { INotebookService } from '../../services/notebookService';
|
||||
import { IToolsService } from '../../services/toolsService';
|
||||
import { getErrorMessage } from '../../utils';
|
||||
import { InputComponents } from '../modelViewUtils';
|
||||
import { WizardBase } from '../wizardBase';
|
||||
import { WizardPageBase } from '../wizardPageBase';
|
||||
import * as VariableNames from './constants';
|
||||
import { AuthenticationMode, DeployClusterWizardModel } from './deployClusterWizardModel';
|
||||
import { AzureSettingsPage } from './pages/azureSettingsPage';
|
||||
import { ClusterSettingsPage } from './pages/clusterSettingsPage';
|
||||
import { DeploymentProfilePage } from './pages/deploymentProfilePage';
|
||||
import { ServiceSettingsPage } from './pages/serviceSettingsPage';
|
||||
import { SummaryPage } from './pages/summaryPage';
|
||||
import { TargetClusterContextPage } from './pages/targetClusterPage';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class DeployClusterWizard extends WizardBase<DeployClusterWizard, DeployClusterWizardModel> {
|
||||
export class DeployClusterWizard extends WizardBase<DeployClusterWizard, WizardPageBase<DeployClusterWizard>, DeployClusterWizardModel> {
|
||||
private _inputComponents: InputComponents = {};
|
||||
|
||||
private _saveConfigButton: azdata.window.Button;
|
||||
|
||||
public get kubeService(): IKubeService {
|
||||
@@ -40,6 +45,10 @@ export class DeployClusterWizard extends WizardBase<DeployClusterWizard, DeployC
|
||||
return this._notebookService;
|
||||
}
|
||||
|
||||
public get inputComponents(): InputComponents {
|
||||
return this._inputComponents;
|
||||
}
|
||||
|
||||
public showCustomButtons(): void {
|
||||
this._saveConfigButton.hidden = false;
|
||||
}
|
||||
@@ -48,7 +57,7 @@ export class DeployClusterWizard extends WizardBase<DeployClusterWizard, DeployC
|
||||
this._saveConfigButton.hidden = true;
|
||||
}
|
||||
|
||||
constructor(private wizardInfo: WizardInfo, private _kubeService: IKubeService, private _azdataService: IAzdataService, private _notebookService: INotebookService) {
|
||||
constructor(private wizardInfo: BdcWizardInfo, private _kubeService: IKubeService, private _azdataService: IAzdataService, private _notebookService: INotebookService, private _toolsService: IToolsService) {
|
||||
super(DeployClusterWizard.getTitle(wizardInfo.type), new DeployClusterWizardModel(wizardInfo.type));
|
||||
this._saveConfigButton = azdata.window.createButton(localize('deployCluster.SaveConfigFiles', "Save config files"), 'left');
|
||||
this._saveConfigButton.hidden = true;
|
||||
@@ -69,8 +78,8 @@ export class DeployClusterWizard extends WizardBase<DeployClusterWizard, DeployC
|
||||
protected onCancel(): void {
|
||||
}
|
||||
|
||||
protected onOk(): void {
|
||||
this.scriptToNotebook();
|
||||
protected async onOk(): Promise<void> {
|
||||
await this.scriptToNotebook();
|
||||
}
|
||||
|
||||
private getPages(): WizardPageBase<DeployClusterWizard>[] {
|
||||
@@ -135,19 +144,15 @@ export class DeployClusterWizard extends WizardBase<DeployClusterWizard, DeployC
|
||||
}
|
||||
}
|
||||
|
||||
private scriptToNotebook(): void {
|
||||
private async scriptToNotebook(): Promise<void> {
|
||||
this.setEnvironmentVariables(process.env);
|
||||
this.notebookService.launchNotebook(this.wizardInfo.notebook).then((notebook: azdata.nb.NotebookEditor) => {
|
||||
notebook.edit((editBuilder: azdata.nb.NotebookEditorEdit) => {
|
||||
// 5 is the position after the 'Set variables' cell in the deployment notebooks
|
||||
editBuilder.insertCell({
|
||||
cell_type: 'code',
|
||||
source: this.model.getCodeCellContentForNotebook()
|
||||
}, 5);
|
||||
});
|
||||
}, (error) => {
|
||||
vscode.window.showErrorMessage(error);
|
||||
});
|
||||
const variableValueStatements = this.model.getCodeCellContentForNotebook(this._toolsService.toolsForCurrentProvider);
|
||||
const insertionPosition = 5; // Cell number 5 is the position where the python variable setting statements need to be inserted in this.wizardInfo.notebook.
|
||||
try {
|
||||
await this.notebookService.launchNotebookWithEdits(this.wizardInfo.notebook, variableValueStatements, insertionPosition);
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(getErrorMessage(error));
|
||||
}
|
||||
}
|
||||
|
||||
private setEnvironmentVariables(env: NodeJS.ProcessEnv): void {
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { EOL } from 'os';
|
||||
import { delimiter } from 'path';
|
||||
import { BdcDeploymentType } from '../../interfaces';
|
||||
import { BdcDeploymentType, ITool } from '../../interfaces';
|
||||
import { BigDataClusterDeploymentProfile, DataResource, HdfsResource, SqlServerMasterResource } from '../../services/bigDataClusterDeploymentProfile';
|
||||
import { KubeCtlToolName } from '../../services/tools/kubeCtlTool';
|
||||
import { getRuntimeBinaryPathEnvironmentVariableName } from '../../utils';
|
||||
import { getRuntimeBinaryPathEnvironmentVariableName, setEnvironmentVariablesForInstallPaths } from '../../utils';
|
||||
import { Model } from '../model';
|
||||
import * as VariableNames from './constants';
|
||||
import { ToolsInstallPath } from './../../constants';
|
||||
import * as VariableNames from './constants';
|
||||
|
||||
export class DeployClusterWizardModel extends Model {
|
||||
constructor(public deploymentTarget: BdcDeploymentType) {
|
||||
@@ -138,7 +138,7 @@ export class DeployClusterWizardModel extends Model {
|
||||
return targetDeploymentProfile;
|
||||
}
|
||||
|
||||
public getCodeCellContentForNotebook(): string[] {
|
||||
public getCodeCellContentForNotebook(tools: ITool[]): string[] {
|
||||
const profile = this.createTargetProfile();
|
||||
const statements: string[] = [];
|
||||
if (this.deploymentTarget === BdcDeploymentType.NewAKS) {
|
||||
@@ -166,16 +166,13 @@ export class DeployClusterWizardModel extends Model {
|
||||
statements.push(`os.environ["DOCKER_PASSWORD"] = os.environ["${VariableNames.DockerPassword_VariableName}"]`);
|
||||
}
|
||||
const kubeCtlEnvVarName: string = getRuntimeBinaryPathEnvironmentVariableName(KubeCtlToolName);
|
||||
statements.push(`os.environ["${kubeCtlEnvVarName}"] = "${this.escapeForNotebookCodeCell(process.env[kubeCtlEnvVarName]!)}"`);
|
||||
statements.push(`os.environ["PATH"] = os.environ["PATH"] + "${delimiter}" + "${this.escapeForNotebookCodeCell(process.env[ToolsInstallPath]!)}"`);
|
||||
const env: NodeJS.ProcessEnv = {};
|
||||
setEnvironmentVariablesForInstallPaths(tools, env);
|
||||
statements.push(`os.environ["${kubeCtlEnvVarName}"] = "${this.escapeForNotebookCodeCell(env[kubeCtlEnvVarName]!)}"`);
|
||||
statements.push(`os.environ["PATH"] = os.environ["PATH"] + "${delimiter}" + "${this.escapeForNotebookCodeCell(env[ToolsInstallPath]!)}"`);
|
||||
statements.push(`print('Variables have been set successfully.')`);
|
||||
return statements.map(line => line + EOL);
|
||||
}
|
||||
|
||||
private escapeForNotebookCodeCell(original: string): string {
|
||||
// Escape the \ character for the code cell string value
|
||||
return original && original.replace(/\\/g, '\\\\');
|
||||
}
|
||||
}
|
||||
|
||||
export enum AuthenticationMode {
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { FieldType, LabelPosition, SectionInfo } from '../../../interfaces';
|
||||
import { createSection, getDropdownComponent, InputComponents, InputComponent, setModelValues, Validator } from '../../modelViewUtils';
|
||||
import { createSection, getDropdownComponent, InputComponentInfo, InputComponents, setModelValues, Validator } from '../../modelViewUtils';
|
||||
import { WizardPageBase } from '../../wizardPageBase';
|
||||
import { AksName_VariableName, Location_VariableName, ResourceGroup_VariableName, SubscriptionId_VariableName, VMCount_VariableName, VMSize_VariableName } from '../constants';
|
||||
import { DeployClusterWizard } from '../deployClusterWizard';
|
||||
@@ -29,7 +29,7 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
labelPosition: LabelPosition.Left,
|
||||
spaceBetweenFields: '5px',
|
||||
rows: [{
|
||||
fields: [{
|
||||
items: [{
|
||||
type: FieldType.Text,
|
||||
label: localize('deployCluster.SubscriptionField', "Subscription id"),
|
||||
required: false,
|
||||
@@ -38,9 +38,7 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
description: localize('deployCluster.SubscriptionDescription', "The default subscription will be used if you leave this field blank.")
|
||||
}, {
|
||||
type: FieldType.ReadonlyText,
|
||||
label: '',
|
||||
labelWidth: '0px',
|
||||
defaultValue: localize('deployCluster.SubscriptionHelpText', "{0}"),
|
||||
label: '{0}',
|
||||
links: [
|
||||
{
|
||||
text: localize('deployCluster.SubscriptionHelpLink', "View available Azure subscriptions"),
|
||||
@@ -49,7 +47,7 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
]
|
||||
}]
|
||||
}, {
|
||||
fields: [{
|
||||
items: [{
|
||||
type: FieldType.DateTimeText,
|
||||
label: localize('deployCluster.ResourceGroupName', "New resource group name"),
|
||||
required: true,
|
||||
@@ -57,7 +55,7 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
defaultValue: 'mssql-'
|
||||
}]
|
||||
}, {
|
||||
fields: [{
|
||||
items: [{
|
||||
type: FieldType.Options,
|
||||
label: localize('deployCluster.Location', "Location"),
|
||||
required: true,
|
||||
@@ -79,9 +77,7 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
]
|
||||
}, {
|
||||
type: FieldType.ReadonlyText,
|
||||
label: '',
|
||||
labelWidth: '0px',
|
||||
defaultValue: localize('deployCluster.LocationHelpText', "{0}"),
|
||||
label: '{0}',
|
||||
links: [
|
||||
{
|
||||
text: localize('deployCluster.AzureLocationHelpLink', "View available Azure locations"),
|
||||
@@ -90,7 +86,7 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
]
|
||||
}]
|
||||
}, {
|
||||
fields: [{
|
||||
items: [{
|
||||
type: FieldType.DateTimeText,
|
||||
label: localize('deployCluster.AksName', "AKS cluster name"),
|
||||
required: true,
|
||||
@@ -98,7 +94,7 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
defaultValue: 'mssql-',
|
||||
}]
|
||||
}, {
|
||||
fields: [
|
||||
items: [
|
||||
{
|
||||
type: FieldType.Number,
|
||||
label: localize('deployCluster.VMCount', "VM count"),
|
||||
@@ -110,7 +106,7 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
}
|
||||
]
|
||||
}, {
|
||||
fields: [{
|
||||
items: [{
|
||||
type: FieldType.Text,
|
||||
label: localize('deployCluster.VMSize', "VM size"),
|
||||
required: true,
|
||||
@@ -118,9 +114,7 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
defaultValue: 'Standard_E8s_v3'
|
||||
}, {
|
||||
type: FieldType.ReadonlyText,
|
||||
label: '',
|
||||
labelWidth: '0px',
|
||||
defaultValue: localize('deployCluster.VMSizeHelpText', "{0}"),
|
||||
label: '{0}',
|
||||
links: [
|
||||
{
|
||||
text: localize('deployCluster.VMSizeHelpLink', "View available VM sizes"),
|
||||
@@ -137,13 +131,14 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
onNewDisposableCreated: (disposable: vscode.Disposable): void => {
|
||||
self.wizard.registerDisposable(disposable);
|
||||
},
|
||||
onNewInputComponentCreated: (name: string, component: InputComponent): void => {
|
||||
self.inputComponents[name] = { component: component };
|
||||
onNewInputComponentCreated: (name: string, inputComponentInfo: InputComponentInfo): void => {
|
||||
this.inputComponents[name] = { component: inputComponentInfo.component };
|
||||
},
|
||||
onNewValidatorCreated: (validator: Validator): void => {
|
||||
self.validators.push(validator);
|
||||
},
|
||||
container: this.wizard.wizardObject
|
||||
container: this.wizard.wizardObject,
|
||||
inputComponents: this.wizard.inputComponents
|
||||
});
|
||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
[{
|
||||
@@ -184,5 +179,6 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
return true;
|
||||
});
|
||||
setModelValues(this.inputComponents, this.wizard.model);
|
||||
Object.assign(this.wizard.inputComponents, this.inputComponents);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { EOL } from 'os';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { DeployClusterWizard } from '../deployClusterWizard';
|
||||
import { SectionInfo, FieldType, LabelPosition } from '../../../interfaces';
|
||||
import { createSection, InputComponents, setModelValues, Validator, getInputBoxComponent, isValidSQLPassword, getInvalidSQLPasswordMessage, getPasswordMismatchMessage, InputComponent } from '../../modelViewUtils';
|
||||
import { FieldType, LabelPosition, SectionInfo } from '../../../interfaces';
|
||||
import { createSection, getInputBoxComponent, getInvalidSQLPasswordMessage, getPasswordMismatchMessage, InputComponentInfo, InputComponents, isValidSQLPassword, setModelValues, Validator } from '../../modelViewUtils';
|
||||
import { WizardPageBase } from '../../wizardPageBase';
|
||||
import * as VariableNames from '../constants';
|
||||
import { EOL } from 'os';
|
||||
import { DeployClusterWizard } from '../deployClusterWizard';
|
||||
import { AuthenticationMode } from '../deployClusterWizardModel';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -174,7 +174,7 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
variableName: VariableNames.DomainServiceAccountPassword_VariableName
|
||||
}, {
|
||||
type: FieldType.Text,
|
||||
label: localize('deployCluster.AppOwers', "App owners"),
|
||||
label: localize('deployCluster.AppOwners', "App owners"),
|
||||
required: false,
|
||||
variableName: VariableNames.AppOwners_VariableName,
|
||||
placeHolder: localize('deployCluster.AppOwnersPlaceHolder', "Use comma to separate the values."),
|
||||
@@ -193,12 +193,13 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
const basicSettingsGroup = createSection({
|
||||
view: view,
|
||||
container: self.wizard.wizardObject,
|
||||
inputComponents: this.wizard.inputComponents,
|
||||
sectionInfo: basicSection,
|
||||
onNewDisposableCreated: (disposable: vscode.Disposable): void => {
|
||||
self.wizard.registerDisposable(disposable);
|
||||
},
|
||||
onNewInputComponentCreated: (name: string, component: InputComponent): void => {
|
||||
self.inputComponents[name] = { component: component };
|
||||
onNewInputComponentCreated: (name: string, inputComponent: InputComponentInfo): void => {
|
||||
self.inputComponents[name] = { component: inputComponent.component };
|
||||
},
|
||||
onNewValidatorCreated: (validator: Validator): void => {
|
||||
self.validators.push(validator);
|
||||
@@ -207,12 +208,13 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
const activeDirectorySettingsGroup = createSection({
|
||||
view: view,
|
||||
container: self.wizard.wizardObject,
|
||||
inputComponents: this.wizard.inputComponents,
|
||||
sectionInfo: activeDirectorySection,
|
||||
onNewDisposableCreated: (disposable: vscode.Disposable): void => {
|
||||
self.wizard.registerDisposable(disposable);
|
||||
},
|
||||
onNewInputComponentCreated: (name: string, component: InputComponent): void => {
|
||||
self.inputComponents[name] = { component: component };
|
||||
onNewInputComponentCreated: (name: string, inputComponentInfo: InputComponentInfo): void => {
|
||||
this.inputComponents[name] = { component: inputComponentInfo.component };
|
||||
},
|
||||
onNewValidatorCreated: (validator: Validator): void => {
|
||||
self.validators.push(validator);
|
||||
@@ -221,12 +223,13 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
const dockerSettingsGroup = createSection({
|
||||
view: view,
|
||||
container: self.wizard.wizardObject,
|
||||
inputComponents: this.wizard.inputComponents,
|
||||
sectionInfo: dockerSection,
|
||||
onNewDisposableCreated: (disposable: vscode.Disposable): void => {
|
||||
self.wizard.registerDisposable(disposable);
|
||||
},
|
||||
onNewInputComponentCreated: (name: string, component: InputComponent): void => {
|
||||
self.inputComponents[name] = { component: component };
|
||||
onNewInputComponentCreated: (name: string, inputComponentInfo: InputComponentInfo): void => {
|
||||
this.inputComponents[name] = { component: inputComponentInfo.component };
|
||||
},
|
||||
onNewValidatorCreated: (validator: Validator): void => {
|
||||
self.validators.push(validator);
|
||||
@@ -266,6 +269,7 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
|
||||
public onLeave() {
|
||||
setModelValues(this.inputComponents, this.wizard.model);
|
||||
Object.assign(this.wizard.inputComponents, this.inputComponents);
|
||||
if (this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory) {
|
||||
const variableDNSPrefixMapping: { [s: string]: string } = {};
|
||||
variableDNSPrefixMapping[VariableNames.AppServiceProxyDNSName_VariableName] = 'bdc-appproxy';
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { DeployClusterWizard } from '../deployClusterWizard';
|
||||
import { SectionInfo, FieldType } from '../../../interfaces';
|
||||
import { Validator, InputComponents, InputComponent, createSection, createGroupContainer, createLabel, createFlexContainer, createTextInput, createNumberInput, setModelValues, getInputBoxComponent, getCheckboxComponent, getDropdownComponent } from '../../modelViewUtils';
|
||||
import { FieldType, SectionInfo } from '../../../interfaces';
|
||||
import { createFlexContainer, createGroupContainer, createLabel, createNumberInput, createSection, createTextInput, getCheckboxComponent, getDropdownComponent, getInputBoxComponent, InputComponentInfo, InputComponents, setModelValues, Validator } from '../../modelViewUtils';
|
||||
import { WizardPageBase } from '../../wizardPageBase';
|
||||
import * as VariableNames from '../constants';
|
||||
import { DeployClusterWizard } from '../deployClusterWizard';
|
||||
import { AuthenticationMode } from '../deployClusterWizardModel';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -59,7 +59,7 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
inputWidth: NumberInputWidth,
|
||||
spaceBetweenFields: '40px',
|
||||
rows: [{
|
||||
fields: [{
|
||||
items: [{
|
||||
type: FieldType.Options,
|
||||
label: localize('deployCluster.MasterSqlServerInstances', "SQL Server master instances"),
|
||||
options: ['1', '3', '4', '5', '6', '7', '8', '9'],
|
||||
@@ -75,7 +75,7 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
variableName: VariableNames.ComputePoolScale_VariableName,
|
||||
}]
|
||||
}, {
|
||||
fields: [{
|
||||
items: [{
|
||||
type: FieldType.Number,
|
||||
label: localize('deployCluster.DataPoolInstances', "Data pool instances"),
|
||||
min: 1,
|
||||
@@ -93,7 +93,7 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
variableName: VariableNames.SparkPoolScale_VariableName
|
||||
}]
|
||||
}, {
|
||||
fields: [
|
||||
items: [
|
||||
{
|
||||
type: FieldType.Number,
|
||||
label: localize('deployCluster.StoragePoolInstances', "Storage pool (HDFS) instances"),
|
||||
@@ -119,12 +119,13 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
return createSection({
|
||||
view: view,
|
||||
container: this.wizard.wizardObject,
|
||||
inputComponents: this.inputComponents,
|
||||
sectionInfo: sectionInfo,
|
||||
onNewDisposableCreated: (disposable: vscode.Disposable): void => {
|
||||
this.wizard.registerDisposable(disposable);
|
||||
},
|
||||
onNewInputComponentCreated: (name: string, component: InputComponent): void => {
|
||||
this.inputComponents[name] = { component: component };
|
||||
onNewInputComponentCreated: (name: string, inputComponentInfo: InputComponentInfo): void => {
|
||||
this.inputComponents[name] = { component: inputComponentInfo.component };
|
||||
},
|
||||
onNewValidatorCreated: (validator: Validator): void => {
|
||||
}
|
||||
@@ -400,6 +401,7 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
|
||||
|
||||
public onLeave(): void {
|
||||
setModelValues(this.inputComponents, this.wizard.model);
|
||||
Object.assign(this.wizard.inputComponents, this.inputComponents);
|
||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -43,18 +43,17 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
|
||||
title: localize('deployCluster.DeploymentTarget', "Deployment target"),
|
||||
rows: [
|
||||
{
|
||||
fields: [
|
||||
items: [
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.Kubeconfig', "Kube config"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.KubeConfigPath_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
},
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.ClusterContext', "Cluster context"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.ClusterContext_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
}]
|
||||
}
|
||||
]
|
||||
@@ -68,33 +67,33 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
|
||||
rows: [
|
||||
{
|
||||
|
||||
fields: [
|
||||
items: [
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.DeploymentProfile', "Deployment profile"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.DeploymentProfile_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
},
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.ClusterName', "Cluster name"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.ClusterName_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
}]
|
||||
}, {
|
||||
fields: [
|
||||
items: [
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.ControllerUsername', "Controller username"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.AdminUserName_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
}, {
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.AuthenticationMode', "Authentication mode"),
|
||||
defaultValue: this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory ?
|
||||
localize('deployCluster.AuthenticationMode.ActiveDirectory', "Active Directory") :
|
||||
localize('deployCluster.AuthenticationMode.Basic', "Basic"),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -103,72 +102,72 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
|
||||
|
||||
if (this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory) {
|
||||
clusterSectionInfo.rows!.push({
|
||||
fields: [
|
||||
items: [
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.OuDistinguishedName', "Organizational unit"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.OrganizationalUnitDistinguishedName_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
},
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.DomainControllerFQDNs', "Domain controller FQDNs"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.DomainControllerFQDNs_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
}]
|
||||
});
|
||||
clusterSectionInfo.rows!.push({
|
||||
fields: [
|
||||
items: [
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.DomainDNSIPAddresses', "Domain DNS IP addresses"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.DomainDNSIPAddresses_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
},
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.DomainDNSName', "Domain DNS name"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.DomainDNSName_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
}]
|
||||
});
|
||||
clusterSectionInfo.rows!.push({
|
||||
fields: [
|
||||
items: [
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.ClusterAdmins', "Cluster admin group"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.ClusterAdmins_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
},
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.ClusterUsers', "Cluster users"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.ClusterUsers_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
}]
|
||||
});
|
||||
clusterSectionInfo.rows!.push({
|
||||
fields: [
|
||||
items: [
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.AppOwers', "App owners"),
|
||||
label: localize('deployCluster.AppOwners', "App owners"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.AppOwners_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
},
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.AppReaders', "App readers"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.AppReaders_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
}]
|
||||
});
|
||||
clusterSectionInfo.rows!.push({
|
||||
fields: [
|
||||
items: [
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.DomainServiceAccountUserName', "Service account username"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.DomainServiceAccountUserName_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
}]
|
||||
});
|
||||
}
|
||||
@@ -179,45 +178,45 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
|
||||
inputWidth: '200px',
|
||||
title: localize('deployCluster.AzureSettings', "Azure settings"),
|
||||
rows: [{
|
||||
fields: [
|
||||
items: [
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.SubscriptionId', "Subscription id"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.SubscriptionId_VariableName) || localize('deployCluster.DefaultSubscription', "Default Azure Subscription"),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
}, {
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.ResourceGroup', "Resource group"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.ResourceGroup_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
}
|
||||
]
|
||||
}, {
|
||||
fields: [
|
||||
items: [
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.Location', "Location"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.Location_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
}, {
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.AksClusterName', "AKS cluster name"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.AksName_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
}
|
||||
]
|
||||
}, {
|
||||
fields: [
|
||||
items: [
|
||||
{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.VMSize', "VM size"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.VMSize_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
}, {
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.VMCount', "VM count"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.VMCount_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -231,35 +230,35 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
|
||||
title: localize('deployCluster.ScaleSettings', "Scale settings"),
|
||||
rows: [
|
||||
{
|
||||
fields: [{
|
||||
items: [{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.MasterSqlServerInstances', "SQL Server master instances"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.SQLServerScale_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
}, {
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.ComputePoolInstances', "Compute pool instances"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.ComputePoolScale_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
}]
|
||||
}, {
|
||||
fields: [{
|
||||
items: [{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.DataPoolInstances', "Data pool instances"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.DataPoolScale_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
}, {
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.SparkPoolInstances', "Spark pool instances"),
|
||||
defaultValue: this.wizard.model.getStringValue(VariableNames.SparkPoolScale_VariableName),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
}]
|
||||
}, {
|
||||
fields: [{
|
||||
items: [{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: localize('deployCluster.StoragePoolInstances', "Storage pool (HDFS) instances"),
|
||||
defaultValue: `${this.wizard.model.getStringValue(VariableNames.HDFSPoolScale_VariableName)} ${this.wizard.model.getBooleanValue(VariableNames.IncludeSpark_VariableName) ? localize('deployCluster.WithSpark', "(Spark included)") : ''}`,
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
}]
|
||||
}
|
||||
]
|
||||
@@ -270,6 +269,7 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
|
||||
title: '',
|
||||
component: createSection({
|
||||
container: this.wizard.wizardObject,
|
||||
inputComponents: this.wizard.inputComponents,
|
||||
sectionInfo: sectionInfo,
|
||||
view: this.view,
|
||||
onNewDisposableCreated: () => { },
|
||||
@@ -398,7 +398,7 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
|
||||
|
||||
private createEndpointRow(name: string, dnsVariableName: string, portVariableName: string): azdata.FlexContainer {
|
||||
const items = [];
|
||||
items.push(createLabel(this.view, { text: name, width: '150px', fontWeight: FontWeight.Bold }));
|
||||
items.push(createLabel(this.view, { text: name, width: '150px', cssStyles: { fontWeight: FontWeight.Bold } }));
|
||||
if (this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory) {
|
||||
items.push(createLabel(this.view, {
|
||||
text: this.wizard.model.getStringValue(dnsVariableName)!, width: '200px'
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { EOL } from 'os';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { DialogBase } from './dialogBase';
|
||||
import { INotebookService } from '../services/notebookService';
|
||||
import { DialogInfo, instanceOfNotebookBasedDialogInfo, NotebookBasedDialogInfo } from '../interfaces';
|
||||
import { Validator, initializeDialog, InputComponents, setModelValues, InputValueTransformer, InputComponent } from './modelViewUtils';
|
||||
import { Model } from './model';
|
||||
import { EOL } from 'os';
|
||||
import { INotebookService } from '../services/notebookService';
|
||||
import { IPlatformService } from '../services/platformService';
|
||||
import { DialogBase } from './dialogBase';
|
||||
import { Model } from './model';
|
||||
import { initializeDialog, InputComponentInfo, InputComponents, setModelValues, Validator } from './modelViewUtils';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -42,11 +42,12 @@ export class DeploymentInputDialog extends DialogBase {
|
||||
initializeDialog({
|
||||
dialogInfo: this.dialogInfo,
|
||||
container: this._dialogObject,
|
||||
inputComponents: this.inputComponents,
|
||||
onNewDisposableCreated: (disposable: vscode.Disposable): void => {
|
||||
this._toDispose.push(disposable);
|
||||
},
|
||||
onNewInputComponentCreated: (name: string, component: InputComponent, inputValueTransformer?: InputValueTransformer): void => {
|
||||
this.inputComponents[name] = { component: component, inputValueTransformer: inputValueTransformer };
|
||||
onNewInputComponentCreated: (name: string, inputComponentInfo: InputComponentInfo): void => {
|
||||
this.inputComponents[name] = inputComponentInfo;
|
||||
},
|
||||
onNewValidatorCreated: (validator: Validator): void => {
|
||||
validators.push(validator);
|
||||
|
||||
@@ -2,7 +2,14 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { NoteBookEnvironmentVariablePrefix } from '../interfaces';
|
||||
import { EOL } from 'os';
|
||||
import { ITool, NoteBookEnvironmentVariablePrefix } from '../interfaces';
|
||||
import { setEnvironmentVariablesForInstallPaths, getRuntimeBinaryPathEnvironmentVariableName } from '../utils';
|
||||
import { ToolsInstallPath } from '../constants';
|
||||
import { delimiter } from 'path';
|
||||
|
||||
|
||||
const NotebookEnvironmentVariablePrefixRegex = new RegExp(`^${NoteBookEnvironmentVariablePrefix}`);
|
||||
|
||||
export class Model {
|
||||
private propValueObject: { [s: string]: string | undefined } = {};
|
||||
@@ -32,13 +39,57 @@ export class Model {
|
||||
return value === undefined ? defaultValue : value === 'true';
|
||||
}
|
||||
|
||||
public setEnvironmentVariables(): void {
|
||||
Object.keys(this.propValueObject).filter(propertyName => propertyName.startsWith(NoteBookEnvironmentVariablePrefix)).forEach(propertyName => {
|
||||
const value = this.getStringValue(propertyName);
|
||||
if (value !== undefined && value !== '') {
|
||||
process.env[propertyName] = value;
|
||||
}
|
||||
process.env[propertyName] = value === undefined ? '' : value;
|
||||
/**
|
||||
* Returns python code statements for setting variables starting with {@see NoteBookEnvironmentVariablePrefix} as python variables.
|
||||
* The prefix {@see NoteBookEnvironmentVariablePrefix} is removed and variable name changed to all lowercase to arrive at python variable name.
|
||||
* The statements returned are escaped for use in cell of a python notebook.
|
||||
*
|
||||
* @param tools - optional set of tools for which variable value setting statements need to be generated;
|
||||
* @param inputFilter - optional parameter to filter out setting of specific variable names. Every variable for which this function returns false is not included
|
||||
* in the emitted code.
|
||||
*/
|
||||
public getCodeCellContentForNotebook(tools: ITool[] = [], inputFilter: (varName: string) => boolean = () => true): string[] {
|
||||
const statements: string[] = Object.keys(this.propValueObject)
|
||||
.filter(propertyName => propertyName.startsWith(NoteBookEnvironmentVariablePrefix) && inputFilter(propertyName))
|
||||
.map(propertyName => {
|
||||
const value = this.escapeForNotebookCodeCell(this.getStringValue(propertyName, ''));
|
||||
const varName = propertyName.replace(NotebookEnvironmentVariablePrefixRegex, '').toLocaleLowerCase();
|
||||
return `${varName} = '${value}'${EOL}`;
|
||||
});
|
||||
statements.push(`print('Variables have been set successfully.')${EOL}`);
|
||||
const env: NodeJS.ProcessEnv = {};
|
||||
setEnvironmentVariablesForInstallPaths(tools, env);
|
||||
tools.forEach(tool => {
|
||||
const envVarName: string = getRuntimeBinaryPathEnvironmentVariableName(tool.name);
|
||||
statements.push(`os.environ["${envVarName}"] = "${this.escapeForNotebookCodeCell(env[envVarName]!)}"${EOL}`);
|
||||
});
|
||||
if (env[ToolsInstallPath]) {
|
||||
statements.push(`os.environ["PATH"] = os.environ["PATH"] + "${delimiter}" + "${this.escapeForNotebookCodeCell(env[ToolsInstallPath])}"${EOL}`);
|
||||
}
|
||||
statements.push(`print('Environment Variables for tools have been set successfully.')${EOL}`);
|
||||
return statements;
|
||||
}
|
||||
|
||||
protected escapeForNotebookCodeCell(original?: string): string | undefined {
|
||||
// Escape the \ character for the code cell string value
|
||||
return original && original.replace(/\\/g, '\\\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the environment variable for each model variable that starts with {@see NoteBookEnvironmentVariablePrefix} in the
|
||||
* current process.
|
||||
*
|
||||
* @param env - env variable object in which the environment variables are populated. Default: process.env
|
||||
* @param inputFilter - an optional filter to further restrict the variables that are set into the env object.
|
||||
* Every variable for which this function returns false is not included does not get the env variable set.
|
||||
* Default all variable meeting prefix requirements are set.
|
||||
*/
|
||||
public setEnvironmentVariables(env: NodeJS.ProcessEnv = process.env, inputFilter: (varName: string) => boolean = () => true): void {
|
||||
Object.keys(this.propValueObject)
|
||||
.filter(propertyName => propertyName.startsWith(NoteBookEnvironmentVariablePrefix) && inputFilter(propertyName))
|
||||
.forEach(propertyName => {
|
||||
const value = this.getStringValue(propertyName);
|
||||
env[propertyName] = value === undefined ? '' : value;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,19 +9,28 @@ import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as azurecore from '../../../azurecore/src/azurecore';
|
||||
import { azureResource } from '../../../azurecore/src/azureResource/azure-resource';
|
||||
import { AzureAccountFieldInfo, AzureLocationsFieldInfo, DialogInfoBase, FieldInfo, FieldType, FontStyle, FontWeight, LabelPosition, PageInfoBase, SectionInfo, KubeClusterContextFieldInfo } from '../interfaces';
|
||||
import { AzureAccountFieldInfo, AzureLocationsFieldInfo, ComponentCSSStyles, DialogInfoBase, FieldInfo, FieldType, KubeClusterContextFieldInfo, LabelPosition, NoteBookEnvironmentVariablePrefix, OptionsInfo, OptionsType, PageInfoBase, RowInfo, SectionInfo, TextCSSStyles } from '../interfaces';
|
||||
import * as loc from '../localizedConstants';
|
||||
import { getDefaultKubeConfigPath, getKubeConfigClusterContexts } from '../services/kubeService';
|
||||
import { getDateTimeString, getErrorMessage } from '../utils';
|
||||
import { assert, getDateTimeString, getErrorMessage } from '../utils';
|
||||
import { WizardInfoBase } from './../interfaces';
|
||||
import { Model } from './model';
|
||||
import { RadioGroupLoadingComponentBuilder } from './radioGroupLoadingComponentBuilder';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export type Validator = () => { valid: boolean, message: string };
|
||||
export type InputValueTransformer = (inputValue: string) => string;
|
||||
export type InputComponent = azdata.InputBoxComponent | azdata.DropDownComponent | azdata.CheckBoxComponent | azdata.RadioButtonComponent;
|
||||
export type InputComponents = { [s: string]: { component: InputComponent; inputValueTransformer?: InputValueTransformer } };
|
||||
export type InputComponent = azdata.TextComponent | azdata.InputBoxComponent | azdata.DropDownComponent | azdata.CheckBoxComponent | RadioGroupLoadingComponentBuilder;
|
||||
export type InputComponentInfo = {
|
||||
component: InputComponent;
|
||||
inputValueTransformer?: InputValueTransformer;
|
||||
isPassword?: boolean
|
||||
};
|
||||
|
||||
export type InputComponents = {
|
||||
[s: string]: InputComponentInfo
|
||||
};
|
||||
|
||||
export function getInputBoxComponent(name: string, inputComponents: InputComponents): azdata.InputBoxComponent {
|
||||
return <azdata.InputBoxComponent>inputComponents[name].component;
|
||||
@@ -39,40 +48,42 @@ export function getTextComponent(name: string, inputComponents: InputComponents)
|
||||
return <azdata.TextComponent>inputComponents[name].component;
|
||||
}
|
||||
|
||||
export const DefaultInputComponentWidth = '400px';
|
||||
export const DefaultLabelComponentWidth = '200px';
|
||||
export const DefaultInputWidth = '400px';
|
||||
export const DefaultLabelWidth = '200px';
|
||||
export const DefaultFieldAlignItems = undefined;
|
||||
export const DefaultFieldWidth = undefined;
|
||||
export const DefaultFieldHeight = undefined;
|
||||
|
||||
export interface DialogContext extends CreateContext {
|
||||
export interface DialogContext extends ContextBase {
|
||||
dialogInfo: DialogInfoBase;
|
||||
container: azdata.window.Dialog;
|
||||
}
|
||||
|
||||
export interface WizardPageContext extends CreateContext {
|
||||
export interface WizardPageContext extends ContextBase {
|
||||
wizardInfo: WizardInfoBase;
|
||||
pageInfo: PageInfoBase;
|
||||
page: azdata.window.WizardPage;
|
||||
container: azdata.window.Wizard;
|
||||
}
|
||||
|
||||
export interface SectionContext extends CreateContext {
|
||||
export interface SectionContext extends ContextBase {
|
||||
sectionInfo: SectionInfo;
|
||||
view: azdata.ModelView;
|
||||
}
|
||||
|
||||
interface FieldContext extends CreateContext {
|
||||
export interface FieldContext extends ContextBase {
|
||||
fieldInfo: FieldInfo;
|
||||
components: azdata.Component[];
|
||||
view: azdata.ModelView;
|
||||
}
|
||||
|
||||
interface FilePickerInputs {
|
||||
export interface FilePickerInputs {
|
||||
input: azdata.InputBoxComponent;
|
||||
browseButton: azdata.ButtonComponent;
|
||||
}
|
||||
|
||||
interface RadioOptionsInputs {
|
||||
optionsList: azdata.DivContainer;
|
||||
loader: azdata.LoadingComponent;
|
||||
interface ReadOnlyFieldInputs {
|
||||
label: azdata.TextComponent;
|
||||
text?: azdata.TextComponent;
|
||||
}
|
||||
|
||||
interface KubeClusterContextFieldContext extends FieldContext {
|
||||
@@ -87,11 +98,12 @@ interface AzureAccountFieldContext extends FieldContext {
|
||||
fieldInfo: AzureAccountFieldInfo;
|
||||
}
|
||||
|
||||
interface CreateContext {
|
||||
interface ContextBase {
|
||||
container: azdata.window.Dialog | azdata.window.Wizard;
|
||||
inputComponents: InputComponents;
|
||||
onNewValidatorCreated: (validator: Validator) => void;
|
||||
onNewDisposableCreated: (disposable: vscode.Disposable) => void;
|
||||
onNewInputComponentCreated: (name: string, component: InputComponent, inputValueTransformer?: InputValueTransformer) => void;
|
||||
onNewInputComponentCreated: (name: string, inputComponentInfo: InputComponentInfo) => void;
|
||||
}
|
||||
|
||||
export function createTextInput(view: azdata.ModelView, inputInfo: { defaultValue?: string, ariaLabel: string, required?: boolean, placeHolder?: string, width?: string, enabled?: boolean }): azdata.InputBoxComponent {
|
||||
@@ -106,12 +118,20 @@ export function createTextInput(view: azdata.ModelView, inputInfo: { defaultValu
|
||||
}).component();
|
||||
}
|
||||
|
||||
export function createLabel(view: azdata.ModelView, info: { text: string, description?: string, required?: boolean, width?: string, fontStyle?: FontStyle, fontWeight?: FontWeight, links?: azdata.LinkArea[] }): azdata.TextComponent {
|
||||
export function createLabel(view: azdata.ModelView, info: { text: string, description?: string, required?: boolean, width?: string, links?: azdata.LinkArea[], cssStyles?: TextCSSStyles }): azdata.TextComponent {
|
||||
let cssStyles: { [key: string]: string } = {};
|
||||
if (info.cssStyles !== undefined) {
|
||||
cssStyles = Object.assign(cssStyles, info.cssStyles, { 'font-style': info.cssStyles.fontStyle || 'normal', 'font-weight': info.cssStyles.fontWeight || 'normal' });
|
||||
if (info.cssStyles.color !== undefined) {
|
||||
cssStyles['color'] = info.cssStyles.color;
|
||||
}
|
||||
}
|
||||
|
||||
const text = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: info.text,
|
||||
description: info.description,
|
||||
requiredIndicator: info.required,
|
||||
CSSStyles: { 'font-style': info.fontStyle || 'normal', 'font-weight': info.fontWeight || 'normal' },
|
||||
CSSStyles: cssStyles,
|
||||
links: info.links
|
||||
}).component();
|
||||
text.width = info.width;
|
||||
@@ -157,8 +177,11 @@ export function initializeDialog(dialogContext: DialogContext): void {
|
||||
const tab = azdata.window.createTab(tabInfo.title);
|
||||
tab.registerContent((view: azdata.ModelView) => {
|
||||
const sections = tabInfo.sections.map(sectionInfo => {
|
||||
sectionInfo.inputWidth = sectionInfo.inputWidth || tabInfo.inputWidth || DefaultInputComponentWidth;
|
||||
sectionInfo.labelWidth = sectionInfo.labelWidth || tabInfo.labelWidth || DefaultLabelComponentWidth;
|
||||
sectionInfo.inputWidth = sectionInfo.inputWidth || tabInfo.inputWidth || DefaultInputWidth;
|
||||
sectionInfo.labelWidth = sectionInfo.labelWidth || tabInfo.labelWidth || DefaultLabelWidth;
|
||||
sectionInfo.fieldAlignItems = sectionInfo.fieldAlignItems || tabInfo.fieldAlignItems || DefaultFieldAlignItems;
|
||||
sectionInfo.fieldWidth = sectionInfo.fieldWidth || tabInfo.fieldWidth || DefaultFieldWidth;
|
||||
sectionInfo.fieldHeight = sectionInfo.fieldHeight || tabInfo.fieldHeight || DefaultFieldHeight;
|
||||
sectionInfo.labelPosition = sectionInfo.labelPosition || tabInfo.labelPosition;
|
||||
return createSection({
|
||||
sectionInfo: sectionInfo,
|
||||
@@ -166,7 +189,8 @@ export function initializeDialog(dialogContext: DialogContext): void {
|
||||
onNewDisposableCreated: dialogContext.onNewDisposableCreated,
|
||||
onNewInputComponentCreated: dialogContext.onNewInputComponentCreated,
|
||||
onNewValidatorCreated: dialogContext.onNewValidatorCreated,
|
||||
container: dialogContext.container
|
||||
container: dialogContext.container,
|
||||
inputComponents: dialogContext.inputComponents
|
||||
});
|
||||
});
|
||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
@@ -189,12 +213,16 @@ export function initializeDialog(dialogContext: DialogContext): void {
|
||||
export function initializeWizardPage(context: WizardPageContext): void {
|
||||
context.page.registerContent((view: azdata.ModelView) => {
|
||||
const sections = context.pageInfo.sections.map(sectionInfo => {
|
||||
sectionInfo.inputWidth = sectionInfo.inputWidth || context.pageInfo.inputWidth || context.wizardInfo.inputWidth || DefaultInputComponentWidth;
|
||||
sectionInfo.labelWidth = sectionInfo.labelWidth || context.pageInfo.labelWidth || context.wizardInfo.labelWidth || DefaultLabelComponentWidth;
|
||||
sectionInfo.inputWidth = sectionInfo.inputWidth || context.pageInfo.inputWidth || context.wizardInfo.inputWidth || DefaultInputWidth;
|
||||
sectionInfo.labelWidth = sectionInfo.labelWidth || context.pageInfo.labelWidth || context.wizardInfo.labelWidth || DefaultLabelWidth;
|
||||
sectionInfo.fieldAlignItems = sectionInfo.fieldAlignItems || context.pageInfo.fieldAlignItems || DefaultFieldAlignItems;
|
||||
sectionInfo.fieldWidth = sectionInfo.fieldWidth || context.pageInfo.fieldWidth || context.wizardInfo.fieldWidth || DefaultFieldWidth;
|
||||
sectionInfo.fieldHeight = sectionInfo.fieldHeight || context.pageInfo.fieldHeight || context.wizardInfo.fieldHeight || DefaultFieldHeight;
|
||||
sectionInfo.labelPosition = sectionInfo.labelPosition || context.pageInfo.labelPosition || context.wizardInfo.labelPosition;
|
||||
return createSection({
|
||||
view: view,
|
||||
container: context.container,
|
||||
inputComponents: context.inputComponents,
|
||||
onNewDisposableCreated: context.onNewDisposableCreated,
|
||||
onNewInputComponentCreated: context.onNewInputComponentCreated,
|
||||
onNewValidatorCreated: context.onNewValidatorCreated,
|
||||
@@ -215,16 +243,16 @@ export function initializeWizardPage(context: WizardPageContext): void {
|
||||
|
||||
export function createSection(context: SectionContext): azdata.GroupContainer {
|
||||
const components: azdata.Component[] = [];
|
||||
context.sectionInfo.inputWidth = context.sectionInfo.inputWidth || DefaultInputComponentWidth;
|
||||
context.sectionInfo.labelWidth = context.sectionInfo.labelWidth || DefaultLabelComponentWidth;
|
||||
context.sectionInfo.inputWidth = context.sectionInfo.inputWidth || DefaultInputWidth;
|
||||
context.sectionInfo.labelWidth = context.sectionInfo.labelWidth || DefaultLabelWidth;
|
||||
context.sectionInfo.fieldAlignItems = context.sectionInfo.fieldAlignItems || DefaultFieldAlignItems;
|
||||
context.sectionInfo.fieldWidth = context.sectionInfo.fieldWidth || DefaultFieldWidth;
|
||||
context.sectionInfo.fieldHeight = context.sectionInfo.fieldHeight || DefaultFieldHeight;
|
||||
if (context.sectionInfo.fields) {
|
||||
processFields(context.sectionInfo.fields, components, context);
|
||||
} else if (context.sectionInfo.rows) {
|
||||
context.sectionInfo.rows.forEach(rowInfo => {
|
||||
const rowItems: azdata.Component[] = [];
|
||||
processFields(rowInfo.fields, rowItems, context, context.sectionInfo.spaceBetweenFields || '50px');
|
||||
const row = createFlexContainer(context.view, rowItems);
|
||||
components.push(row);
|
||||
components.push(processRow(rowInfo, context));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -235,11 +263,26 @@ export function createSection(context: SectionContext): azdata.GroupContainer {
|
||||
});
|
||||
}
|
||||
|
||||
function processRow(rowInfo: RowInfo, context: SectionContext): azdata.Component {
|
||||
const items: azdata.Component[] = [];
|
||||
if ('items' in rowInfo.items[0]) { // rowInfo.items is RowInfo[]
|
||||
const rowItems = rowInfo.items as RowInfo[];
|
||||
items.push(...rowItems.map(rowInfo => processRow(rowInfo, context)));
|
||||
} else { // rowInfo.items is FieldInfo[]
|
||||
const fieldItems = rowInfo.items as FieldInfo[];
|
||||
processFields(fieldItems, items, context, context.sectionInfo.spaceBetweenFields === undefined ? '50px' : context.sectionInfo.spaceBetweenFields);
|
||||
}
|
||||
return createFlexContainer(context.view, items, true, context.sectionInfo.fieldWidth, context.sectionInfo.fieldHeight, context.sectionInfo.fieldAlignItems, rowInfo.cssStyles);
|
||||
}
|
||||
|
||||
function processFields(fieldInfoArray: FieldInfo[], components: azdata.Component[], context: SectionContext, spaceBetweenFields?: string): void {
|
||||
for (let i = 0; i < fieldInfoArray.length; i++) {
|
||||
const fieldInfo = fieldInfoArray[i];
|
||||
fieldInfo.labelWidth = fieldInfo.labelWidth || context.sectionInfo.labelWidth;
|
||||
fieldInfo.inputWidth = fieldInfo.inputWidth || context.sectionInfo.inputWidth;
|
||||
fieldInfo.fieldAlignItems = fieldInfo.fieldAlignItems || context.sectionInfo.fieldAlignItems;
|
||||
fieldInfo.fieldWidth = fieldInfo.fieldWidth || context.sectionInfo.fieldWidth;
|
||||
fieldInfo.fieldHeight = fieldInfo.fieldHeight || context.sectionInfo.fieldHeight;
|
||||
fieldInfo.labelPosition = fieldInfo.labelPosition === undefined ? context.sectionInfo.labelPosition : fieldInfo.labelPosition;
|
||||
processField({
|
||||
view: context.view,
|
||||
@@ -248,6 +291,7 @@ function processFields(fieldInfoArray: FieldInfo[], components: azdata.Component
|
||||
onNewValidatorCreated: context.onNewValidatorCreated,
|
||||
fieldInfo: fieldInfo,
|
||||
container: context.container,
|
||||
inputComponents: context.inputComponents,
|
||||
components: components
|
||||
});
|
||||
if (spaceBetweenFields && i < fieldInfoArray.length - 1) {
|
||||
@@ -256,24 +300,37 @@ function processFields(fieldInfoArray: FieldInfo[], components: azdata.Component
|
||||
}
|
||||
}
|
||||
|
||||
export function createFlexContainer(view: azdata.ModelView, items: azdata.Component[], rowLayout: boolean = true): azdata.FlexContainer {
|
||||
export function createFlexContainer(view: azdata.ModelView, items: azdata.Component[], rowLayout: boolean = true, width?: string | number, height?: string | number, alignItems?: azdata.AlignItemsType, cssStyles?: ComponentCSSStyles): azdata.FlexContainer {
|
||||
const flexFlow = rowLayout ? 'row' : 'column';
|
||||
const alignItems = rowLayout ? 'center' : undefined;
|
||||
const itemsStyle = rowLayout ? { CSSStyles: { 'margin-right': '5px' } } : {};
|
||||
return view.modelBuilder.flexContainer().withItems(items, itemsStyle).withLayout({ flexFlow: flexFlow, alignItems: alignItems }).component();
|
||||
alignItems = alignItems || (rowLayout ? 'center' : undefined);
|
||||
const itemsStyle = rowLayout ? { CSSStyles: { 'margin-right': '5px', } } : {};
|
||||
const flexLayout: azdata.FlexLayout = { flexFlow: flexFlow };
|
||||
if (height) {
|
||||
flexLayout.height = height;
|
||||
}
|
||||
if (width) {
|
||||
flexLayout.width = width;
|
||||
}
|
||||
if (alignItems) {
|
||||
flexLayout.alignItems = alignItems;
|
||||
}
|
||||
return view.modelBuilder.flexContainer().withItems(items, itemsStyle).withLayout(flexLayout).withProperties<azdata.ComponentProperties>({ CSSStyles: cssStyles || {} }).component();
|
||||
}
|
||||
|
||||
export function createGroupContainer(view: azdata.ModelView, items: azdata.Component[], layout: azdata.GroupLayout): azdata.GroupContainer {
|
||||
return view.modelBuilder.groupContainer().withItems(items).withLayout(layout).component();
|
||||
}
|
||||
|
||||
function addLabelInputPairToContainer(view: azdata.ModelView, components: azdata.Component[], label: azdata.Component, input: azdata.Component, labelPosition?: LabelPosition, additionalComponents?: azdata.Component[]) {
|
||||
const inputs = [label, input];
|
||||
function addLabelInputPairToContainer(view: azdata.ModelView, components: azdata.Component[], label: azdata.Component, input: azdata.Component | undefined, fieldInfo: FieldInfo, additionalComponents?: azdata.Component[]) {
|
||||
const inputs: azdata.Component[] = [label];
|
||||
if (input !== undefined) {
|
||||
inputs.push(input);
|
||||
}
|
||||
if (additionalComponents && additionalComponents.length > 0) {
|
||||
inputs.push(...additionalComponents);
|
||||
}
|
||||
if (labelPosition && labelPosition === LabelPosition.Left) {
|
||||
const row = createFlexContainer(view, inputs);
|
||||
if (fieldInfo.labelPosition === LabelPosition.Left) {
|
||||
const row = createFlexContainer(view, inputs, true, fieldInfo.fieldWidth, fieldInfo.fieldHeight, fieldInfo.fieldAlignItems);
|
||||
components.push(row);
|
||||
} else {
|
||||
components.push(...inputs);
|
||||
@@ -285,9 +342,6 @@ function processField(context: FieldContext): void {
|
||||
case FieldType.Options:
|
||||
processOptionsTypeField(context);
|
||||
break;
|
||||
case FieldType.RadioOptions:
|
||||
processRadioOptionsTypeField(context);
|
||||
break;
|
||||
case FieldType.DateTimeText:
|
||||
processDateTimeTextField(context);
|
||||
break;
|
||||
@@ -325,21 +379,42 @@ function processField(context: FieldContext): void {
|
||||
}
|
||||
|
||||
function processOptionsTypeField(context: FieldContext): void {
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
|
||||
assert(context.fieldInfo.options !== undefined, `FieldInfo.options must be defined for FieldType:${FieldType.Options}`);
|
||||
if (Array.isArray(context.fieldInfo.options)) {
|
||||
context.fieldInfo.options = <OptionsInfo>{
|
||||
values: context.fieldInfo.options,
|
||||
defaultValue: context.fieldInfo.defaultValue,
|
||||
optionsType: OptionsType.Dropdown
|
||||
};
|
||||
}
|
||||
assert(typeof context.fieldInfo.options === 'object', `FieldInfo.options must be an object if it is not an array`);
|
||||
assert('optionsType' in context.fieldInfo.options, `When FieldInfo.options is an object it must have 'optionsType' property`);
|
||||
if (context.fieldInfo.options.optionsType === OptionsType.Radio) {
|
||||
processRadioOptionsTypeField(context);
|
||||
} else {
|
||||
assert(context.fieldInfo.options.optionsType === OptionsType.Dropdown, `When optionsType is not ${OptionsType.Radio} then it must be ${OptionsType.Dropdown}`);
|
||||
processDropdownOptionsTypeField(context);
|
||||
}
|
||||
}
|
||||
|
||||
function processDropdownOptionsTypeField(context: FieldContext): void {
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
|
||||
const options = context.fieldInfo.options as OptionsInfo;
|
||||
const dropdown = createDropdown(context.view, {
|
||||
values: context.fieldInfo.options,
|
||||
defaultValue: context.fieldInfo.defaultValue,
|
||||
values: options.values,
|
||||
defaultValue: options.defaultValue,
|
||||
width: context.fieldInfo.inputWidth,
|
||||
editable: context.fieldInfo.editable,
|
||||
required: context.fieldInfo.required,
|
||||
label: context.fieldInfo.label
|
||||
});
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, dropdown);
|
||||
addLabelInputPairToContainer(context.view, context.components, label, dropdown, context.fieldInfo.labelPosition);
|
||||
dropdown.fireOnTextChange = true;
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: dropdown });
|
||||
addLabelInputPairToContainer(context.view, context.components, label, dropdown, context.fieldInfo);
|
||||
}
|
||||
|
||||
function processDateTimeTextField(context: FieldContext): void {
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
|
||||
const defaultValue = context.fieldInfo.defaultValue + getDateTimeString();
|
||||
const input = context.view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
value: defaultValue,
|
||||
@@ -349,12 +424,12 @@ function processDateTimeTextField(context: FieldContext): void {
|
||||
placeHolder: context.fieldInfo.placeHolder
|
||||
}).component();
|
||||
input.width = context.fieldInfo.inputWidth;
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, input);
|
||||
addLabelInputPairToContainer(context.view, context.components, label, input, context.fieldInfo.labelPosition);
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: input });
|
||||
addLabelInputPairToContainer(context.view, context.components, label, input, context.fieldInfo);
|
||||
}
|
||||
|
||||
function processNumberField(context: FieldContext): void {
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
|
||||
const input = createNumberInput(context.view, {
|
||||
defaultValue: context.fieldInfo.defaultValue,
|
||||
ariaLabel: context.fieldInfo.label,
|
||||
@@ -364,12 +439,12 @@ function processNumberField(context: FieldContext): void {
|
||||
width: context.fieldInfo.inputWidth,
|
||||
placeHolder: context.fieldInfo.placeHolder
|
||||
});
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, input);
|
||||
addLabelInputPairToContainer(context.view, context.components, label, input, context.fieldInfo.labelPosition);
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: input });
|
||||
addLabelInputPairToContainer(context.view, context.components, label, input, context.fieldInfo);
|
||||
}
|
||||
|
||||
function processTextField(context: FieldContext): void {
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
|
||||
const input = createTextInput(context.view, {
|
||||
defaultValue: context.fieldInfo.defaultValue,
|
||||
ariaLabel: context.fieldInfo.label,
|
||||
@@ -378,8 +453,8 @@ function processTextField(context: FieldContext): void {
|
||||
width: context.fieldInfo.inputWidth,
|
||||
enabled: context.fieldInfo.enabled
|
||||
});
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, input);
|
||||
addLabelInputPairToContainer(context.view, context.components, label, input, context.fieldInfo.labelPosition);
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: input });
|
||||
addLabelInputPairToContainer(context.view, context.components, label, input, context.fieldInfo);
|
||||
|
||||
if (context.fieldInfo.textValidationRequired) {
|
||||
let validationRegex: RegExp = new RegExp(context.fieldInfo.textValidationRegex!);
|
||||
@@ -399,12 +474,11 @@ function processTextField(context: FieldContext): void {
|
||||
return { valid: inputIsValid, message: context.fieldInfo.textValidationDescription! };
|
||||
};
|
||||
context.onNewValidatorCreated(inputValidator);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function processPasswordField(context: FieldContext): void {
|
||||
const passwordLabel = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
|
||||
const passwordLabel = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
|
||||
const passwordInput = context.view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
ariaLabel: context.fieldInfo.label,
|
||||
inputType: 'password',
|
||||
@@ -412,8 +486,8 @@ function processPasswordField(context: FieldContext): void {
|
||||
placeHolder: context.fieldInfo.placeHolder,
|
||||
width: context.fieldInfo.inputWidth
|
||||
}).component();
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, passwordInput);
|
||||
addLabelInputPairToContainer(context.view, context.components, passwordLabel, passwordInput, context.fieldInfo.labelPosition);
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: passwordInput, isPassword: true });
|
||||
addLabelInputPairToContainer(context.view, context.components, passwordLabel, passwordInput, context.fieldInfo);
|
||||
|
||||
if (context.fieldInfo.type === FieldType.SQLPassword) {
|
||||
const invalidPasswordMessage = getInvalidSQLPasswordMessage(context.fieldInfo.label);
|
||||
@@ -430,7 +504,7 @@ function processPasswordField(context: FieldContext): void {
|
||||
|
||||
if (context.fieldInfo.confirmationRequired) {
|
||||
const passwordNotMatchMessage = getPasswordMismatchMessage(context.fieldInfo.label);
|
||||
const confirmPasswordLabel = createLabel(context.view, { text: context.fieldInfo.confirmationLabel!, required: true, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
|
||||
const confirmPasswordLabel = createLabel(context.view, { text: context.fieldInfo.confirmationLabel!, required: true, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
|
||||
const confirmPasswordInput = context.view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
ariaLabel: context.fieldInfo.confirmationLabel,
|
||||
inputType: 'password',
|
||||
@@ -438,7 +512,7 @@ function processPasswordField(context: FieldContext): void {
|
||||
width: context.fieldInfo.inputWidth
|
||||
}).component();
|
||||
|
||||
addLabelInputPairToContainer(context.view, context.components, confirmPasswordLabel, confirmPasswordInput, context.fieldInfo.labelPosition);
|
||||
addLabelInputPairToContainer(context.view, context.components, confirmPasswordLabel, confirmPasswordInput, context.fieldInfo);
|
||||
context.onNewValidatorCreated((): { valid: boolean, message: string } => {
|
||||
const passwordMatches = passwordInput.value === confirmPasswordInput.value;
|
||||
return { valid: passwordMatches, message: passwordNotMatchMessage };
|
||||
@@ -459,34 +533,85 @@ function processPasswordField(context: FieldContext): void {
|
||||
}
|
||||
}
|
||||
|
||||
function processReadonlyTextField(context: FieldContext): void {
|
||||
let defaultValue = context.fieldInfo.defaultValue || '';
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: false, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
|
||||
const text = createLabel(context.view, { text: defaultValue, description: '', required: false, width: context.fieldInfo.inputWidth, fontWeight: context.fieldInfo.textFontWeight, fontStyle: context.fieldInfo.fontStyle, links: context.fieldInfo.links });
|
||||
addLabelInputPairToContainer(context.view, context.components, label, text, context.fieldInfo.labelPosition);
|
||||
function processReadonlyTextField(context: FieldContext, allowEvaluation: boolean = true): ReadOnlyFieldInputs {
|
||||
if ((context.fieldInfo.links?.length ?? 0) > 0) {
|
||||
return processHyperlinkedTextField(context);
|
||||
} else if (context.fieldInfo.isEvaluated && allowEvaluation) {
|
||||
return processEvaluatedTextField(context);
|
||||
}
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: false, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
|
||||
const text = context.fieldInfo.defaultValue !== undefined
|
||||
? createLabel(context.view, { text: context.fieldInfo.defaultValue, description: '', required: false, width: context.fieldInfo.inputWidth })
|
||||
: undefined;
|
||||
addLabelInputPairToContainer(context.view, context.components, label, text, context.fieldInfo);
|
||||
return { label: label, text: text };
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a text component that has text that contains hyperlinks. The context.fieldInfo.label contains {0},{1} ...
|
||||
* placeholder(s) where contents of link array object are placed with that portion interpolated as a clickable link.
|
||||
*
|
||||
* @param context - the FieldContext object using which the field gets created
|
||||
*/
|
||||
function processHyperlinkedTextField(context: FieldContext): ReadOnlyFieldInputs {
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: false, width: context.fieldInfo.labelWidth, links: context.fieldInfo.links, cssStyles: context.fieldInfo.labelCSSStyles });
|
||||
context.components.push(label);
|
||||
return { label: label };
|
||||
}
|
||||
|
||||
function processEvaluatedTextField(context: FieldContext): ReadOnlyFieldInputs {
|
||||
const readOnlyField = processReadonlyTextField(context, false /*allowEvaluation*/);
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName || context.fieldInfo.label, {
|
||||
component: readOnlyField.text!,
|
||||
inputValueTransformer: () => {
|
||||
readOnlyField.text!.value = substituteVariableValues(context.inputComponents, context.fieldInfo.defaultValue);
|
||||
return readOnlyField.text?.value!;
|
||||
}
|
||||
});
|
||||
return readOnlyField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string that interpolates all variable names in the {@param inputValue} string de-marked as $(VariableName)
|
||||
* substituted with their corresponding values.
|
||||
*
|
||||
* Only variables in the current model starting with {@see NoteBookEnvironmentVariablePrefix} are replaced.
|
||||
*
|
||||
* @param inputValue
|
||||
* @param inputComponents
|
||||
*/
|
||||
function substituteVariableValues(inputComponents: InputComponents, inputValue?: string): string | undefined {
|
||||
Object.keys(inputComponents)
|
||||
.filter(key => key.startsWith(NoteBookEnvironmentVariablePrefix))
|
||||
.forEach(key => {
|
||||
const value = getInputComponentValue(inputComponents, key) ?? '<undefined>';
|
||||
const re: RegExp = new RegExp(`\\\$\\\(${key}\\\)`, 'gi');
|
||||
inputValue = inputValue?.replace(re, value);
|
||||
});
|
||||
return inputValue;
|
||||
}
|
||||
|
||||
function processCheckboxField(context: FieldContext): void {
|
||||
const checkbox = createCheckbox(context.view, { initialValue: context.fieldInfo.defaultValue! === 'true', label: context.fieldInfo.label, required: context.fieldInfo.required });
|
||||
context.components.push(checkbox);
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, checkbox);
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: checkbox });
|
||||
}
|
||||
|
||||
/**
|
||||
* A File Picker field consists of a text field and a browse button that allows a user to pick a file system file.
|
||||
* @param context The context to use to create the field
|
||||
*/
|
||||
function processFilePickerField(context: FieldContext, defaultFilePath?: string): FilePickerInputs {
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
|
||||
function processFilePickerField(context: FieldContext): FilePickerInputs {
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
|
||||
const input = createTextInput(context.view, {
|
||||
defaultValue: defaultFilePath || context.fieldInfo.defaultValue || '',
|
||||
defaultValue: context.fieldInfo.defaultValue || '',
|
||||
ariaLabel: context.fieldInfo.label,
|
||||
required: context.fieldInfo.required,
|
||||
placeHolder: context.fieldInfo.placeHolder,
|
||||
width: context.fieldInfo.inputWidth,
|
||||
enabled: context.fieldInfo.enabled
|
||||
});
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, input);
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: input });
|
||||
input.enabled = false;
|
||||
const browseFileButton = context.view!.modelBuilder.button().withProperties({ label: loc.browse }).component();
|
||||
context.onNewDisposableCreated(browseFileButton.onDidClick(async () => {
|
||||
@@ -506,42 +631,22 @@ function processFilePickerField(context: FieldContext, defaultFilePath?: string)
|
||||
let fileUri = fileUris[0];
|
||||
input.value = fileUri.fsPath;
|
||||
}));
|
||||
addLabelInputPairToContainer(context.view, context.components, label, input, LabelPosition.Left, [browseFileButton]);
|
||||
context.fieldInfo.labelPosition = LabelPosition.Left;
|
||||
addLabelInputPairToContainer(context.view, context.components, label, input, context.fieldInfo, [browseFileButton]);
|
||||
return { input: input, browseButton: browseFileButton };
|
||||
}
|
||||
|
||||
/**
|
||||
* An Kube Config Cluster picker field consists of a file system filee picker and radio button selector for cluster contexts defined in the config filed picked using the file picker.
|
||||
* @param context The context to use to create the field
|
||||
* This function returns a method that reads the cluster context from the {@param file}. This method then returns the cluster contexts
|
||||
* read as an OptionsInfo object asynchronously.
|
||||
*
|
||||
* @param file - the file from which to fetch the cluster contexts
|
||||
*/
|
||||
async function processKubeConfigClusterPickerField(context: KubeClusterContextFieldContext): Promise<void> {
|
||||
const kubeConfigFilePathVariableName = context.fieldInfo.configFileVariableName || 'AZDATA_NB_VAR_KUBECONFIG_PATH';
|
||||
const filePickerContext: FieldContext = {
|
||||
container: context.container,
|
||||
components: context.components,
|
||||
view: context.view,
|
||||
onNewValidatorCreated: context.onNewValidatorCreated,
|
||||
onNewDisposableCreated: context.onNewDisposableCreated,
|
||||
onNewInputComponentCreated: context.onNewInputComponentCreated,
|
||||
fieldInfo: {
|
||||
label: loc.kubeConfigFilePath,
|
||||
type: FieldType.FilePicker,
|
||||
labelWidth: context.fieldInfo.labelWidth,
|
||||
variableName: kubeConfigFilePathVariableName,
|
||||
required: true
|
||||
}
|
||||
};
|
||||
const filePicker = processFilePickerField(filePickerContext, getDefaultKubeConfigPath());
|
||||
context.fieldInfo.subFields = context.fieldInfo.subFields || [];
|
||||
context.fieldInfo.subFields!.push({
|
||||
label: filePickerContext.fieldInfo.label,
|
||||
variableName: kubeConfigFilePathVariableName
|
||||
});
|
||||
context.onNewInputComponentCreated(kubeConfigFilePathVariableName, filePicker.input);
|
||||
const getClusterContexts = async () => {
|
||||
function getClusterContexts(file: string): (() => Promise<OptionsInfo>) {
|
||||
return async () => {
|
||||
try {
|
||||
let currentClusterContext = '';
|
||||
const clusterContexts: string[] = (await getKubeConfigClusterContexts(filePicker.input.value!)).map(kubeClusterContext => {
|
||||
const clusterContexts: string[] = (await getKubeConfigClusterContexts(file)).map(kubeClusterContext => {
|
||||
if (kubeClusterContext.isCurrentContext) {
|
||||
currentClusterContext = kubeClusterContext.name;
|
||||
}
|
||||
@@ -551,76 +656,78 @@ async function processKubeConfigClusterPickerField(context: KubeClusterContextFi
|
||||
throw Error(loc.clusterContextNotFound);
|
||||
}
|
||||
return { values: clusterContexts, defaultValue: currentClusterContext };
|
||||
} catch (e) {
|
||||
throw Error(localize('kubeConfigClusterPicker.errorLoadingClusters', "An error ocurred while loading or parsing the config file:{0}, error is:{1}", filePicker.input.value, getErrorMessage(e)));
|
||||
}
|
||||
catch (e) {
|
||||
throw Error(localize('getClusterContexts.errorFetchingClusters', "An error ocurred while loading or parsing the config file:{0}, error is:{1}", file, getErrorMessage(e)));
|
||||
}
|
||||
};
|
||||
|
||||
createRadioOptions(context, getClusterContexts)
|
||||
.then(clusterContextOptions => {
|
||||
filePicker.input.onTextChanged(async () => {
|
||||
await loadOrReloadRadioOptions(context, clusterContextOptions.optionsList, clusterContextOptions.loader, getClusterContexts);
|
||||
});
|
||||
}).catch(error => {
|
||||
console.log(`failed to create radio options, Error: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
async function processRadioOptionsTypeField(context: FieldContext): Promise<RadioOptionsInputs> {
|
||||
/**
|
||||
* A Kube Config Cluster picker field consists of a file system file picker and radio button selector for cluster contexts defined in the config filed picked using the file picker.
|
||||
* @param context The context to use to create the field
|
||||
*/
|
||||
async function processKubeConfigClusterPickerField(context: KubeClusterContextFieldContext): Promise<void> {
|
||||
const kubeConfigFilePathVariableName = context.fieldInfo.configFileVariableName || 'AZDATA_NB_VAR_KUBECONFIG_PATH';
|
||||
const filePickerContext: FieldContext = {
|
||||
container: context.container,
|
||||
inputComponents: context.inputComponents,
|
||||
components: context.components,
|
||||
view: context.view,
|
||||
onNewValidatorCreated: context.onNewValidatorCreated,
|
||||
onNewDisposableCreated: context.onNewDisposableCreated,
|
||||
onNewInputComponentCreated: context.onNewInputComponentCreated,
|
||||
fieldInfo: {
|
||||
label: loc.kubeConfigFilePath,
|
||||
type: FieldType.FilePicker,
|
||||
defaultValue: getDefaultKubeConfigPath(),
|
||||
inputWidth: context.fieldInfo.inputWidth,
|
||||
labelWidth: context.fieldInfo.labelWidth,
|
||||
variableName: kubeConfigFilePathVariableName,
|
||||
required: true
|
||||
}
|
||||
};
|
||||
const filePicker = processFilePickerField(filePickerContext);
|
||||
context.fieldInfo.subFields = context.fieldInfo.subFields || [];
|
||||
context.fieldInfo.subFields.push({
|
||||
label: filePickerContext.fieldInfo.label,
|
||||
variableName: kubeConfigFilePathVariableName
|
||||
});
|
||||
|
||||
const radioOptionsGroup = await createRadioOptions(context, getClusterContexts(filePicker.input.value!));
|
||||
context.onNewDisposableCreated(filePicker.input.onTextChanged(async () =>
|
||||
await radioOptionsGroup.loadOptions(getClusterContexts(filePicker.input.value!))
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
async function processRadioOptionsTypeField(context: FieldContext): Promise<RadioGroupLoadingComponentBuilder> {
|
||||
return await createRadioOptions(context);
|
||||
}
|
||||
|
||||
async function createRadioOptions(context: FieldContext, getRadioButtonInfo?: (() => Promise<{ values: string[] | azdata.CategoryValue[], defaultValue: string }>))
|
||||
: Promise<RadioOptionsInputs> {
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
|
||||
const optionsList = context.view!.modelBuilder.divContainer().withProperties<azdata.DivContainerProperties>({ clickable: false }).component();
|
||||
const radioOptionsLoadingComponent = context.view!.modelBuilder.loadingComponent().withItem(optionsList).component();
|
||||
addLabelInputPairToContainer(context.view, context.components, label, radioOptionsLoadingComponent, LabelPosition.Left);
|
||||
await loadOrReloadRadioOptions(context, optionsList, radioOptionsLoadingComponent, getRadioButtonInfo);
|
||||
return { optionsList: optionsList, loader: radioOptionsLoadingComponent };
|
||||
|
||||
async function createRadioOptions(context: FieldContext, getRadioButtonInfo?: (() => Promise<OptionsInfo>))
|
||||
: Promise<RadioGroupLoadingComponentBuilder> {
|
||||
if (context.fieldInfo.fieldAlignItems === undefined) {
|
||||
context.fieldInfo.fieldAlignItems = 'flex-start'; // by default align the items to the top.
|
||||
}
|
||||
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
|
||||
const radioGroupLoadingComponentBuilder = new RadioGroupLoadingComponentBuilder(context.view, context.onNewDisposableCreated);
|
||||
context.fieldInfo.labelPosition = LabelPosition.Left;
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: radioGroupLoadingComponentBuilder });
|
||||
addLabelInputPairToContainer(context.view, context.components, label, radioGroupLoadingComponentBuilder.component(), context.fieldInfo);
|
||||
const options = context.fieldInfo.options as OptionsInfo;
|
||||
await radioGroupLoadingComponentBuilder.loadOptions(
|
||||
getRadioButtonInfo || options); // wait for the radioGroup to be fully initialized
|
||||
return radioGroupLoadingComponentBuilder;
|
||||
}
|
||||
|
||||
async function loadOrReloadRadioOptions(context: FieldContext, optionsList: azdata.DivContainer, radioOptionsLoadingComponent: azdata.LoadingComponent, getRadioButtonInfo: (() => Promise<{ values: string[] | azdata.CategoryValue[]; defaultValue: string; }>) | undefined): Promise<void> {
|
||||
radioOptionsLoadingComponent.loading = true;
|
||||
optionsList.clearItems();
|
||||
let options: (string[] | azdata.CategoryValue[]) = context.fieldInfo.options!;
|
||||
let defaultValue: string = context.fieldInfo.defaultValue!;
|
||||
try {
|
||||
if (getRadioButtonInfo) {
|
||||
const radioButtonsInfo = await getRadioButtonInfo();
|
||||
options = radioButtonsInfo.values;
|
||||
defaultValue = radioButtonsInfo.defaultValue;
|
||||
}
|
||||
options.forEach((op: string | azdata.CategoryValue) => {
|
||||
const option: azdata.CategoryValue = (typeof op === 'string') ? { name: op, displayName: op } : op as azdata.CategoryValue;
|
||||
const radioOption = context.view!.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({
|
||||
label: option.displayName,
|
||||
checked: option.displayName === defaultValue,
|
||||
name: option.name,
|
||||
}).component();
|
||||
if (radioOption.checked) {
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, radioOption);
|
||||
}
|
||||
context.onNewDisposableCreated(radioOption.onDidClick(() => {
|
||||
// reset checked status of all remaining radioButtons
|
||||
optionsList.items.filter(otherOption => otherOption !== radioOption).forEach(otherOption => (otherOption as azdata.RadioButtonComponent).checked = false);
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, radioOption!);
|
||||
}));
|
||||
optionsList.addItem(radioOption);
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
const errorLoadingRadioOptionsLabel = context.view!.modelBuilder.text().withProperties({ value: getErrorMessage(e) }).component();
|
||||
optionsList.addItem(errorLoadingRadioOptionsLabel);
|
||||
}
|
||||
radioOptionsLoadingComponent.loading = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* An Azure Account field consists of 3 separate dropdown fields - Account, Subscription and Resource Group
|
||||
* @param context The context to use to create the field
|
||||
*/
|
||||
function processAzureAccountField(context: AzureAccountFieldContext): void {
|
||||
async function processAzureAccountField(context: AzureAccountFieldContext): Promise<void> {
|
||||
context.fieldInfo.subFields = [];
|
||||
const accountValueToAccountMap = new Map<string, azdata.Account>();
|
||||
const subscriptionValueToSubscriptionMap = new Map<string, azureResource.AzureResourceSubscription>();
|
||||
@@ -628,11 +735,12 @@ function processAzureAccountField(context: AzureAccountFieldContext): void {
|
||||
const subscriptionDropdown = createAzureSubscriptionDropdown(context, subscriptionValueToSubscriptionMap);
|
||||
const resourceGroupDropdown = createAzureResourceGroupsDropdown(context, accountDropdown, accountValueToAccountMap, subscriptionDropdown, subscriptionValueToSubscriptionMap);
|
||||
const locationDropdown = context.fieldInfo.locations && processAzureLocationsField(context);
|
||||
accountDropdown.onValueChanged(selectedItem => {
|
||||
accountDropdown.onValueChanged(async selectedItem => {
|
||||
const selectedAccount = accountValueToAccountMap.get(selectedItem.selected)!;
|
||||
handleSelectedAccountChanged(context, selectedAccount, subscriptionDropdown, subscriptionValueToSubscriptionMap, resourceGroupDropdown, locationDropdown);
|
||||
await handleSelectedAccountChanged(context, selectedAccount, subscriptionDropdown, subscriptionValueToSubscriptionMap, resourceGroupDropdown, locationDropdown);
|
||||
});
|
||||
azdata.accounts.getAllAccounts().then((accounts: azdata.Account[]) => {
|
||||
try {
|
||||
const accounts = await azdata.accounts.getAllAccounts();
|
||||
// Append a blank value for the "default" option if the field isn't required, context will clear all the dropdowns when selected
|
||||
const dropdownValues = context.fieldInfo.required ? [] : [''];
|
||||
accountDropdown.values = dropdownValues.concat(accounts.map(account => {
|
||||
@@ -641,8 +749,10 @@ function processAzureAccountField(context: AzureAccountFieldContext): void {
|
||||
return displayName;
|
||||
}));
|
||||
const selectedAccount = accountDropdown.value ? accountValueToAccountMap.get(accountDropdown.value.toString()) : undefined;
|
||||
handleSelectedAccountChanged(context, selectedAccount, subscriptionDropdown, subscriptionValueToSubscriptionMap, resourceGroupDropdown, locationDropdown);
|
||||
}, (err: any) => console.log(`Unexpected error fetching accounts: ${err}`));
|
||||
await handleSelectedAccountChanged(context, selectedAccount, subscriptionDropdown, subscriptionValueToSubscriptionMap, resourceGroupDropdown, locationDropdown);
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(localize('azure.accounts.unexpectedAccountsError', 'Unexpected error fetching accounts: ${0}', getErrorMessage(error)));
|
||||
}
|
||||
}
|
||||
|
||||
function createAzureAccountDropdown(context: AzureAccountFieldContext): azdata.DropDownComponent {
|
||||
@@ -651,7 +761,7 @@ function createAzureAccountDropdown(context: AzureAccountFieldContext): azdata.D
|
||||
description: context.fieldInfo.description,
|
||||
required: context.fieldInfo.required,
|
||||
width: context.fieldInfo.labelWidth,
|
||||
fontWeight: context.fieldInfo.labelFontWeight
|
||||
cssStyles: context.fieldInfo.labelCSSStyles
|
||||
});
|
||||
const accountDropdown = createDropdown(context.view, {
|
||||
width: context.fieldInfo.inputWidth,
|
||||
@@ -659,8 +769,9 @@ function createAzureAccountDropdown(context: AzureAccountFieldContext): azdata.D
|
||||
required: context.fieldInfo.required,
|
||||
label: loc.account
|
||||
});
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, accountDropdown);
|
||||
addLabelInputPairToContainer(context.view, context.components, label, accountDropdown, context.fieldInfo.labelPosition);
|
||||
accountDropdown.fireOnTextChange = true;
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: accountDropdown });
|
||||
addLabelInputPairToContainer(context.view, context.components, label, accountDropdown, context.fieldInfo);
|
||||
return accountDropdown;
|
||||
}
|
||||
|
||||
@@ -671,36 +782,48 @@ function createAzureSubscriptionDropdown(
|
||||
text: loc.subscription,
|
||||
required: context.fieldInfo.required,
|
||||
width: context.fieldInfo.labelWidth,
|
||||
fontWeight: context.fieldInfo.labelFontWeight
|
||||
cssStyles: context.fieldInfo.labelCSSStyles
|
||||
});
|
||||
const subscriptionDropdown = createDropdown(context.view, {
|
||||
defaultValue: (context.fieldInfo.required) ? undefined : '',
|
||||
width: context.fieldInfo.inputWidth,
|
||||
editable: false,
|
||||
required: context.fieldInfo.required,
|
||||
label: loc.subscription
|
||||
});
|
||||
subscriptionDropdown.fireOnTextChange = true;
|
||||
context.fieldInfo.subFields!.push({
|
||||
label: label.value!,
|
||||
variableName: context.fieldInfo.subscriptionVariableName
|
||||
});
|
||||
context.onNewInputComponentCreated(context.fieldInfo.subscriptionVariableName!, subscriptionDropdown, (inputValue: string) => {
|
||||
return subscriptionValueToSubscriptionMap.get(inputValue)?.id || inputValue;
|
||||
context.onNewInputComponentCreated(context.fieldInfo.subscriptionVariableName!, {
|
||||
component: subscriptionDropdown,
|
||||
inputValueTransformer: (inputValue: string) => {
|
||||
return subscriptionValueToSubscriptionMap.get(inputValue)?.id || inputValue;
|
||||
}
|
||||
});
|
||||
addLabelInputPairToContainer(context.view, context.components, label, subscriptionDropdown, context.fieldInfo.labelPosition);
|
||||
if (context.fieldInfo.displaySubscriptionVariableName) {
|
||||
context.fieldInfo.subFields!.push({
|
||||
label: label.value!,
|
||||
variableName: context.fieldInfo.displaySubscriptionVariableName
|
||||
});
|
||||
context.onNewInputComponentCreated(context.fieldInfo.displaySubscriptionVariableName, { component: subscriptionDropdown });
|
||||
}
|
||||
addLabelInputPairToContainer(context.view, context.components, label, subscriptionDropdown, context.fieldInfo);
|
||||
return subscriptionDropdown;
|
||||
}
|
||||
|
||||
function handleSelectedAccountChanged(
|
||||
async function handleSelectedAccountChanged(
|
||||
context: AzureAccountFieldContext,
|
||||
selectedAccount: azdata.Account | undefined,
|
||||
subscriptionDropdown: azdata.DropDownComponent,
|
||||
subscriptionValueToSubscriptionMap: Map<string, azureResource.AzureResourceSubscription>,
|
||||
resourceGroupDropdown: azdata.DropDownComponent,
|
||||
locationDropdown?: azdata.DropDownComponent
|
||||
): void {
|
||||
): Promise<void> {
|
||||
subscriptionValueToSubscriptionMap.clear();
|
||||
subscriptionDropdown.values = [];
|
||||
handleSelectedSubscriptionChanged(context, selectedAccount, undefined, resourceGroupDropdown);
|
||||
await handleSelectedSubscriptionChanged(context, selectedAccount, undefined, resourceGroupDropdown);
|
||||
if (!selectedAccount) {
|
||||
subscriptionDropdown.values = [''];
|
||||
if (locationDropdown) {
|
||||
@@ -715,7 +838,8 @@ function handleSelectedAccountChanged(
|
||||
}
|
||||
}
|
||||
|
||||
vscode.commands.executeCommand<azurecore.GetSubscriptionsResult>('azure.accounts.getSubscriptions', selectedAccount, true /*ignoreErrors*/).then(response => {
|
||||
try {
|
||||
const response = await vscode.commands.executeCommand<azurecore.GetSubscriptionsResult>('azure.accounts.getSubscriptions', selectedAccount, true /*ignoreErrors*/);
|
||||
if (!response) {
|
||||
return;
|
||||
}
|
||||
@@ -732,8 +856,10 @@ function handleSelectedAccountChanged(
|
||||
return displayName;
|
||||
}).sort((a: string, b: string) => a.toLocaleLowerCase().localeCompare(b.toLocaleLowerCase()));
|
||||
const selectedSubscription = subscriptionDropdown.values.length > 0 ? subscriptionValueToSubscriptionMap.get(subscriptionDropdown.values[0]) : undefined;
|
||||
handleSelectedSubscriptionChanged(context, selectedAccount, selectedSubscription, resourceGroupDropdown);
|
||||
}, err => { vscode.window.showErrorMessage(localize('azure.accounts.unexpectedSubscriptionsError', "Unexpected error fetching subscriptions for account {0} ({1}): {2}", selectedAccount?.displayInfo.displayName, selectedAccount?.key.accountId, err.message)); });
|
||||
await handleSelectedSubscriptionChanged(context, selectedAccount, selectedSubscription, resourceGroupDropdown);
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(localize('azure.accounts.unexpectedSubscriptionsError', "Unexpected error fetching subscriptions for account {0} ({1}): {2}", selectedAccount?.displayInfo.displayName, selectedAccount?.key.accountId, getErrorMessage(error)));
|
||||
}
|
||||
}
|
||||
|
||||
function createAzureResourceGroupsDropdown(
|
||||
@@ -746,35 +872,42 @@ function createAzureResourceGroupsDropdown(
|
||||
text: loc.resourceGroup,
|
||||
required: context.fieldInfo.required,
|
||||
width: context.fieldInfo.labelWidth,
|
||||
fontWeight: context.fieldInfo.labelFontWeight
|
||||
cssStyles: context.fieldInfo.labelCSSStyles
|
||||
});
|
||||
const resourceGroupDropdown = createDropdown(context.view, {
|
||||
defaultValue: (context.fieldInfo.required) ? undefined : '',
|
||||
width: context.fieldInfo.inputWidth,
|
||||
editable: false,
|
||||
required: context.fieldInfo.required,
|
||||
label: loc.resourceGroup
|
||||
});
|
||||
resourceGroupDropdown.fireOnTextChange = true;
|
||||
context.fieldInfo.subFields!.push({
|
||||
label: label.value!,
|
||||
variableName: context.fieldInfo.resourceGroupVariableName
|
||||
});
|
||||
context.onNewInputComponentCreated(context.fieldInfo.resourceGroupVariableName!, resourceGroupDropdown);
|
||||
addLabelInputPairToContainer(context.view, context.components, label, resourceGroupDropdown, context.fieldInfo.labelPosition);
|
||||
subscriptionDropdown.onValueChanged(selectedItem => {
|
||||
const rgValueChangedEmitter = new vscode.EventEmitter<void>();
|
||||
resourceGroupDropdown.onValueChanged(() => rgValueChangedEmitter.fire());
|
||||
context.onNewInputComponentCreated(context.fieldInfo.resourceGroupVariableName!, { component: resourceGroupDropdown });
|
||||
addLabelInputPairToContainer(context.view, context.components, label, resourceGroupDropdown, context.fieldInfo);
|
||||
subscriptionDropdown.onValueChanged(async selectedItem => {
|
||||
const selectedAccount = !accountDropdown || !accountDropdown.value ? undefined : accountValueToAccountMap.get(accountDropdown.value.toString());
|
||||
const selectedSubscription = subscriptionValueToSubscriptionMap.get(selectedItem.selected);
|
||||
handleSelectedSubscriptionChanged(context, selectedAccount, selectedSubscription, resourceGroupDropdown);
|
||||
await handleSelectedSubscriptionChanged(context, selectedAccount, selectedSubscription, resourceGroupDropdown);
|
||||
rgValueChangedEmitter.fire();
|
||||
});
|
||||
return resourceGroupDropdown;
|
||||
}
|
||||
|
||||
function handleSelectedSubscriptionChanged(context: AzureAccountFieldContext, selectedAccount: azdata.Account | undefined, selectedSubscription: azureResource.AzureResourceSubscription | undefined, resourceGroupDropdown: azdata.DropDownComponent): void {
|
||||
async function handleSelectedSubscriptionChanged(context: AzureAccountFieldContext, selectedAccount: azdata.Account | undefined, selectedSubscription: azureResource.AzureResourceSubscription | undefined, resourceGroupDropdown: azdata.DropDownComponent): Promise<void> {
|
||||
resourceGroupDropdown.values = [''];
|
||||
if (!selectedAccount || !selectedSubscription) {
|
||||
// Don't need to execute command if we don't have both an account and subscription selected
|
||||
return;
|
||||
}
|
||||
vscode.commands.executeCommand<azurecore.GetResourceGroupsResult>('azure.accounts.getResourceGroups', selectedAccount, selectedSubscription, true /*ignoreErrors*/).then(response => {
|
||||
try {
|
||||
const response = await vscode.commands.executeCommand<azurecore.GetResourceGroupsResult>('azure.accounts.getResourceGroups', selectedAccount, selectedSubscription, true /*ignoreErrors*/);
|
||||
//.then(response => {
|
||||
if (!response) {
|
||||
return;
|
||||
}
|
||||
@@ -788,7 +921,9 @@ function handleSelectedSubscriptionChanged(context: AzureAccountFieldContext, se
|
||||
resourceGroupDropdown.values = (response.resourceGroups.length !== 0)
|
||||
? response.resourceGroups.map(resourceGroup => resourceGroup.name).sort((a: string, b: string) => a.toLocaleLowerCase().localeCompare(b.toLocaleLowerCase()))
|
||||
: [''];
|
||||
}, err => { vscode.window.showErrorMessage(localize('azure.accounts.unexpectedResourceGroupsError', "Unexpected error fetching resource groups for subscription {0} ({1}): {2}", selectedSubscription?.name, selectedSubscription?.id, err.message)); });
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(localize('azure.accounts.unexpectedResourceGroupsError', "Unexpected error fetching resource groups for subscription {0} ({1}): {2}", selectedSubscription?.name, selectedSubscription?.id, getErrorMessage(error)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -809,7 +944,7 @@ function processAzureLocationsField(context: AzureLocationsFieldContext): azdata
|
||||
text: context.fieldInfo.label || loc.location,
|
||||
required: context.fieldInfo.required,
|
||||
width: context.fieldInfo.labelWidth,
|
||||
fontWeight: context.fieldInfo.labelFontWeight
|
||||
cssStyles: context.fieldInfo.labelCSSStyles
|
||||
});
|
||||
const locationDropdown = createDropdown(context.view, {
|
||||
width: context.fieldInfo.inputWidth,
|
||||
@@ -818,14 +953,18 @@ function processAzureLocationsField(context: AzureLocationsFieldContext): azdata
|
||||
label: loc.location,
|
||||
values: context.fieldInfo.locations
|
||||
});
|
||||
locationDropdown.fireOnTextChange = true;
|
||||
context.fieldInfo.subFields = context.fieldInfo.subFields || [];
|
||||
if (context.fieldInfo.locationVariableName) {
|
||||
context.fieldInfo.subFields!.push({
|
||||
label: label.value!,
|
||||
variableName: context.fieldInfo.locationVariableName
|
||||
});
|
||||
context.onNewInputComponentCreated(context.fieldInfo.locationVariableName, locationDropdown, (inputValue: string) => {
|
||||
return knownAzureLocationNameMappings.get(inputValue) || inputValue;
|
||||
context.onNewInputComponentCreated(context.fieldInfo.locationVariableName, {
|
||||
component: locationDropdown,
|
||||
inputValueTransformer: (inputValue: string) => {
|
||||
return knownAzureLocationNameMappings.get(inputValue) || inputValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (context.fieldInfo.displayLocationVariableName) {
|
||||
@@ -833,12 +972,15 @@ function processAzureLocationsField(context: AzureLocationsFieldContext): azdata
|
||||
label: label.value!,
|
||||
variableName: context.fieldInfo.displayLocationVariableName
|
||||
});
|
||||
context.onNewInputComponentCreated(context.fieldInfo.displayLocationVariableName, locationDropdown);
|
||||
context.onNewInputComponentCreated(context.fieldInfo.displayLocationVariableName, { component: locationDropdown });
|
||||
}
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, locationDropdown, (inputValue: string) => {
|
||||
return knownAzureLocationNameMappings.get(inputValue) || inputValue;
|
||||
context.onNewInputComponentCreated(context.fieldInfo.variableName!, {
|
||||
component: locationDropdown,
|
||||
inputValueTransformer: (inputValue: string) => {
|
||||
return knownAzureLocationNameMappings.get(inputValue) || inputValue;
|
||||
}
|
||||
});
|
||||
addLabelInputPairToContainer(context.view, context.components, label, locationDropdown, context.fieldInfo.labelPosition);
|
||||
addLabelInputPairToContainer(context.view, context.components, label, locationDropdown, context.fieldInfo);
|
||||
return locationDropdown;
|
||||
}
|
||||
|
||||
@@ -871,31 +1013,38 @@ export function getPasswordMismatchMessage(fieldName: string): string {
|
||||
|
||||
export function setModelValues(inputComponents: InputComponents, model: Model): void {
|
||||
Object.keys(inputComponents).forEach(key => {
|
||||
let value;
|
||||
const input = inputComponents[key].component;
|
||||
if ('name' in input && 'checked' in input) { //RadioButtonComponent
|
||||
value = input.name;
|
||||
} else if ('checked' in input) { // CheckBoxComponent
|
||||
value = input.checked ? 'true' : 'false';
|
||||
} else if ('value' in input) { // InputBoxComponent or DropDownComponent
|
||||
const inputValue = input.value;
|
||||
if (typeof inputValue === 'string' || typeof inputValue === 'undefined') {
|
||||
value = inputValue;
|
||||
} else {
|
||||
value = inputValue.name;
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unknown input type with ID ${input.id}`);
|
||||
}
|
||||
|
||||
const inputValueTransformer = inputComponents[key].inputValueTransformer;
|
||||
if (inputValueTransformer) {
|
||||
value = inputValueTransformer(value || '');
|
||||
}
|
||||
const value = getInputComponentValue(inputComponents, key);
|
||||
model.setPropertyValue(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
function getInputComponentValue(inputComponents: InputComponents, key: string): string | undefined {
|
||||
const input = inputComponents[key].component;
|
||||
if (input === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
let value;
|
||||
if (input instanceof RadioGroupLoadingComponentBuilder) {
|
||||
value = input.value;
|
||||
} else if ('checked' in input) { // CheckBoxComponent
|
||||
value = input.checked ? 'true' : 'false';
|
||||
} else if ('value' in input) { // InputBoxComponent or DropDownComponent
|
||||
const inputValue = input.value;
|
||||
if (typeof inputValue === 'string' || typeof inputValue === 'undefined') {
|
||||
value = inputValue;
|
||||
} else {
|
||||
value = inputValue.name;
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unknown input type with ID ${input.id}`);
|
||||
}
|
||||
const inputValueTransformer = inputComponents[key].inputValueTransformer;
|
||||
if (inputValueTransformer) {
|
||||
value = inputValueTransformer(value || '');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function isInputBoxEmpty(input: azdata.InputBoxComponent): boolean {
|
||||
return input.value === undefined || input.value === '';
|
||||
}
|
||||
|
||||
@@ -2,20 +2,23 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { INotebookService } from '../../services/notebookService';
|
||||
import { INotebookService, Notebook } from '../../services/notebookService';
|
||||
import { IToolsService } from '../../services/toolsService';
|
||||
import { Model } from '../model';
|
||||
import { InputComponents, setModelValues } from '../modelViewUtils';
|
||||
import { WizardBase } from '../wizardBase';
|
||||
import { WizardPageBase } from '../wizardPageBase';
|
||||
import { DeploymentType, NotebookWizardInfo } from './../../interfaces';
|
||||
import { IPlatformService } from './../../services/platformService';
|
||||
import { NotebookWizardAutoSummaryPage } from './notebookWizardAutoSummaryPage';
|
||||
import { NotebookWizardPage } from './notebookWizardPage';
|
||||
import { NotebookWizardSummaryPage } from './notebookWizardSummaryPage';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class NotebookWizard extends WizardBase<NotebookWizard, Model> {
|
||||
export class NotebookWizard extends WizardBase<NotebookWizard, NotebookWizardPage, Model> {
|
||||
private _inputComponents: InputComponents = {};
|
||||
|
||||
public get notebookService(): INotebookService {
|
||||
return this._notebookService;
|
||||
@@ -29,8 +32,15 @@ export class NotebookWizard extends WizardBase<NotebookWizard, Model> {
|
||||
return this._wizardInfo;
|
||||
}
|
||||
|
||||
constructor(private _wizardInfo: NotebookWizardInfo, private _notebookService: INotebookService, private _platformService: IPlatformService) {
|
||||
public get inputComponents(): InputComponents {
|
||||
return this._inputComponents;
|
||||
}
|
||||
|
||||
constructor(private _wizardInfo: NotebookWizardInfo, private _notebookService: INotebookService, private _platformService: IPlatformService, private _toolsService: IToolsService) {
|
||||
super(_wizardInfo.title, new Model());
|
||||
if (this._wizardInfo.codeCellInsertionPosition === undefined) {
|
||||
this._wizardInfo.codeCellInsertionPosition = 0;
|
||||
}
|
||||
this.wizardObject.doneButton.label = _wizardInfo.actionText || this.wizardObject.doneButton.label;
|
||||
}
|
||||
|
||||
@@ -41,31 +51,64 @@ export class NotebookWizard extends WizardBase<NotebookWizard, Model> {
|
||||
protected initialize(): void {
|
||||
this.setPages(this.getPages());
|
||||
this.wizardObject.generateScriptButton.hidden = true;
|
||||
this.wizardInfo.actionText = this.wizardInfo.actionText || localize('deployCluster.ScriptToNotebook', "Script to Notebook");
|
||||
this.wizardInfo.actionText = this.wizardInfo.actionText || localize('notebookWizard.ScriptToNotebook', "Script to Notebook");
|
||||
this.wizardObject.doneButton.label = this.wizardInfo.actionText;
|
||||
}
|
||||
|
||||
protected onCancel(): void {
|
||||
}
|
||||
|
||||
protected onOk(): void {
|
||||
this.model.setEnvironmentVariables();
|
||||
if (this.wizardInfo.runNotebook) {
|
||||
this.notebookService.backgroundExecuteNotebook(this.wizardInfo.taskName, this.wizardInfo.notebook, 'deploy', this.platformService);
|
||||
} else {
|
||||
this.notebookService.launchNotebook(this.wizardInfo.notebook).then(() => { }, (error) => {
|
||||
vscode.window.showErrorMessage(error);
|
||||
});
|
||||
protected async onOk(): Promise<void> {
|
||||
setModelValues(this.inputComponents, this.model);
|
||||
const env: NodeJS.ProcessEnv = {};
|
||||
this.model.setEnvironmentVariables(env, (varName) => {
|
||||
const isPassword = !!this.inputComponents[varName]?.isPassword;
|
||||
return isPassword;
|
||||
});
|
||||
console.log(`TCL:: env`, env);
|
||||
const notebook: Notebook = await this.notebookService.getNotebook(this.wizardInfo.notebook);
|
||||
// generate python code statements for all variables captured by the wizard
|
||||
const statements = this.model.getCodeCellContentForNotebook(
|
||||
this._toolsService.toolsForCurrentProvider,
|
||||
(varName) => {
|
||||
const isPassword = !!this.inputComponents[varName]?.isPassword;
|
||||
return !isPassword;
|
||||
}
|
||||
);
|
||||
// insert generated code statements into the notebook.
|
||||
notebook.cells.splice(
|
||||
this.wizardInfo.codeCellInsertionPosition ?? 0,
|
||||
0,
|
||||
{
|
||||
cell_type: 'code',
|
||||
source: statements,
|
||||
metadata: {},
|
||||
outputs: [],
|
||||
execution_count: 0
|
||||
}
|
||||
);
|
||||
try {
|
||||
if (this.wizardInfo.runNotebook) {
|
||||
this.notebookService.backgroundExecuteNotebook(this.wizardInfo.taskName, notebook, 'deploy', this.platformService, env);
|
||||
} else {
|
||||
Object.assign(process.env, env);
|
||||
const title = path.basename(this.notebookService.getNotebookPath(this.wizardInfo.notebook));
|
||||
await this.notebookService.launchNotebookWithContent(title, JSON.stringify(notebook, undefined, 4));
|
||||
}
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(error);
|
||||
}
|
||||
}
|
||||
|
||||
private getPages(): WizardPageBase<NotebookWizard>[] {
|
||||
const pages: WizardPageBase<NotebookWizard>[] = [];
|
||||
private getPages(): NotebookWizardPage[] {
|
||||
const pages: NotebookWizardPage[] = [];
|
||||
for (let pageIndex: number = 0; pageIndex < this.wizardInfo.pages.length; pageIndex++) {
|
||||
pages.push(new NotebookWizardPage(this, pageIndex));
|
||||
}
|
||||
if (this.wizardInfo.generateSummaryPage) {
|
||||
pages.push(new NotebookWizardSummaryPage(this));
|
||||
if (this.wizardInfo.pages[pageIndex].isSummaryPage && this.wizardInfo.isSummaryPageAutoGenerated) {
|
||||
// If we are auto-generating the summary page
|
||||
pages.push(new NotebookWizardAutoSummaryPage(this, pageIndex));
|
||||
} else {
|
||||
pages.push(new NotebookWizardPage(this, pageIndex));
|
||||
}
|
||||
}
|
||||
return pages;
|
||||
}
|
||||
|
||||
@@ -6,19 +6,23 @@ import * as azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
|
||||
import { SubFieldInfo, FieldType, FontWeight, LabelPosition, SectionInfo } from '../../interfaces';
|
||||
import { createSection, DefaultInputComponentWidth, DefaultLabelComponentWidth } from '../modelViewUtils';
|
||||
import { WizardPageBase } from '../wizardPageBase';
|
||||
import { createSection, DefaultInputWidth, DefaultLabelWidth, DefaultFieldAlignItems, DefaultFieldWidth, DefaultFieldHeight } from '../modelViewUtils';
|
||||
import { NotebookWizard } from './notebookWizard';
|
||||
import { NotebookWizardPage } from './notebookWizardPage';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class NotebookWizardSummaryPage extends WizardPageBase<NotebookWizard> {
|
||||
export class NotebookWizardAutoSummaryPage extends NotebookWizardPage {
|
||||
private formItems: azdata.FormComponent[] = [];
|
||||
private form!: azdata.FormBuilder;
|
||||
private view!: azdata.ModelView;
|
||||
|
||||
constructor(wizard: NotebookWizard) {
|
||||
super(localize('notebookWizard.summaryPageTitle', "Review your configuration"), '', wizard);
|
||||
constructor(wizard: NotebookWizard, _pageIndex: number) {
|
||||
super(wizard,
|
||||
_pageIndex,
|
||||
wizard.wizardInfo.pages[_pageIndex].title || localize('notebookWizard.autoSummaryPageTitle', "Review your configuration"),
|
||||
wizard.wizardInfo.pages[_pageIndex].description || ''
|
||||
);
|
||||
}
|
||||
|
||||
public initialize(): void {
|
||||
@@ -29,25 +33,31 @@ export class NotebookWizardSummaryPage extends WizardPageBase<NotebookWizard> {
|
||||
});
|
||||
}
|
||||
|
||||
public onLeave() {
|
||||
public onLeave(): void {
|
||||
this.wizard.wizardObject.message = { text: '' };
|
||||
}
|
||||
|
||||
public onEnter() {
|
||||
public onEnter(): void {
|
||||
this.formItems.forEach(item => {
|
||||
this.form!.removeFormItem(item);
|
||||
});
|
||||
this.formItems = [];
|
||||
|
||||
const inputWidth = this.wizard.wizardInfo.inputWidth || (this.wizard.wizardInfo.summaryPage && this.wizard.wizardInfo.summaryPage.inputWidth) || DefaultInputComponentWidth;
|
||||
const labelWidth = this.wizard.wizardInfo.labelWidth || (this.wizard.wizardInfo.summaryPage && this.wizard.wizardInfo.summaryPage.labelWidth) || DefaultLabelComponentWidth;
|
||||
const labelPosition = this.wizard.wizardInfo.labelPosition || (this.wizard.wizardInfo.summaryPage && this.wizard.wizardInfo.summaryPage.labelPosition) || LabelPosition.Left;
|
||||
const fieldWidth = this.pageInfo.fieldWidth || this.wizard.wizardInfo.fieldWidth || DefaultFieldWidth;
|
||||
const fieldHeight = this.pageInfo.fieldHeight || this.wizard.wizardInfo.fieldHeight || DefaultFieldHeight;
|
||||
const fieldAlignItems = this.pageInfo.fieldAlignItems || this.wizard.wizardInfo.fieldAlignItems || DefaultFieldAlignItems;
|
||||
const labelWidth = this.pageInfo.labelWidth || this.wizard.wizardInfo.labelWidth || DefaultLabelWidth;
|
||||
const labelPosition = this.pageInfo.labelPosition || this.wizard.wizardInfo.labelPosition || LabelPosition.Left;
|
||||
const inputWidth = this.pageInfo.inputWidth || this.wizard.wizardInfo.inputWidth || DefaultInputWidth;
|
||||
|
||||
this.wizard.wizardInfo.pages.forEach(pageInfo => {
|
||||
this.wizard.wizardInfo.pages.filter((undefined, index) => index < this._pageIndex).forEach(pageInfo => {
|
||||
const summarySectionInfo: SectionInfo = {
|
||||
labelPosition: labelPosition,
|
||||
labelWidth: labelWidth,
|
||||
inputWidth: inputWidth,
|
||||
fieldWidth: fieldWidth,
|
||||
fieldHeight: fieldHeight,
|
||||
fieldAlignItems: fieldAlignItems,
|
||||
title: '',
|
||||
rows: []
|
||||
};
|
||||
@@ -68,6 +78,7 @@ export class NotebookWizardSummaryPage extends WizardPageBase<NotebookWizard> {
|
||||
title: pageInfo.title,
|
||||
component: createSection({
|
||||
container: this.wizard.wizardObject,
|
||||
inputComponents: this.wizard.inputComponents,
|
||||
sectionInfo: summarySectionInfo,
|
||||
view: this.view,
|
||||
onNewDisposableCreated: () => { },
|
||||
@@ -84,11 +95,11 @@ export class NotebookWizardSummaryPage extends WizardPageBase<NotebookWizard> {
|
||||
|
||||
private addSummaryForVariable(summarySectionInfo: SectionInfo, fieldInfo: SubFieldInfo) {
|
||||
summarySectionInfo!.rows!.push({
|
||||
fields: [{
|
||||
items: [{
|
||||
type: FieldType.ReadonlyText,
|
||||
label: fieldInfo.label,
|
||||
defaultValue: this.wizard.model.getStringValue(fieldInfo.variableName!),
|
||||
labelFontWeight: FontWeight.Bold
|
||||
labelCSSStyles: { fontWeight: FontWeight.Bold }
|
||||
}]
|
||||
});
|
||||
}
|
||||
@@ -7,58 +7,73 @@ import { EOL } from 'os';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { NotebookWizardPageInfo } from '../../interfaces';
|
||||
import { initializeWizardPage, InputComponents, InputComponent, setModelValues, Validator } from '../modelViewUtils';
|
||||
import { initializeWizardPage, InputComponentInfo, setModelValues, Validator } from '../modelViewUtils';
|
||||
import { WizardPageBase } from '../wizardPageBase';
|
||||
import { NotebookWizard } from './notebookWizard';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class NotebookWizardPage extends WizardPageBase<NotebookWizard> {
|
||||
private inputComponents: InputComponents = {};
|
||||
|
||||
protected get pageInfo(): NotebookWizardPageInfo {
|
||||
return this.wizard.wizardInfo.pages[this._pageIndex];
|
||||
}
|
||||
|
||||
constructor(wizard: NotebookWizard, private _pageIndex: number) {
|
||||
super(wizard.wizardInfo.pages[_pageIndex].title, wizard.wizardInfo.pages[_pageIndex].description || '', wizard);
|
||||
constructor(
|
||||
wizard: NotebookWizard,
|
||||
protected _pageIndex: number,
|
||||
title?: string,
|
||||
description?: string
|
||||
) {
|
||||
super(
|
||||
wizard.wizardInfo.pages[_pageIndex].title || title || '',
|
||||
wizard.wizardInfo.pages[_pageIndex].description || description || '',
|
||||
wizard
|
||||
);
|
||||
}
|
||||
|
||||
public initialize(): void {
|
||||
const self = this;
|
||||
initializeWizardPage({
|
||||
container: this.wizard.wizardObject,
|
||||
inputComponents: this.wizard.inputComponents,
|
||||
wizardInfo: this.wizard.wizardInfo,
|
||||
pageInfo: this.pageInfo,
|
||||
page: this.pageObject,
|
||||
onNewDisposableCreated: (disposable: vscode.Disposable): void => {
|
||||
self.wizard.registerDisposable(disposable);
|
||||
this.wizard.registerDisposable(disposable);
|
||||
},
|
||||
onNewInputComponentCreated: (name: string, component: InputComponent): void => {
|
||||
self.inputComponents[name] = { component: component };
|
||||
onNewInputComponentCreated: (
|
||||
name: string,
|
||||
inputComponentInfo: InputComponentInfo
|
||||
): void => {
|
||||
if (name) {
|
||||
this.wizard.inputComponents[name] = inputComponentInfo;
|
||||
}
|
||||
},
|
||||
onNewValidatorCreated: (validator: Validator): void => {
|
||||
self.validators.push(validator);
|
||||
}
|
||||
this.validators.push(validator);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public onLeave() {
|
||||
setModelValues(this.inputComponents, this.wizard.model);
|
||||
public onLeave(): void {
|
||||
// The following callback registration clears previous navigation validators.
|
||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public onEnter() {
|
||||
public onEnter(): void {
|
||||
if (this.pageInfo.isSummaryPage) {
|
||||
setModelValues(this.wizard.inputComponents, this.wizard.model);
|
||||
}
|
||||
|
||||
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
|
||||
this.wizard.wizardObject.message = { text: '' };
|
||||
if (pcInfo.newPage > pcInfo.lastPage) {
|
||||
const messages: string[] = [];
|
||||
|
||||
this.validators.forEach(validator => {
|
||||
this.validators.forEach((validator) => {
|
||||
const result = validator();
|
||||
if (!result.valid) {
|
||||
messages.push(result.message);
|
||||
@@ -67,9 +82,15 @@ export class NotebookWizardPage extends WizardPageBase<NotebookWizard> {
|
||||
|
||||
if (messages.length > 0) {
|
||||
this.wizard.wizardObject.message = {
|
||||
text: messages.length === 1 ? messages[0] : localize('wizardPage.ValidationError', "There are some errors on this page, click 'Show Details' to view the errors."),
|
||||
text:
|
||||
messages.length === 1
|
||||
? messages[0]
|
||||
: localize(
|
||||
"wizardPage.ValidationError",
|
||||
"There are some errors on this page, click 'Show Details' to view the errors."
|
||||
),
|
||||
description: messages.length === 1 ? undefined : messages.join(EOL),
|
||||
level: azdata.window.MessageLevel.Error
|
||||
level: azdata.window.MessageLevel.Error,
|
||||
};
|
||||
}
|
||||
return messages.length === 0;
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { OptionsInfo } from '../interfaces';
|
||||
import { getErrorMessage } from '../utils';
|
||||
|
||||
export class RadioGroupLoadingComponentBuilder implements azdata.ComponentBuilder<azdata.LoadingComponent> {
|
||||
private _optionsDivContainer!: azdata.DivContainer;
|
||||
private _optionsLoadingBuilder: azdata.LoadingComponentBuilder;
|
||||
private _onValueChangedEmitter: vscode.EventEmitter<void> = new vscode.EventEmitter();
|
||||
private _currentRadioOption!: azdata.RadioButtonComponent;
|
||||
constructor(private _view: azdata.ModelView, private _onNewDisposableCreated: (disposable: vscode.Disposable) => void) {
|
||||
this._optionsDivContainer = this._view!.modelBuilder.divContainer().withProperties<azdata.DivContainerProperties>({ clickable: false }).component();
|
||||
this._optionsLoadingBuilder = this._view!.modelBuilder.loadingComponent().withItem(this._optionsDivContainer);
|
||||
}
|
||||
|
||||
component(): azdata.LoadingComponent {
|
||||
return this._optionsLoadingBuilder.component();
|
||||
}
|
||||
|
||||
withProperties<U>(properties: U): azdata.ComponentBuilder<azdata.LoadingComponent> {
|
||||
return this._optionsLoadingBuilder.withProperties(properties);
|
||||
}
|
||||
|
||||
withValidation(validation: (component: azdata.LoadingComponent) => boolean): azdata.ComponentBuilder<azdata.LoadingComponent> {
|
||||
return this._optionsLoadingBuilder.withValidation(validation);
|
||||
}
|
||||
|
||||
async loadOptions(optionsInfo: OptionsInfo | (() => Promise<OptionsInfo>)): Promise<void> {
|
||||
if (typeof optionsInfo !== 'object') {
|
||||
optionsInfo = await optionsInfo();
|
||||
}
|
||||
this.component().loading = true;
|
||||
this._optionsDivContainer.clearItems();
|
||||
let options: (string[] | azdata.CategoryValue[]) = optionsInfo.values!;
|
||||
let defaultValue: string = optionsInfo.defaultValue!;
|
||||
try {
|
||||
options.forEach((op: string | azdata.CategoryValue) => {
|
||||
const option: azdata.CategoryValue = (typeof op === 'string')
|
||||
? { name: op, displayName: op }
|
||||
: op as azdata.CategoryValue;
|
||||
const radioOption = this._view!.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({
|
||||
label: option.displayName,
|
||||
checked: option.displayName === defaultValue,
|
||||
name: option.name,
|
||||
}).component();
|
||||
if (radioOption.checked) {
|
||||
this._currentRadioOption = radioOption;
|
||||
this._onValueChangedEmitter.fire();
|
||||
}
|
||||
this._onNewDisposableCreated(radioOption.onDidClick(() => {
|
||||
this._optionsDivContainer.items
|
||||
.filter(otherOption => otherOption !== radioOption)
|
||||
.forEach(otherOption => (otherOption as azdata.RadioButtonComponent).checked = false);
|
||||
this._currentRadioOption = radioOption;
|
||||
this._onValueChangedEmitter.fire();
|
||||
}));
|
||||
this._optionsDivContainer.addItem(radioOption);
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
const errorLoadingRadioOptionsLabel = this._view!.modelBuilder.text().withProperties({ value: getErrorMessage(e), CSSStyles: { 'color': 'Red' } }).component();
|
||||
this._optionsDivContainer.addItem(errorLoadingRadioOptionsLabel);
|
||||
}
|
||||
this.component().loading = false;
|
||||
}
|
||||
|
||||
get value(): string | undefined {
|
||||
return this._currentRadioOption?.label;
|
||||
}
|
||||
|
||||
get checked(): azdata.RadioButtonComponent {
|
||||
return this._currentRadioOption;
|
||||
}
|
||||
|
||||
get onValueChanged(): vscode.Event<void> {
|
||||
return this._onValueChangedEmitter.event;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import * as nls from 'vscode-nls';
|
||||
import { AgreementInfo, DeploymentProvider, ITool, ResourceType, ToolStatus } from '../interfaces';
|
||||
import { IResourceTypeService } from '../services/resourceTypeService';
|
||||
import { IToolsService } from '../services/toolsService';
|
||||
import { getErrorMessage, setEnvironmentVariablesForInstallPaths } from '../utils';
|
||||
import { getErrorMessage } from '../utils';
|
||||
import { DialogBase } from './dialogBase';
|
||||
import { createFlexContainer } from './modelViewUtils';
|
||||
|
||||
@@ -337,7 +337,7 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
}
|
||||
|
||||
protected onComplete(): void {
|
||||
setEnvironmentVariablesForInstallPaths(this._tools);
|
||||
this.toolsService.toolsForCurrentProvider = this._tools;
|
||||
this.resourceTypeService.startDeployment(this.getCurrentProvider());
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ import { WizardPageBase } from './wizardPageBase';
|
||||
import { Model } from './model';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export abstract class WizardBase<T, M extends Model> {
|
||||
export abstract class WizardBase<T, P extends WizardPageBase<T>, M extends Model> {
|
||||
private customButtons: azdata.window.Button[] = [];
|
||||
private pages: WizardPageBase<T>[] = [];
|
||||
public pages: P[] = [];
|
||||
|
||||
public wizardObject: azdata.window.Wizard;
|
||||
public toDispose: vscode.Disposable[] = [];
|
||||
@@ -34,8 +34,8 @@ export abstract class WizardBase<T, M extends Model> {
|
||||
newPage.onEnter();
|
||||
}));
|
||||
|
||||
this.toDispose.push(this.wizardObject.doneButton.onClick(() => {
|
||||
this.onOk();
|
||||
this.toDispose.push(this.wizardObject.doneButton.onClick(async () => {
|
||||
await this.onOk();
|
||||
this.dispose();
|
||||
}));
|
||||
this.toDispose.push(this.wizardObject.cancelButton.onClick(() => {
|
||||
@@ -52,14 +52,14 @@ export abstract class WizardBase<T, M extends Model> {
|
||||
}
|
||||
|
||||
protected abstract initialize(): void;
|
||||
protected abstract onOk(): void;
|
||||
protected abstract async onOk(): Promise<void>;
|
||||
protected abstract onCancel(): void;
|
||||
|
||||
public addButton(button: azdata.window.Button) {
|
||||
this.customButtons.push(button);
|
||||
}
|
||||
|
||||
protected setPages(pages: WizardPageBase<T>[]) {
|
||||
protected setPages(pages: P[]) {
|
||||
this.wizardObject!.pages = pages.map(p => p.pageObject);
|
||||
this.pages = pages;
|
||||
this.pages.forEach((page) => {
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { ITool, NoteBookEnvironmentVariablePrefix } from './interfaces';
|
||||
import * as path from 'path';
|
||||
import { ToolsInstallPath } from './constants';
|
||||
import { ITool, NoteBookEnvironmentVariablePrefix } from './interfaces';
|
||||
|
||||
export function getErrorMessage(error: any): string {
|
||||
return (error instanceof Error)
|
||||
@@ -21,7 +21,7 @@ export function getRuntimeBinaryPathEnvironmentVariableName(toolName: string): s
|
||||
return `${NoteBookEnvironmentVariablePrefix}${toolName.toUpperCase().replace(/ |-/g, '_')}`;
|
||||
}
|
||||
|
||||
export function setEnvironmentVariablesForInstallPaths(tools: ITool[]): void {
|
||||
export function setEnvironmentVariablesForInstallPaths(tools: ITool[], env: NodeJS.ProcessEnv = process.env): void {
|
||||
// Use Set class to make sure the collection only contains unique values.
|
||||
let installationPaths: Set<string> = new Set<string>();
|
||||
tools.forEach(t => {
|
||||
@@ -30,12 +30,18 @@ export function setEnvironmentVariablesForInstallPaths(tools: ITool[]): void {
|
||||
// construct an env variable name with NoteBookEnvironmentVariablePrefix prefix
|
||||
// and tool.name as suffix, making sure of using all uppercase characters and only _ as separator
|
||||
const envVarName = getRuntimeBinaryPathEnvironmentVariableName(t.name);
|
||||
process.env[envVarName] = t.installationPathOrAdditionalInformation;
|
||||
env[envVarName] = t.installationPathOrAdditionalInformation;
|
||||
installationPaths.add(path.dirname(t.installationPathOrAdditionalInformation));
|
||||
}
|
||||
});
|
||||
if (installationPaths.size > 0) {
|
||||
const envVarToolsInstallationPath: string = [...installationPaths.values()].join(path.delimiter);
|
||||
process.env[ToolsInstallPath] = envVarToolsInstallationPath;
|
||||
env[ToolsInstallPath] = envVarToolsInstallationPath;
|
||||
}
|
||||
}
|
||||
|
||||
export function assert(condition: boolean, message?: string): asserts condition {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"aiKey": "AIF-37eefaf0-8022-4671-a3fb-64752724682e",
|
||||
"engines": {
|
||||
"vscode": "*",
|
||||
"azdata": ">1.10.0"
|
||||
"azdata": ">=1.19.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -23,6 +23,257 @@
|
||||
],
|
||||
"contributes": {
|
||||
"resourceDeploymentTypes": [
|
||||
{
|
||||
"name": "test-wizard",
|
||||
"displayName": "%resource.type.wizard.display.name%",
|
||||
"description": "%resource.type.wizard.description%",
|
||||
"platforms": "*",
|
||||
"icon": {
|
||||
"light": "./images/book.svg",
|
||||
"dark": "./images/book_inverse.svg"
|
||||
},
|
||||
"providers": [
|
||||
{
|
||||
"notebookWizard": {
|
||||
"notebook": "%deployment-notebook-1%",
|
||||
"type": "new-arc-control-plane",
|
||||
"runNotebook": false,
|
||||
"codeCellInsertionPosition": 1,
|
||||
"actionText": "%deploy.wizard.action%",
|
||||
"title": "%wizard.new.wizard.title%",
|
||||
"name": "wizard.new.wizard",
|
||||
"labelPosition": "left",
|
||||
"generateSummaryPage": false,
|
||||
"pages": [
|
||||
{
|
||||
"title": "%wizard.select.cluster.title%",
|
||||
"sections": [
|
||||
{
|
||||
"fields": [
|
||||
{
|
||||
"type": "kube_cluster_context_picker",
|
||||
"label": "%wizard.kube.cluster.context%",
|
||||
"required": true,
|
||||
"inputWidth": "350px",
|
||||
"variableName": "AZDATA_NB_VAR_CLUSTER_CONTEXT",
|
||||
"configFileVariableName": "AZDATA_NB_VAR_CONFIG_FILE"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "%wizard.cluster.config.profile.title%",
|
||||
"sections": [
|
||||
{
|
||||
"fields": [
|
||||
{
|
||||
"type": "readonly_text",
|
||||
"label": "%wizard.project.details.description%",
|
||||
"labelWidth": "600px"
|
||||
},
|
||||
{
|
||||
"type": "options",
|
||||
"label": "%wizard.cluster.config.profile%",
|
||||
"required": true,
|
||||
"variableName": "AZDATA_NB_VAR_PROFILE",
|
||||
"editable": false,
|
||||
"options": {
|
||||
"values":[
|
||||
"aks-dev-test",
|
||||
"aks-dev-test-ha",
|
||||
"aks-dev-test",
|
||||
"aks-private-preview",
|
||||
"kubeadm-dev-test",
|
||||
"kubeadm-private-preview"
|
||||
],
|
||||
"defaultValue": "aks-dev-test",
|
||||
"optionsType": "radio"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "%wizard.dropdown.options.field%",
|
||||
"variableName": "AZDATA_NB_VAR_DROPDOWN_OPTIONS",
|
||||
"type": "options",
|
||||
"options": {
|
||||
"values": ["1","2","3"],
|
||||
"defaultValue": "2",
|
||||
"optionsType": "dropdown"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "%wizard.data.controller.create.summary.title%",
|
||||
"isSummaryPage": true,
|
||||
"fieldHeight": "16px",
|
||||
"sections": [
|
||||
{
|
||||
"title": "",
|
||||
"collapsible": false,
|
||||
"fieldWidth": "200px",
|
||||
"fieldHeight": "12px",
|
||||
"spaceBetweenFields": 0,
|
||||
"rows": [
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"label": "%wizard.summary.data.controller%",
|
||||
"type": "readonly_text",
|
||||
"enabled": true,
|
||||
"labelWidth": "185px"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"label": "%wizard.summary.estimated.cost.per.month%",
|
||||
"type": "readonly_text",
|
||||
"enabled": true,
|
||||
"labelWidth": "190px",
|
||||
"labelCSSStyles": {
|
||||
"fontWeight": "Bold"
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"label": "%wizard.summary.by.contoso%",
|
||||
"type": "readonly_text",
|
||||
"labelWidth": "185px"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"label": "%wizard.summary.free%",
|
||||
"type": "readonly_text",
|
||||
"enabled": true,
|
||||
"defaultValue": "",
|
||||
"labelWidth": "100px"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"label": "{0}",
|
||||
"type": "readonly_text",
|
||||
"enabled": true,
|
||||
"labelCSSStyles": { "color": "#0078D4" },
|
||||
"labelWidth": "67px",
|
||||
"links": [
|
||||
{
|
||||
"text": "%wizard.summary.terms.of.use%",
|
||||
"url": "https://aka.ms/eula-azdata-en"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "|",
|
||||
"type": "readonly_text",
|
||||
"enabled": true,
|
||||
"defaultValue": "",
|
||||
"labelWidth": "4px",
|
||||
"fieldWidth": "6px"
|
||||
},
|
||||
{
|
||||
"label": "{0}",
|
||||
"type": "readonly_text",
|
||||
"enabled": true,
|
||||
"labelCSSStyles": { "color": "#0078D4" },
|
||||
"labelWidth": "102px",
|
||||
"links": [
|
||||
{
|
||||
"text": "%wizard.summary.terms.privacy.policy%",
|
||||
"url": "https://go.contoso.com/fwlink/?LinkId=853010"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "%wizard.summary.terms%",
|
||||
"fieldHeight": "88px",
|
||||
"fields":[
|
||||
{
|
||||
"label": "%wizard.summary.terms.description%",
|
||||
"type": "readonly_text",
|
||||
"enabled": true,
|
||||
"labelWidth": "750px"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "%wizard.summary.kubernetes%",
|
||||
"fields":[
|
||||
{
|
||||
"label": "%wizard.summary.kube.config.file.path%",
|
||||
"type": "readonly_text",
|
||||
"isEvaluated": true,
|
||||
"defaultValue": "$(AZDATA_NB_VAR_CONFIG_FILE)"
|
||||
},
|
||||
{
|
||||
"label": "%wizard.summary.cluster.context%",
|
||||
"type": "readonly_text",
|
||||
"isEvaluated": true,
|
||||
"defaultValue": "$(AZDATA_NB_VAR_CLUSTER_CONTEXT)"
|
||||
},
|
||||
{
|
||||
"label": "%wizard.summary.profile%",
|
||||
"type": "readonly_text",
|
||||
"isEvaluated": true,
|
||||
"defaultValue": "$(AZDATA_NB_VAR_PROFILE)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"requiredTools": [
|
||||
{
|
||||
"name": "kubectl"
|
||||
}
|
||||
],
|
||||
"when": true
|
||||
}
|
||||
],
|
||||
"agreement": {
|
||||
"template": "%wizard.data.controller.agreement%",
|
||||
"links": [
|
||||
{
|
||||
"text": "%contoso.agreement.privacy.statement%",
|
||||
"url": "https://go.contoso.com/fwlink/?LinkId=853010"
|
||||
},
|
||||
{
|
||||
"text": "%wizard.agreement.contosoCmd.eula%",
|
||||
"url": "https://aka.ms/eula-contosoCmd-en"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "x-data-service",
|
||||
"displayName": "%resource-type-display-name%",
|
||||
@@ -81,6 +332,14 @@
|
||||
"defaultValue": "",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "kube_cluster_context_picker",
|
||||
"label": "%kube.cluster.context%",
|
||||
"required": true,
|
||||
"inputWidth": "350px",
|
||||
"variableName": "AZDATA_NB_VAR_CLUSTER_CONTEXT",
|
||||
"configFileVariableName": "AZDATA_NB_VAR_CONFIG_FILE"
|
||||
},
|
||||
{
|
||||
"label": "%number-field%",
|
||||
"variableName": "AZDATA_NB_VAR_NUMBER",
|
||||
@@ -159,7 +418,7 @@
|
||||
"links": [
|
||||
{
|
||||
"text": "%agreement-1-name%",
|
||||
"url": "https://www.microsoft.com"
|
||||
"url": "https://www.contoso.com"
|
||||
},
|
||||
{
|
||||
"text": "%agreement-2-name%",
|
||||
|
||||
@@ -12,9 +12,41 @@
|
||||
"deployment-notebook-2": "./notebooks/deploy-x-data-service-2.ipynb",
|
||||
"text-field": "text field",
|
||||
"password-field": "password field",
|
||||
"kube.cluster.context": "Kube cluster context",
|
||||
"number-field": "numeric field",
|
||||
"confirm-password": "confirm password",
|
||||
"agreement": "I accept {0} and {1}.",
|
||||
"agreement-1-name": "Agreement 1",
|
||||
"agreement-2-name": "Agreement 2"
|
||||
"agreement-2-name": "Agreement 2",
|
||||
|
||||
"resource.type.wizard.display.name": "Test controller",
|
||||
"resource.type.wizard.description": "Creates a Test controller",
|
||||
|
||||
"wizard.new.wizard.title": "Create Test controller",
|
||||
"wizard.cluster.environment.title": "What is your target existing Kubernetes cluster environment?",
|
||||
"wizard.select.cluster.title": "Select from installed existing Kubernetes clusters",
|
||||
"wizard.kube.cluster.context": "Cluster context",
|
||||
"wizard.cluster.config.profile.title": "Choose the config profile",
|
||||
"wizard.cluster.config.profile": "Config profile",
|
||||
"wizard.dropdown.options.field": "dropdown field",
|
||||
"wizard.project.details.title": "Project details",
|
||||
"wizard.project.details.description": "Project details for Contoso corporation",
|
||||
"wizard.data.controller.create.summary.title": "Review your configuration",
|
||||
"wizard.summary.data.controller": "Test controller",
|
||||
"wizard.summary.estimated.cost.per.month": "Estimated cost per month",
|
||||
"wizard.summary.by.contoso" : "by Contoso",
|
||||
"wizard.summary.free" : "Free",
|
||||
"wizard.summary.terms.of.use" : "Terms of use",
|
||||
"wizard.summary.terms.privacy.policy" : "Privacy policy",
|
||||
"wizard.summary.terms" : "Terms",
|
||||
"wizard.summary.terms.description": "By clicking 'Script to notebook', I (a) agree to the legal terms and privacy statement(s) associated with the doing business with Contoso.",
|
||||
"wizard.summary.kubernetes": "Kubernetes",
|
||||
"wizard.summary.kube.config.file.path": "Kube config file path",
|
||||
"wizard.summary.cluster.context": "Cluster context",
|
||||
"wizard.summary.profile": "Config profile",
|
||||
"wizard.data.controller.agreement": "I accept {0} and {1}.",
|
||||
"contoso.agreement.privacy.statement":"contoso Privacy Statement",
|
||||
"wizard.agreement.contosoCmd.eula":"contoso cmd license terms",
|
||||
"deploy.wizard.action":"Script to notebook"
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user