mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-22 21:00:30 -04:00
* backup page * config rpo first * rpo az cli * working 1 * working 2 * working -3 * working -3 * working 4 * working with button component * remove Date usage, use string instead * cleanup * cleanup 2 * Update localizedConstants.ts rectify the wording until, figure out a way to fetch earliest backup * pitr dialog, remove rpo * pr feedback * pr feedback * pr feedback * pr feedback * feedback * remove iso time conversion and show time as-is
434 lines
16 KiB
TypeScript
434 lines
16 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 * as azdata from 'azdata';
|
|
import * as azExt from 'az-ext';
|
|
import * as azurecore from 'azurecore';
|
|
import * as vscode from 'vscode';
|
|
import { getDatabaseStateDisplayText, promptForInstanceDeletion } from '../../../common/utils';
|
|
import { cssStyles, IconPathHelper, miaaTroubleshootDocsUrl } from '../../../constants';
|
|
import * as loc from '../../../localizedConstants';
|
|
import { ControllerModel } from '../../../models/controllerModel';
|
|
import { MiaaModel } from '../../../models/miaaModel';
|
|
import { DashboardPage } from '../../components/dashboardPage';
|
|
import { ResourceType } from 'arc';
|
|
|
|
export class MiaaDashboardOverviewPage extends DashboardPage {
|
|
|
|
private _propertiesLoading!: azdata.LoadingComponent;
|
|
private _kibanaLoading!: azdata.LoadingComponent;
|
|
private _grafanaLoading!: azdata.LoadingComponent;
|
|
|
|
private _propertiesContainer!: azdata.PropertiesContainerComponent;
|
|
private _kibanaLink!: azdata.HyperlinkComponent;
|
|
private _grafanaLink!: azdata.HyperlinkComponent;
|
|
private _databasesTable!: azdata.DeclarativeTableComponent;
|
|
private _databasesMessage!: azdata.TextComponent;
|
|
private _openInAzurePortalButton!: azdata.ButtonComponent;
|
|
|
|
private _databasesContainer!: azdata.DivContainer;
|
|
private _connectToServerLoading!: azdata.LoadingComponent;
|
|
private _connectToServerButton!: azdata.ButtonComponent;
|
|
private _databasesTableLoading!: azdata.LoadingComponent;
|
|
|
|
private readonly _azApi: azExt.IExtension;
|
|
private readonly _azurecoreApi: azurecore.IExtension;
|
|
|
|
private _instanceProperties = {
|
|
resourceGroup: '-',
|
|
status: '-',
|
|
dataController: '-',
|
|
region: '-',
|
|
subscriptionId: '-',
|
|
miaaAdmin: '-',
|
|
externalEndpoint: '-',
|
|
vCores: ''
|
|
};
|
|
|
|
constructor(modelView: azdata.ModelView, dashboard: azdata.window.ModelViewDashboard, private _controllerModel: ControllerModel, private _miaaModel: MiaaModel) {
|
|
super(modelView, dashboard);
|
|
this._azApi = vscode.extensions.getExtension(azExt.extension.name)?.exports;
|
|
this._azurecoreApi = vscode.extensions.getExtension(azurecore.extension.name)?.exports;
|
|
|
|
this._instanceProperties.miaaAdmin = this._miaaModel.username || this._instanceProperties.miaaAdmin;
|
|
this.disposables.push(
|
|
this._controllerModel.onRegistrationsUpdated(() => this.handleRegistrationsUpdated()),
|
|
this._controllerModel.onEndpointsUpdated(() => this.eventuallyRunOnInitialized(() => this.refreshDashboardLinks())),
|
|
this._miaaModel.onConfigUpdated(() => this.eventuallyRunOnInitialized(() => this.handleMiaaConfigUpdated())),
|
|
this._miaaModel.onDatabasesUpdated(() => this.eventuallyRunOnInitialized(() => this.handleDatabasesUpdated()))
|
|
);
|
|
}
|
|
|
|
public get title(): string {
|
|
return loc.overview;
|
|
}
|
|
|
|
public get id(): string {
|
|
return 'miaa-overview';
|
|
}
|
|
|
|
public get icon(): { dark: string, light: string } {
|
|
return IconPathHelper.properties;
|
|
}
|
|
|
|
protected async refresh(): Promise<void> {
|
|
await Promise.all([this._controllerModel.refresh(false, this._controllerModel.info.namespace), this._miaaModel.refresh()]);
|
|
}
|
|
|
|
public get container(): azdata.Component {
|
|
// Create loaded components
|
|
this._propertiesContainer = this.modelView.modelBuilder.propertiesContainer().component();
|
|
this._propertiesLoading = this.modelView.modelBuilder.loadingComponent().component();
|
|
|
|
this._kibanaLink = this.modelView.modelBuilder.hyperlink().component();
|
|
this._kibanaLoading = this.modelView.modelBuilder.loadingComponent().component();
|
|
|
|
this._grafanaLink = this.modelView.modelBuilder.hyperlink().component();
|
|
this._grafanaLoading = this.modelView.modelBuilder.loadingComponent().component();
|
|
|
|
this._databasesContainer = this.modelView.modelBuilder.divContainer().component();
|
|
|
|
const connectToServerText = this.modelView.modelBuilder.text().withProps({
|
|
value: loc.miaaConnectionRequired
|
|
}).component();
|
|
|
|
this._connectToServerButton = this.modelView.modelBuilder.button().withProps({
|
|
label: loc.connectToServer,
|
|
enabled: false,
|
|
CSSStyles: { 'max-width': '125px', 'margin-left': '40%' }
|
|
}).component();
|
|
|
|
const connectToServerContainer = this.modelView.modelBuilder.divContainer().component();
|
|
|
|
|
|
connectToServerContainer.addItem(connectToServerText, { CSSStyles: { 'text-align': 'center', 'margin-top': '20px' } });
|
|
connectToServerContainer.addItem(this._connectToServerButton);
|
|
|
|
this._connectToServerLoading = this.modelView.modelBuilder.loadingComponent().withItem(connectToServerContainer).component();
|
|
|
|
this._databasesContainer.addItem(this._connectToServerLoading, { CSSStyles: { 'margin-top': '20px' } });
|
|
|
|
this._databasesTableLoading = this.modelView.modelBuilder.loadingComponent().component();
|
|
this._databasesTable = this.modelView.modelBuilder.declarativeTable().withProps({
|
|
width: '100%',
|
|
columns: [
|
|
{
|
|
displayName: loc.name,
|
|
valueType: azdata.DeclarativeDataType.string,
|
|
isReadOnly: true,
|
|
width: '80%',
|
|
headerCssStyles: cssStyles.tableHeader,
|
|
rowCssStyles: cssStyles.tableRow
|
|
},
|
|
{
|
|
displayName: loc.status,
|
|
valueType: azdata.DeclarativeDataType.string,
|
|
isReadOnly: true,
|
|
width: '20%',
|
|
headerCssStyles: cssStyles.tableHeader,
|
|
rowCssStyles: cssStyles.tableRow
|
|
}
|
|
],
|
|
dataValues: []
|
|
}).component();
|
|
|
|
this._databasesMessage = this.modelView.modelBuilder.text()
|
|
.withProps({ CSSStyles: { 'text-align': 'center' } })
|
|
.component();
|
|
|
|
// Update loaded components with data
|
|
this.handleRegistrationsUpdated();
|
|
this.handleMiaaConfigUpdated();
|
|
this.refreshDashboardLinks();
|
|
this.handleDatabasesUpdated();
|
|
|
|
// Assign the loading component after it has data
|
|
this._propertiesLoading.component = this._propertiesContainer;
|
|
this._kibanaLoading.component = this._kibanaLink;
|
|
this._grafanaLoading.component = this._grafanaLink;
|
|
this._databasesTableLoading.component = this._databasesTable;
|
|
|
|
// Assemble the container
|
|
const rootContainer = this.modelView.modelBuilder.flexContainer()
|
|
.withLayout({ flexFlow: 'column' })
|
|
.withProps({ CSSStyles: { 'margin': '18px' } })
|
|
.component();
|
|
|
|
// Properties
|
|
rootContainer.addItem(this._propertiesLoading, { CSSStyles: cssStyles.text });
|
|
|
|
// Service endpoints
|
|
const titleCSS = { ...cssStyles.title, 'margin-block-start': '2em', 'margin-block-end': '0' };
|
|
rootContainer.addItem(this.modelView.modelBuilder.text().withProps({ value: loc.serviceEndpoints, CSSStyles: titleCSS }).component());
|
|
|
|
const endpointsTable = this.modelView.modelBuilder.declarativeTable().withProps({
|
|
width: '100%',
|
|
columns: [
|
|
{
|
|
displayName: loc.name,
|
|
valueType: azdata.DeclarativeDataType.string,
|
|
isReadOnly: true,
|
|
width: '20%',
|
|
headerCssStyles: cssStyles.tableHeader,
|
|
rowCssStyles: cssStyles.tableRow
|
|
},
|
|
{
|
|
displayName: loc.endpoint,
|
|
valueType: azdata.DeclarativeDataType.component,
|
|
isReadOnly: true,
|
|
width: '50%',
|
|
headerCssStyles: cssStyles.tableHeader,
|
|
rowCssStyles: {
|
|
...cssStyles.tableRow,
|
|
'overflow': 'hidden',
|
|
'text-overflow': 'ellipsis',
|
|
'white-space': 'nowrap',
|
|
'max-width': '0'
|
|
}
|
|
},
|
|
{
|
|
displayName: loc.description,
|
|
valueType: azdata.DeclarativeDataType.string,
|
|
isReadOnly: true,
|
|
width: '30%',
|
|
headerCssStyles: cssStyles.tableHeader,
|
|
rowCssStyles: cssStyles.tableRow
|
|
}
|
|
],
|
|
dataValues: [
|
|
[{ value: loc.kibanaDashboard }, { value: this._kibanaLoading }, { value: loc.kibanaDashboardDescription }],
|
|
[{ value: loc.grafanaDashboard }, { value: this._grafanaLoading }, { value: loc.grafanaDashboardDescription }]]
|
|
}).component();
|
|
|
|
rootContainer.addItem(endpointsTable);
|
|
|
|
// Databases
|
|
rootContainer.addItem(this.modelView.modelBuilder.text().withProps({ value: loc.databases, CSSStyles: titleCSS }).component());
|
|
this.disposables.push(
|
|
this._connectToServerButton!.onDidClick(async () => {
|
|
this._connectToServerButton!.enabled = false;
|
|
this._databasesTableLoading!.loading = true;
|
|
try {
|
|
await this._miaaModel.callGetDatabases();
|
|
} catch {
|
|
this._connectToServerButton!.enabled = true;
|
|
}
|
|
})
|
|
);
|
|
rootContainer.addItem(this._databasesContainer);
|
|
rootContainer.addItem(this._databasesMessage);
|
|
|
|
this.initialized = true;
|
|
return rootContainer;
|
|
}
|
|
|
|
public get toolbarContainer(): azdata.ToolbarContainer {
|
|
|
|
const deleteButton = this.modelView.modelBuilder.button().withProps({
|
|
label: loc.deleteText,
|
|
iconPath: IconPathHelper.delete
|
|
}).component();
|
|
|
|
this.disposables.push(
|
|
deleteButton.onDidClick(async () => {
|
|
deleteButton.enabled = false;
|
|
try {
|
|
if (await promptForInstanceDeletion(this._miaaModel.info.name)) {
|
|
await vscode.window.withProgress(
|
|
{
|
|
location: vscode.ProgressLocation.Notification,
|
|
title: loc.deletingInstance(this._miaaModel.info.name),
|
|
cancellable: false
|
|
},
|
|
async (_progress, _token) => {
|
|
return await this._azApi.az.sql.miarc.delete(this._miaaModel.info.name, this._controllerModel.info.namespace, this._controllerModel.azAdditionalEnvVars);
|
|
}
|
|
);
|
|
await this._controllerModel.refreshTreeNode();
|
|
vscode.window.showInformationMessage(loc.instanceDeleted(this._miaaModel.info.name));
|
|
try {
|
|
await this.dashboard.close();
|
|
} catch (err) {
|
|
// Failures closing the dashboard aren't something we need to show users
|
|
console.log('Error closing MIAA dashboard ', err);
|
|
}
|
|
|
|
}
|
|
} catch (error) {
|
|
vscode.window.showErrorMessage(loc.instanceDeletionFailed(this._miaaModel.info.name, error));
|
|
} finally {
|
|
deleteButton.enabled = true;
|
|
}
|
|
}));
|
|
|
|
// Refresh
|
|
const refreshButton = this.modelView.modelBuilder.button().withProps({
|
|
label: loc.refresh,
|
|
iconPath: IconPathHelper.refresh
|
|
}).component();
|
|
|
|
this.disposables.push(
|
|
refreshButton.onDidClick(async () => {
|
|
refreshButton.enabled = false;
|
|
try {
|
|
this._propertiesLoading!.loading = true;
|
|
this._kibanaLoading!.loading = true;
|
|
this._grafanaLoading!.loading = true;
|
|
this._databasesTableLoading!.loading = true;
|
|
|
|
await this.refresh();
|
|
} finally {
|
|
refreshButton.enabled = true;
|
|
}
|
|
}));
|
|
|
|
this._openInAzurePortalButton = this.modelView.modelBuilder.button().withProps({
|
|
label: loc.openInAzurePortal,
|
|
iconPath: IconPathHelper.openInTab,
|
|
enabled: !!this._controllerModel.controllerConfig
|
|
}).component();
|
|
|
|
this.disposables.push(
|
|
this._openInAzurePortalButton.onDidClick(async () => {
|
|
const config = this._controllerModel.controllerConfig;
|
|
if (config) {
|
|
vscode.env.openExternal(vscode.Uri.parse(
|
|
`https://portal.azure.com/#resource/subscriptions/${config.spec.settings.azure.subscription}/resourceGroups/${config.spec.settings.azure.resourceGroup}/providers/Microsoft.AzureArcData/${ResourceType.sqlManagedInstances}/${this._miaaModel.info.name}`));
|
|
} else {
|
|
vscode.window.showErrorMessage(loc.couldNotFindControllerRegistration);
|
|
}
|
|
}));
|
|
|
|
const troubleshootButton = this.modelView.modelBuilder.button().withProps({
|
|
label: loc.troubleshoot,
|
|
iconPath: IconPathHelper.wrench
|
|
}).component();
|
|
|
|
this.disposables.push(
|
|
troubleshootButton.onDidClick(async () => {
|
|
await vscode.env.openExternal(vscode.Uri.parse(miaaTroubleshootDocsUrl));
|
|
})
|
|
);
|
|
|
|
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems(
|
|
[
|
|
{ component: deleteButton },
|
|
{ component: refreshButton, toolbarSeparatorAfter: true },
|
|
{ component: this._openInAzurePortalButton }
|
|
]
|
|
).component();
|
|
}
|
|
|
|
private handleRegistrationsUpdated(): void {
|
|
const config = this._controllerModel.controllerConfig;
|
|
if (this._openInAzurePortalButton) {
|
|
this._openInAzurePortalButton.enabled = !!config;
|
|
}
|
|
this._instanceProperties.resourceGroup = config?.spec.settings.azure.resourceGroup || this._instanceProperties.resourceGroup;
|
|
this._instanceProperties.dataController = config?.metadata.name || this._instanceProperties.dataController;
|
|
this._instanceProperties.region = this._azurecoreApi.getRegionDisplayName(config?.spec.settings.azure.location) || this._instanceProperties.region;
|
|
this._instanceProperties.subscriptionId = config?.spec.settings.azure.subscription || this._instanceProperties.subscriptionId;
|
|
this.refreshDisplayedProperties();
|
|
}
|
|
|
|
private handleMiaaConfigUpdated(): void {
|
|
if (this._miaaModel.config) {
|
|
this._instanceProperties.status = this._miaaModel.config.status.state || '-';
|
|
this._instanceProperties.externalEndpoint = this._miaaModel.config.status.primaryEndpoint || loc.notConfigured;
|
|
this._instanceProperties.vCores = this._miaaModel.config.spec.scheduling?.default?.resources?.limits?.cpu?.toString() || '';
|
|
this._databasesMessage.value = !this._miaaModel.config.status.primaryEndpoint ? loc.noExternalEndpoint : '';
|
|
if (!this._miaaModel.config.status.primaryEndpoint) {
|
|
this._databasesContainer.removeItem(this._connectToServerLoading);
|
|
}
|
|
}
|
|
|
|
this.refreshDisplayedProperties();
|
|
this.refreshDashboardLinks();
|
|
}
|
|
|
|
private handleDatabasesUpdated(): void {
|
|
// If we were able to get the databases it means we have a good connection so update the username too
|
|
this._instanceProperties.miaaAdmin = this._miaaModel.username || this._instanceProperties.miaaAdmin;
|
|
this.refreshDisplayedProperties();
|
|
let databaseDisplayText = this._miaaModel.databases.map(d => [d.name, getDatabaseStateDisplayText(d.status)]);
|
|
let databasesTextValues = databaseDisplayText.map(d => {
|
|
return d.map((value): azdata.DeclarativeTableCellValue => {
|
|
return { value: value };
|
|
});
|
|
});
|
|
this._databasesTable.setDataValues(databasesTextValues);
|
|
this._databasesTableLoading.loading = false;
|
|
|
|
if (this._miaaModel.databasesLastUpdated) {
|
|
// We successfully connected so now can remove the button and replace it with the actual databases table
|
|
this._databasesContainer.removeItem(this._connectToServerLoading);
|
|
this._databasesContainer.addItem(this._databasesTableLoading, { CSSStyles: { 'margin-bottom': '20px' } });
|
|
} else {
|
|
// If we don't have an endpoint then there's no point in showing the connect button - but the logic
|
|
// to display text informing the user of this is already handled by the handleMiaaConfigUpdated
|
|
if (this._miaaModel?.config?.status.primaryEndpoint) {
|
|
this._connectToServerLoading.loading = false;
|
|
this._connectToServerButton.enabled = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
private refreshDisplayedProperties(): void {
|
|
this._propertiesContainer.propertyItems = [
|
|
{
|
|
displayName: loc.resourceGroup,
|
|
value: this._instanceProperties.resourceGroup
|
|
},
|
|
{
|
|
displayName: loc.status,
|
|
value: this._instanceProperties.status
|
|
},
|
|
{
|
|
displayName: loc.dataController,
|
|
value: this._instanceProperties.dataController
|
|
},
|
|
{
|
|
displayName: loc.region,
|
|
value: this._instanceProperties.region
|
|
},
|
|
{
|
|
displayName: loc.subscriptionId,
|
|
value: this._instanceProperties.subscriptionId
|
|
},
|
|
{
|
|
displayName: loc.miaaAdmin,
|
|
value: this._instanceProperties.miaaAdmin
|
|
},
|
|
{
|
|
displayName: loc.externalEndpoint,
|
|
value: this._instanceProperties.externalEndpoint
|
|
},
|
|
{
|
|
displayName: loc.compute,
|
|
value: loc.numVCores(this._instanceProperties.vCores)
|
|
}
|
|
];
|
|
|
|
this._propertiesLoading.loading =
|
|
!this._controllerModel.registrationsLastUpdated &&
|
|
!this._miaaModel.configLastUpdated &&
|
|
!this._miaaModel.databasesLastUpdated;
|
|
}
|
|
|
|
private refreshDashboardLinks(): void {
|
|
if (this._miaaModel.config) {
|
|
const kibanaUrl = this._miaaModel.config.status.logSearchDashboard ?? '';
|
|
this._kibanaLink.label = kibanaUrl;
|
|
this._kibanaLink.url = kibanaUrl;
|
|
this._kibanaLoading!.loading = false;
|
|
|
|
const grafanaUrl = this._miaaModel.config.status.metricsDashboard ?? '';
|
|
this._grafanaLink.label = grafanaUrl;
|
|
this._grafanaLink.url = grafanaUrl;
|
|
this._grafanaLoading!.loading = false;
|
|
}
|
|
}
|
|
}
|