mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-06-18 17:45:08 -04:00
* dropdown not supporting dynamic enablement * paused for weekend * Make connection mode display with capitalization but value lowercase * Undo changes in modelviewutils Co-authored-by: Candice Ye <canye@microsoft.com>
223 lines
8.7 KiB
TypeScript
223 lines
8.7 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import { ControllerInfo, ResourceInfo } from 'arc';
|
|
import * as azdata from 'azdata';
|
|
import { v4 as uuid } from 'uuid';
|
|
import * as vscode from 'vscode';
|
|
import { Deferred } from '../../common/promise';
|
|
import * as loc from '../../localizedConstants';
|
|
import { ControllerModel } from '../../models/controllerModel';
|
|
import { InitializingComponent } from '../components/initializingComponent';
|
|
import { AzureArcTreeDataProvider } from '../tree/azureArcTreeDataProvider';
|
|
import { RadioOptionsGroup } from '../components/radioOptionsGroup';
|
|
import { getCurrentClusterContext, getDefaultKubeConfigPath, getKubeConfigClusterContexts, KubeClusterContext } from '../../common/kubeUtils';
|
|
import { FilePicker } from '../components/filePicker';
|
|
import { ConnectionMode } from '../../constants';
|
|
|
|
export type ConnectToControllerDialogModel = { controllerModel: ControllerModel };
|
|
|
|
abstract class ControllerDialogBase extends InitializingComponent {
|
|
protected _toDispose: vscode.Disposable[] = [];
|
|
protected modelBuilder!: azdata.ModelBuilder;
|
|
protected dialog: azdata.window.Dialog;
|
|
|
|
protected namespaceInputBox!: azdata.InputBoxComponent;
|
|
protected kubeConfigInputBox!: FilePicker;
|
|
protected clusterContextRadioGroup!: RadioOptionsGroup;
|
|
protected nameInputBox!: azdata.InputBoxComponent;
|
|
|
|
private _kubeClusters: KubeClusterContext[] = [];
|
|
|
|
protected dispose(): void {
|
|
this._toDispose.forEach(disposable => disposable.dispose());
|
|
this._toDispose.length = 0;
|
|
}
|
|
|
|
protected getComponents(): (azdata.FormComponent<azdata.Component> & { layout?: azdata.FormItemLayout | undefined; })[] {
|
|
return [
|
|
{
|
|
component: this.namespaceInputBox,
|
|
title: loc.namespace,
|
|
required: true
|
|
}, {
|
|
component: this.kubeConfigInputBox.component(),
|
|
title: loc.controllerKubeConfig,
|
|
required: true
|
|
}, {
|
|
component: this.clusterContextRadioGroup.component(),
|
|
title: loc.controllerClusterContext,
|
|
required: true
|
|
}, {
|
|
component: this.nameInputBox,
|
|
title: loc.controllerName,
|
|
required: false,
|
|
layout: {
|
|
info: loc.controllerNameDescription
|
|
}
|
|
}
|
|
];
|
|
}
|
|
|
|
protected abstract fieldToFocusOn(): azdata.Component;
|
|
protected readonlyFields(): azdata.Component[] { return []; }
|
|
|
|
protected initializeFields(controllerInfo: ControllerInfo | undefined) {
|
|
this.namespaceInputBox = this.modelBuilder.inputBox()
|
|
.withProps({
|
|
value: controllerInfo?.namespace,
|
|
}).component();
|
|
this.kubeConfigInputBox = new FilePicker(
|
|
this.modelBuilder,
|
|
controllerInfo?.kubeConfigFilePath || getDefaultKubeConfigPath(),
|
|
(disposable) => this._toDispose.push(disposable),
|
|
loc.controllerKubeConfig,
|
|
loc.invalidConfigPath,
|
|
true
|
|
);
|
|
this.modelBuilder.inputBox()
|
|
.withProps({
|
|
value: controllerInfo?.kubeConfigFilePath || getDefaultKubeConfigPath()
|
|
}).component();
|
|
this.clusterContextRadioGroup = new RadioOptionsGroup(this.modelBuilder, (disposable) => this._toDispose.push(disposable), undefined, loc.loadingClusterContextCompleted, loc.loadingClusterContextsError);
|
|
this.loadRadioGroup(controllerInfo?.kubeClusterContext);
|
|
this._toDispose.push(this.clusterContextRadioGroup.onRadioOptionChanged(newContext => this.updateNamespace(newContext)));
|
|
this._toDispose.push(this.kubeConfigInputBox.onTextChanged(() => this.loadRadioGroup(controllerInfo?.kubeClusterContext)));
|
|
this.nameInputBox = this.modelBuilder.inputBox()
|
|
.withProps({
|
|
value: controllerInfo?.name
|
|
}).component();
|
|
}
|
|
|
|
protected completionPromise = new Deferred<ConnectToControllerDialogModel | undefined>();
|
|
protected id!: string;
|
|
protected resources: ResourceInfo[] = [];
|
|
protected resourceGroup!: string;
|
|
protected connectionMode!: string;
|
|
protected location!: string;
|
|
protected customLocation!: string;
|
|
|
|
constructor(protected treeDataProvider: AzureArcTreeDataProvider, title: string) {
|
|
super();
|
|
this.dialog = azdata.window.createModelViewDialog(title);
|
|
}
|
|
|
|
private loadRadioGroup(previousClusterContext?: string): void {
|
|
this.clusterContextRadioGroup.load(() => {
|
|
this._kubeClusters = getKubeConfigClusterContexts(this.kubeConfigInputBox.value!);
|
|
const currentClusterContext = getCurrentClusterContext(this._kubeClusters, previousClusterContext, false);
|
|
this.namespaceInputBox.value = currentClusterContext.namespace || this.namespaceInputBox.value;
|
|
return {
|
|
values: this._kubeClusters.map(c => c.name),
|
|
defaultValue: currentClusterContext.name
|
|
};
|
|
});
|
|
}
|
|
|
|
private updateNamespace(currentContextName: string | undefined): void {
|
|
const currentContext = this._kubeClusters.find(cluster => cluster.name === currentContextName);
|
|
this.namespaceInputBox.value = currentContext?.namespace;
|
|
}
|
|
|
|
public showDialog(controllerInfo?: ControllerInfo): azdata.window.Dialog {
|
|
this.id = controllerInfo?.id ?? uuid();
|
|
this.resources = controllerInfo?.resources ?? [];
|
|
this._toDispose.push(this.dialog.cancelButton.onClick(() => this.handleCancel()));
|
|
this.dialog.registerContent(async (view) => {
|
|
this.modelBuilder = view.modelBuilder;
|
|
this.initializeFields(controllerInfo);
|
|
|
|
let formModel = this.modelBuilder.formContainer()
|
|
.withFormItems([{
|
|
components: this.getComponents(),
|
|
title: ''
|
|
}]).withLayout({ width: '100%' }).component();
|
|
await view.initializeModel(formModel);
|
|
await this.fieldToFocusOn().focus();
|
|
this.readonlyFields().forEach(f => f.enabled = false);
|
|
this.initialized = true;
|
|
});
|
|
|
|
this.dialog.registerCloseValidator(async () => {
|
|
const isValidated = await this.validate();
|
|
if (isValidated) {
|
|
this.dispose();
|
|
}
|
|
return isValidated;
|
|
});
|
|
this.dialog.okButton.label = loc.connect;
|
|
this.dialog.cancelButton.label = loc.cancel;
|
|
azdata.window.openDialog(this.dialog);
|
|
return this.dialog;
|
|
}
|
|
|
|
public abstract validate(): Promise<boolean>;
|
|
|
|
private handleCancel(): void {
|
|
this.completionPromise.resolve(undefined);
|
|
}
|
|
|
|
public waitForClose(): Promise<ConnectToControllerDialogModel | undefined> {
|
|
return this.completionPromise.promise;
|
|
}
|
|
|
|
protected getControllerInfo(): ControllerInfo {
|
|
return {
|
|
id: this.id,
|
|
namespace: this.namespaceInputBox.value!.trim(),
|
|
kubeConfigFilePath: this.kubeConfigInputBox.value!,
|
|
kubeClusterContext: this.clusterContextRadioGroup.value!,
|
|
name: this.nameInputBox.value ?? '',
|
|
resources: this.resources,
|
|
resourceGroup: this.resourceGroup ?? '',
|
|
connectionMode: this.connectionMode ?? '',
|
|
location: this.location ?? '',
|
|
customLocation: this.customLocation ?? ''
|
|
};
|
|
}
|
|
}
|
|
|
|
export class ConnectToControllerDialog extends ControllerDialogBase {
|
|
protected fieldToFocusOn() {
|
|
return this.namespaceInputBox;
|
|
}
|
|
|
|
constructor(treeDataProvider: AzureArcTreeDataProvider) {
|
|
super(treeDataProvider, loc.connectToController);
|
|
}
|
|
|
|
public async validate(): Promise<boolean> {
|
|
if (!this.namespaceInputBox.value) {
|
|
return false;
|
|
}
|
|
|
|
const controllerInfo: ControllerInfo = this.getControllerInfo();
|
|
const controllerModel = new ControllerModel(this.treeDataProvider, controllerInfo);
|
|
try {
|
|
// Validate that we can connect to the controller, this also populates the controllerRegistration from the connection response.
|
|
await controllerModel.refresh(false, this.namespaceInputBox.value);
|
|
// default info.name to the name of the controller instance if the user did not specify their own and to a pre-canned default if for some weird reason controller endpoint returned instanceName is also not a valid value
|
|
controllerModel.info.name = controllerModel.info.name || controllerModel.controllerConfig?.metadata.name || loc.defaultControllerName;
|
|
controllerModel.info.resourceGroup = <string>controllerModel.controllerConfig?.spec.settings.azure.resourceGroup;
|
|
controllerModel.info.connectionMode = <string>controllerModel.controllerConfig?.spec.settings.azure.connectionMode.toLowerCase();
|
|
controllerModel.info.location = <string>controllerModel.controllerConfig?.spec.settings.azure.location;
|
|
|
|
if (controllerModel.info.connectionMode === ConnectionMode.direct) {
|
|
const rawCustomLocation = <string>controllerModel.controllerConfig?.metadata.annotations['management.azure.com/customLocation'];
|
|
const exp = /custom[lL]ocations\/([\S]*)/;
|
|
controllerModel.info.customLocation = <string>exp.exec(rawCustomLocation)?.pop();
|
|
}
|
|
} catch (err) {
|
|
this.dialog.message = {
|
|
text: loc.connectToControllerFailed(this.namespaceInputBox.value, err),
|
|
level: azdata.window.MessageLevel.Error
|
|
};
|
|
return false;
|
|
}
|
|
this.completionPromise.resolve({ controllerModel: controllerModel });
|
|
return true;
|
|
}
|
|
}
|