mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Add Backup tab under SQL Miaa dashboard, 'Configure Retention Policy' settings dialog, listing databases with latest PITR timetamp, Pitr dialog to restore (#17269)
* 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
This commit is contained in:
committed by
GitHub
parent
74aacda70d
commit
f126c998d2
323
extensions/arc/src/ui/dashboards/miaa/miaaBackupsPage.ts
Normal file
323
extensions/arc/src/ui/dashboards/miaa/miaaBackupsPage.ts
Normal file
@@ -0,0 +1,323 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import * as azExt from 'az-ext';
|
||||
import * as loc from '../../../localizedConstants';
|
||||
import { IconPathHelper, cssStyles } from '../../../constants';
|
||||
import { DashboardPage } from '../../components/dashboardPage';
|
||||
import { MiaaModel, RPModel, DatabaseModel, systemDbs } from '../../../models/miaaModel';
|
||||
import { ControllerModel } from '../../../models/controllerModel';
|
||||
import { ConfigureRPOSqlDialog } from '../../dialogs/configureRPOSqlDialog';
|
||||
import { RestoreSqlDialog } from '../../dialogs/restoreSqlDialog';
|
||||
|
||||
export class MiaaBackupsPage extends DashboardPage {
|
||||
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.disposables.push(
|
||||
this._miaaModel.onDatabasesUpdated(() => this.eventuallyRunOnInitialized(() => this.handleDatabasesUpdated())),
|
||||
this._miaaModel.onConfigUpdated(() => this.eventuallyRunOnInitialized(() => this.handleDatabasesUpdated()))
|
||||
);
|
||||
}
|
||||
private _databasesContainer!: azdata.DivContainer;
|
||||
private _configureRetentionPolicyButton!: azdata.ButtonComponent;
|
||||
private _connectToServerLoading!: azdata.LoadingComponent;
|
||||
private _connectToServerButton!: azdata.ButtonComponent;
|
||||
private _databasesTableLoading!: azdata.LoadingComponent;
|
||||
private _databasesTable!: azdata.DeclarativeTableComponent;
|
||||
private _databasesMessage!: azdata.TextComponent;
|
||||
private readonly _azApi: azExt.IExtension;
|
||||
|
||||
public saveArgs: RPModel = {
|
||||
recoveryPointObjective: '',
|
||||
retentionDays: ''
|
||||
};
|
||||
|
||||
public pitrArgs = {
|
||||
destName: '',
|
||||
managedInstance: '',
|
||||
time: '',
|
||||
noWait: true
|
||||
};
|
||||
|
||||
public get title(): string {
|
||||
return loc.backups;
|
||||
}
|
||||
|
||||
public get id(): string {
|
||||
return 'backups';
|
||||
}
|
||||
|
||||
public get icon(): { dark: string, light: string } {
|
||||
return IconPathHelper.pitr;
|
||||
}
|
||||
protected async refresh(): Promise<void> {
|
||||
await Promise.all([this._controllerModel.refresh(false, this._controllerModel.info.namespace), this._miaaModel.refresh()]);
|
||||
}
|
||||
|
||||
public get container(): azdata.Component {
|
||||
const root = this.modelView.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'column' })
|
||||
.withProps({ CSSStyles: { 'margin': '18px' } })
|
||||
.component();
|
||||
const content = this.modelView.modelBuilder.divContainer().component();
|
||||
this._databasesContainer = this.modelView.modelBuilder.divContainer().component();
|
||||
root.addItem(content, { CSSStyles: { 'margin': '20px' } });
|
||||
const databaseTitle = this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.databases,
|
||||
CSSStyles: { ...cssStyles.title },
|
||||
}).component();
|
||||
|
||||
content.addItem(databaseTitle);
|
||||
const infoBackupDatabases = this.modelView.modelBuilder.text().withProps({
|
||||
value: loc.miaaBackupsDatabasesDescription,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px', 'max-width': 'auto' }
|
||||
}).component();
|
||||
const backupInfoDescription = this.modelView.modelBuilder.flexContainer()
|
||||
.withLayout({ flexWrap: 'wrap' })
|
||||
.withItems([
|
||||
infoBackupDatabases
|
||||
], { CSSStyles: { 'margin-right': '5px' } }).component();
|
||||
|
||||
const backupsDbsLearnMoreLink = this.modelView.modelBuilder.hyperlink().withProps({
|
||||
label: loc.learnMore,
|
||||
url: 'https://docs.microsoft.com/azure/azure-arc/data/point-in-time-restore',
|
||||
CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
|
||||
const backupDatabaseInfoAndLink = this.modelView.modelBuilder.flexContainer()
|
||||
.withLayout({ flexWrap: 'wrap' })
|
||||
.withItems([
|
||||
backupInfoDescription,
|
||||
backupsDbsLearnMoreLink
|
||||
], { CSSStyles: { 'margin-right': '5px' } }).component();
|
||||
|
||||
content.addItem(backupDatabaseInfoAndLink, { CSSStyles: { 'min-height': '30px' } });
|
||||
|
||||
// Create loaded components
|
||||
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.database,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: '30%',
|
||||
headerCssStyles: cssStyles.tableHeader,
|
||||
rowCssStyles: cssStyles.tableRow
|
||||
},
|
||||
{
|
||||
displayName: loc.latestpitrRestorePoint,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
isReadOnly: true,
|
||||
width: '50%',
|
||||
headerCssStyles: cssStyles.tableHeader,
|
||||
rowCssStyles: cssStyles.tableRow
|
||||
},
|
||||
{
|
||||
displayName: loc.restore,
|
||||
valueType: azdata.DeclarativeDataType.component,
|
||||
isReadOnly: true,
|
||||
width: '20%',
|
||||
headerCssStyles: cssStyles.tableHeader,
|
||||
rowCssStyles: cssStyles.tableRow,
|
||||
}
|
||||
],
|
||||
dataValues: []
|
||||
}).component();
|
||||
|
||||
this._databasesMessage = this.modelView.modelBuilder.text()
|
||||
.withProps({ CSSStyles: { 'text-align': 'center' } })
|
||||
.component();
|
||||
|
||||
this.handleDatabasesUpdated();
|
||||
this._databasesTableLoading.component = this._databasesTable;
|
||||
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;
|
||||
}
|
||||
})
|
||||
);
|
||||
root.addItem(this._databasesContainer);
|
||||
root.addItem(this._databasesMessage);
|
||||
|
||||
this.initialized = true;
|
||||
return root;
|
||||
}
|
||||
|
||||
public get toolbarContainer(): azdata.ToolbarContainer {
|
||||
// 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 {
|
||||
await this.refresh();
|
||||
} finally {
|
||||
refreshButton.enabled = true;
|
||||
}
|
||||
}));
|
||||
this._configureRetentionPolicyButton = this.modelView.modelBuilder.button().withProps({
|
||||
label: loc.configureRetentionPolicyButton,
|
||||
enabled: true,
|
||||
iconPath: IconPathHelper.edit,
|
||||
}).component();
|
||||
this.disposables.push(
|
||||
this._configureRetentionPolicyButton.onDidClick(async () => {
|
||||
const retentionPolicySqlDialog = new ConfigureRPOSqlDialog(this._miaaModel);
|
||||
this.refreshRD();
|
||||
retentionPolicySqlDialog.showDialog(loc.configureRP, this.saveArgs.retentionDays);
|
||||
|
||||
let rpArg = await retentionPolicySqlDialog.waitForClose();
|
||||
if (rpArg) {
|
||||
try {
|
||||
this._configureRetentionPolicyButton.enabled = false;
|
||||
this.saveArgs.retentionDays = rpArg.retentionDays;
|
||||
await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: loc.updatingInstance(this._miaaModel.info.name),
|
||||
cancellable: false
|
||||
},
|
||||
async (_progress, _token): Promise<void> => {
|
||||
await this._azApi.az.sql.miarc.edit(
|
||||
this._miaaModel.info.name, this.saveArgs, this._miaaModel.controllerModel.info.namespace, this._miaaModel.controllerModel.azAdditionalEnvVars);
|
||||
|
||||
try {
|
||||
await this._miaaModel.refresh();
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(loc.refreshFailed(error));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(loc.updateExtensionsFailed(error));
|
||||
} finally {
|
||||
this._configureRetentionPolicyButton.enabled = true;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
return this.modelView.modelBuilder.toolbarContainer().withToolbarItems(
|
||||
[
|
||||
{ component: refreshButton, toolbarSeparatorAfter: true },
|
||||
{ component: this._configureRetentionPolicyButton, toolbarSeparatorAfter: false },
|
||||
|
||||
]
|
||||
).component();
|
||||
}
|
||||
|
||||
private handleDatabasesUpdated(): void {
|
||||
// If we were able to get the databases it means we have a good connection so update the username too
|
||||
let databaseDisplay = this._miaaModel.databases.map(d => [
|
||||
d.name,
|
||||
d.lastBackup,
|
||||
this.createRestoreButton(d)]);
|
||||
let databasesValues = databaseDisplay.map(d => {
|
||||
return d.map((value): azdata.DeclarativeTableCellValue => {
|
||||
return { value: value };
|
||||
});
|
||||
});
|
||||
this._databasesTable.setDataValues(databasesValues);
|
||||
|
||||
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 refreshRD(): void {
|
||||
this.saveArgs.retentionDays = this._miaaModel.config?.spec?.backup?.retentionPeriodInDays.toString() ?? '';
|
||||
}
|
||||
|
||||
// Create restore button for every database entry in the database table
|
||||
private createRestoreButton(db: DatabaseModel): azdata.ButtonComponent | string {
|
||||
let pitrDate = db.lastBackup;
|
||||
if (!pitrDate) {
|
||||
return '';
|
||||
}
|
||||
const restoreButton = this.modelView.modelBuilder.button().withProps({
|
||||
enabled: systemDbs.indexOf(db.name) > -1 ? false : true,
|
||||
iconPath: IconPathHelper.openInTab,
|
||||
}).component();
|
||||
this.disposables.push(
|
||||
restoreButton.onDidClick(async () => {
|
||||
const restoreDialog = new RestoreSqlDialog(this._miaaModel, this._controllerModel, db);
|
||||
restoreDialog.showDialog(loc.restoreDatabase);
|
||||
let args = await restoreDialog.waitForClose();
|
||||
if (args) {
|
||||
try {
|
||||
restoreButton.enabled = false;
|
||||
this.pitrArgs.destName = args.dbName;
|
||||
this.pitrArgs.managedInstance = args.instanceName;
|
||||
this.pitrArgs.time = `"${args.restorePoint}"`;
|
||||
await vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: loc.updatingInstance(this._miaaModel.info.name),
|
||||
cancellable: false
|
||||
},
|
||||
async (_progress, _token): Promise<void> => {
|
||||
await this._azApi.az.sql.midbarc.restore(
|
||||
db.name, this.pitrArgs, this._miaaModel.controllerModel.info.namespace, this._miaaModel.controllerModel.azAdditionalEnvVars);
|
||||
try {
|
||||
await this._miaaModel.refresh();
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(loc.refreshFailed(error));
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
vscode.window.showErrorMessage(loc.updateExtensionsFailed(error));
|
||||
} finally {
|
||||
this._configureRetentionPolicyButton.enabled = true;
|
||||
}
|
||||
}
|
||||
}));
|
||||
return restoreButton;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import * as loc from '../../../localizedConstants';
|
||||
import { MiaaConnectionStringsPage } from './miaaConnectionStringsPage';
|
||||
import { MiaaModel } from '../../../models/miaaModel';
|
||||
import { MiaaComputeAndStoragePage } from './miaaComputeAndStoragePage';
|
||||
import { MiaaBackupsPage } from './miaaBackupsPage';
|
||||
|
||||
export class MiaaDashboard extends Dashboard {
|
||||
|
||||
@@ -29,13 +30,15 @@ export class MiaaDashboard extends Dashboard {
|
||||
const overviewPage = new MiaaDashboardOverviewPage(modelView, this.dashboard, this._controllerModel, this._miaaModel);
|
||||
const connectionStringsPage = new MiaaConnectionStringsPage(modelView, this.dashboard, this._miaaModel);
|
||||
const computeAndStoragePage = new MiaaComputeAndStoragePage(modelView, this.dashboard, this._miaaModel);
|
||||
const miaaBackupsPage = new MiaaBackupsPage(modelView, this.dashboard, this._controllerModel, this._miaaModel);
|
||||
return [
|
||||
overviewPage.tab,
|
||||
{
|
||||
title: loc.settings,
|
||||
tabs: [
|
||||
connectionStringsPage.tab,
|
||||
computeAndStoragePage.tab
|
||||
computeAndStoragePage.tab,
|
||||
miaaBackupsPage.tab
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
@@ -14,7 +14,6 @@ import { ControllerModel } from '../../../models/controllerModel';
|
||||
import { MiaaModel } from '../../../models/miaaModel';
|
||||
import { DashboardPage } from '../../components/dashboardPage';
|
||||
import { ResourceType } from 'arc';
|
||||
import { UserCancelledError } from '../../../common/api';
|
||||
|
||||
export class MiaaDashboardOverviewPage extends DashboardPage {
|
||||
|
||||
@@ -212,7 +211,7 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
||||
this._connectToServerButton!.enabled = false;
|
||||
this._databasesTableLoading!.loading = true;
|
||||
try {
|
||||
await this.callGetDatabases();
|
||||
await this._miaaModel.callGetDatabases();
|
||||
} catch {
|
||||
this._connectToServerButton!.enabled = true;
|
||||
}
|
||||
@@ -322,19 +321,6 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
||||
).component();
|
||||
}
|
||||
|
||||
private async callGetDatabases(): Promise<void> {
|
||||
try {
|
||||
await this._miaaModel.getDatabases();
|
||||
} catch (error) {
|
||||
if (error instanceof UserCancelledError) {
|
||||
vscode.window.showWarningMessage(loc.miaaConnectionRequired);
|
||||
} else {
|
||||
vscode.window.showErrorMessage(loc.fetchDatabasesFailed(this._miaaModel.info.name, error));
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private handleRegistrationsUpdated(): void {
|
||||
const config = this._controllerModel.controllerConfig;
|
||||
if (this._openInAzurePortalButton) {
|
||||
|
||||
101
extensions/arc/src/ui/dialogs/configureRPOSqlDialog.ts
Normal file
101
extensions/arc/src/ui/dialogs/configureRPOSqlDialog.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Deferred } from '../../common/promise';
|
||||
import * as loc from '../../localizedConstants';
|
||||
import { cssStyles } from '../../constants';
|
||||
import { InitializingComponent } from '../components/initializingComponent';
|
||||
import { MiaaModel, RPModel } from '../../models/miaaModel';
|
||||
|
||||
export class ConfigureRPOSqlDialog extends InitializingComponent {
|
||||
protected modelBuilder!: azdata.ModelBuilder;
|
||||
protected retentionDaysInputBox!: azdata.InputBoxComponent;
|
||||
protected _completionPromise = new Deferred<RPModel | undefined>();
|
||||
public saveArgs: RPModel = {
|
||||
recoveryPointObjective: '',
|
||||
retentionDays: ''
|
||||
};
|
||||
|
||||
constructor(protected _model: MiaaModel) {
|
||||
super();
|
||||
}
|
||||
|
||||
public showDialog(dialogTitle: string, retentionDays: string | undefined): azdata.window.Dialog {
|
||||
const dialog = azdata.window.createModelViewDialog(dialogTitle);
|
||||
dialog.cancelButton.onClick(() => this.handleCancel());
|
||||
retentionDays = (retentionDays === undefined ? this._model.config?.spec?.backup?.retentionPeriodInDays?.toString() : retentionDays);
|
||||
dialog.registerContent(async view => {
|
||||
this.modelBuilder = view.modelBuilder;
|
||||
this.retentionDaysInputBox = this.modelBuilder.inputBox()
|
||||
.withProps({
|
||||
readOnly: false,
|
||||
min: 1,
|
||||
max: 35,
|
||||
inputType: 'number',
|
||||
ariaLabel: loc.retentionDays,
|
||||
value: retentionDays
|
||||
}).component();
|
||||
|
||||
const info = this.modelBuilder.text().withProps({
|
||||
value: loc.pitrInfo,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
|
||||
const link = this.modelBuilder.hyperlink().withProps({
|
||||
label: loc.learnMore,
|
||||
url: 'https://docs.microsoft.com/azure/azure-arc/data/point-in-time-restore',
|
||||
}).component();
|
||||
|
||||
const infoAndLink = this.modelBuilder.flexContainer().withLayout({ flexWrap: 'wrap' }).component();
|
||||
infoAndLink.addItem(this.modelBuilder.text().withProps({
|
||||
value: loc.pitr,
|
||||
CSSStyles: { ...cssStyles.title }
|
||||
}).component());
|
||||
infoAndLink.addItem(info, { CSSStyles: { 'margin-right': '5px' } });
|
||||
infoAndLink.addItem(link);
|
||||
|
||||
let formModel = this.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
components: [
|
||||
{
|
||||
component: infoAndLink
|
||||
},
|
||||
{
|
||||
component: this.retentionDaysInputBox,
|
||||
title: loc.retentionDays,
|
||||
required: false
|
||||
}
|
||||
],
|
||||
title: ''
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
await view.initializeModel(formModel);
|
||||
this.retentionDaysInputBox.focus();
|
||||
this.initialized = true;
|
||||
});
|
||||
|
||||
dialog.okButton.label = loc.apply;
|
||||
dialog.cancelButton.label = loc.cancel;
|
||||
dialog.registerCloseValidator(async () => await this.validate());
|
||||
dialog.okButton.onClick(() => {
|
||||
this.saveArgs.retentionDays = this.retentionDaysInputBox.value ?? '';
|
||||
this._completionPromise.resolve(this.saveArgs);
|
||||
});
|
||||
azdata.window.openDialog(dialog);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public async validate(): Promise<boolean> {
|
||||
return !!this.retentionDaysInputBox.value;
|
||||
}
|
||||
|
||||
private handleCancel(): void {
|
||||
this._completionPromise.resolve(undefined);
|
||||
}
|
||||
|
||||
public waitForClose(): Promise<RPModel | undefined> {
|
||||
return this._completionPromise.promise;
|
||||
}
|
||||
}
|
||||
272
extensions/arc/src/ui/dialogs/restoreSqlDialog.ts
Normal file
272
extensions/arc/src/ui/dialogs/restoreSqlDialog.ts
Normal file
@@ -0,0 +1,272 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Deferred } from '../../common/promise';
|
||||
import * as loc from '../../localizedConstants';
|
||||
import * as vscode from 'vscode';
|
||||
import { cssStyles } from '../../constants';
|
||||
import { InitializingComponent } from '../components/initializingComponent';
|
||||
import { MiaaModel, PITRModel, DatabaseModel } from '../../models/miaaModel';
|
||||
import * as azurecore from 'azurecore';
|
||||
import { ControllerModel } from '../../models/controllerModel';
|
||||
|
||||
export class RestoreSqlDialog extends InitializingComponent {
|
||||
protected modelBuilder!: azdata.ModelBuilder;
|
||||
private pitrSettings: PITRModel = {
|
||||
instanceName: '-',
|
||||
resourceGroupName: '-',
|
||||
location: '-',
|
||||
subscriptionId: '-',
|
||||
dbName: '-',
|
||||
restorePoint: '-',
|
||||
earliestPitr: '-',
|
||||
latestPitr: '-',
|
||||
};
|
||||
|
||||
private earliestRestorePointInputBox!: azdata.InputBoxComponent;
|
||||
private latestRestorePointInputBox!: azdata.InputBoxComponent;
|
||||
private subscriptionInputBox!: azdata.InputBoxComponent;
|
||||
private resourceGroupInputBox!: azdata.InputBoxComponent;
|
||||
private sourceDbInputBox!: azdata.InputBoxComponent;
|
||||
private restorePointInputBox!: azdata.InputBoxComponent;
|
||||
private databaseNameInputBox!: azdata.InputBoxComponent;
|
||||
private instanceInputBox!: azdata.InputBoxComponent;
|
||||
protected _completionPromise = new Deferred<PITRModel | undefined>();
|
||||
private _azurecoreApi: azurecore.IExtension;
|
||||
constructor(protected _miaaModel: MiaaModel, protected _controllerModel: ControllerModel, protected _database: DatabaseModel) {
|
||||
super();
|
||||
this._azurecoreApi = vscode.extensions.getExtension(azurecore.extension.name)?.exports;
|
||||
|
||||
}
|
||||
|
||||
public showDialog(dialogTitle: string): azdata.window.Dialog {
|
||||
const dialog = azdata.window.createModelViewDialog(dialogTitle, dialogTitle, 'normal');
|
||||
dialog.cancelButton.onClick(() => this.handleCancel());
|
||||
dialog.registerContent(async view => {
|
||||
this.modelBuilder = view.modelBuilder;
|
||||
this.refreshPitrSettings();
|
||||
const pitrTitle = this.modelBuilder.text().withProps({
|
||||
value: loc.pitr,
|
||||
CSSStyles: { ...cssStyles.title }
|
||||
}).component();
|
||||
const projectDetailsTitle = this.modelBuilder.text().withProps({
|
||||
value: loc.projectDetails,
|
||||
CSSStyles: { ...cssStyles.title }
|
||||
}).component();
|
||||
const projectDetailsTextLabel = this.modelBuilder.text().withProps({
|
||||
value: loc.projectDetailsText,
|
||||
}).component();
|
||||
this.subscriptionInputBox = this.modelBuilder.inputBox()
|
||||
.withProps({
|
||||
enabled: false,
|
||||
ariaLabel: loc.subscription,
|
||||
value: this.pitrSettings.subscriptionId
|
||||
}).component();
|
||||
this.resourceGroupInputBox = this.modelBuilder.inputBox()
|
||||
.withProps({
|
||||
enabled: false,
|
||||
ariaLabel: loc.resourceGroup,
|
||||
value: this.pitrSettings.resourceGroupName
|
||||
}).component();
|
||||
|
||||
const sourceDetailsTitle = this.modelBuilder.text().withProps({
|
||||
value: loc.sourceDetails,
|
||||
CSSStyles: { ...cssStyles.title }
|
||||
}).component();
|
||||
const sourceDetailsTextLabel = this.modelBuilder.text().withProps({
|
||||
value: loc.sourceDetailsText,
|
||||
}).component();
|
||||
this.sourceDbInputBox = this.modelBuilder.inputBox()
|
||||
.withProps({
|
||||
enabled: false,
|
||||
ariaLabel: loc.sourceDatabase,
|
||||
value: this._database.name
|
||||
}).component();
|
||||
|
||||
this.earliestRestorePointInputBox = this.modelBuilder.inputBox()
|
||||
.withProps({
|
||||
enabled: false,
|
||||
ariaLabel: loc.earliestPitrRestorePoint,
|
||||
value: ''
|
||||
}).component();
|
||||
|
||||
this.latestRestorePointInputBox = this.modelBuilder.inputBox()
|
||||
.withProps({
|
||||
enabled: false,
|
||||
ariaLabel: loc.latestpitrRestorePoint,
|
||||
value: this._database.lastBackup
|
||||
}).component();
|
||||
|
||||
this.restorePointInputBox = this.modelBuilder.inputBox()
|
||||
.withProps({
|
||||
readOnly: false,
|
||||
ariaLabel: loc.restorePoint,
|
||||
value: ''
|
||||
}).component();
|
||||
const databaseDetailsTitle = this.modelBuilder.text().withProps({
|
||||
value: loc.databaseDetails,
|
||||
CSSStyles: { ...cssStyles.title }
|
||||
}).component();
|
||||
const databaseDetailsTextLabel = this.modelBuilder.text().withProps({
|
||||
value: loc.databaseDetailsText,
|
||||
}).component();
|
||||
this.databaseNameInputBox = this.modelBuilder.inputBox()
|
||||
.withProps({
|
||||
readOnly: false,
|
||||
ariaLabel: loc.databaseName,
|
||||
value: ''
|
||||
}).component();
|
||||
|
||||
this.instanceInputBox = this.modelBuilder.inputBox()
|
||||
.withProps({
|
||||
enabled: false,
|
||||
ariaLabel: loc.instance,
|
||||
value: this.pitrSettings.instanceName
|
||||
}).component();
|
||||
const info = this.modelBuilder.text().withProps({
|
||||
value: loc.restoreInfo,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
|
||||
const link = this.modelBuilder.hyperlink().withProps({
|
||||
label: loc.learnMore,
|
||||
url: 'https://docs.microsoft.com/azure/azure-arc/data/point-in-time-restore',
|
||||
}).component();
|
||||
|
||||
const infoAndLink = this.modelBuilder.flexContainer().withLayout({ flexWrap: 'wrap' }).component();
|
||||
|
||||
infoAndLink.addItem(info, { CSSStyles: { 'margin-right': '5px' } });
|
||||
infoAndLink.addItem(link);
|
||||
|
||||
let formModel = this.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
components: [
|
||||
{
|
||||
component: pitrTitle
|
||||
},
|
||||
{
|
||||
component: infoAndLink
|
||||
},
|
||||
{
|
||||
component: projectDetailsTitle,
|
||||
},
|
||||
{
|
||||
component: projectDetailsTextLabel,
|
||||
},
|
||||
{
|
||||
component: this.subscriptionInputBox,
|
||||
title: loc.subscription,
|
||||
|
||||
},
|
||||
{
|
||||
component: this.resourceGroupInputBox,
|
||||
title: loc.resourceGroup,
|
||||
|
||||
},
|
||||
{
|
||||
component: sourceDetailsTitle,
|
||||
},
|
||||
{
|
||||
component: sourceDetailsTextLabel,
|
||||
},
|
||||
{
|
||||
component: this.sourceDbInputBox,
|
||||
title: loc.sourceDatabase,
|
||||
|
||||
},
|
||||
{
|
||||
component: this.earliestRestorePointInputBox,
|
||||
title: loc.earliestPitrRestorePoint,
|
||||
|
||||
},
|
||||
{
|
||||
component: this.latestRestorePointInputBox,
|
||||
title: loc.latestpitrRestorePoint,
|
||||
|
||||
},
|
||||
{
|
||||
component: this.restorePointInputBox,
|
||||
title: loc.restorePoint,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
component: databaseDetailsTitle,
|
||||
},
|
||||
{
|
||||
component: databaseDetailsTextLabel,
|
||||
},
|
||||
{
|
||||
component: this.databaseNameInputBox,
|
||||
title: loc.databaseName,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
component: this.instanceInputBox,
|
||||
title: loc.instance,
|
||||
|
||||
},
|
||||
],
|
||||
title: ''
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
await view.initializeModel(formModel);
|
||||
this.subscriptionInputBox.focus();
|
||||
this.resourceGroupInputBox.focus();
|
||||
this.sourceDbInputBox.focus();
|
||||
this.earliestRestorePointInputBox.focus();
|
||||
this.latestRestorePointInputBox.focus();
|
||||
this.restorePointInputBox.focus();
|
||||
this.databaseNameInputBox.focus();
|
||||
this.instanceInputBox.focus();
|
||||
this.initialized = true;
|
||||
});
|
||||
|
||||
dialog.okButton.label = loc.restore;
|
||||
dialog.cancelButton.label = loc.cancel;
|
||||
dialog.registerCloseValidator(async () => await this.validate());
|
||||
dialog.okButton.onClick(() => {
|
||||
this.pitrSettings.subscriptionId = this.subscriptionInputBox.value ?? '';
|
||||
this.pitrSettings.instanceName = this.instanceInputBox.value ?? '';
|
||||
this.pitrSettings.resourceGroupName = this.resourceGroupInputBox.value ?? '';
|
||||
this.pitrSettings.dbName = this.databaseNameInputBox.value ?? '';
|
||||
this.pitrSettings.earliestPitr = this.earliestRestorePointInputBox.value ?? '';
|
||||
this.pitrSettings.latestPitr = this.latestRestorePointInputBox.value ?? '';
|
||||
this.pitrSettings.restorePoint = this.restorePointInputBox.value ?? '';
|
||||
this._completionPromise.resolve(this.pitrSettings);
|
||||
});
|
||||
azdata.window.openDialog(dialog);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public async validate(): Promise<boolean> {
|
||||
if (!this.subscriptionInputBox.value || !this.resourceGroupInputBox.value
|
||||
|| !this.sourceDbInputBox.value || !this.latestRestorePointInputBox.value
|
||||
|| !this.restorePointInputBox.value || !this.databaseNameInputBox.value
|
||||
|| !this.instanceInputBox.value) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private handleCancel(): void {
|
||||
this._completionPromise.resolve(undefined);
|
||||
}
|
||||
|
||||
public waitForClose(): Promise<PITRModel | undefined> {
|
||||
return this._completionPromise.promise;
|
||||
}
|
||||
|
||||
public refreshPitrSettings(): void {
|
||||
this.pitrSettings.instanceName = this._miaaModel?.config?.metadata.name || this.pitrSettings.instanceName;
|
||||
this.pitrSettings.resourceGroupName = this._controllerModel?.controllerConfig?.spec.settings.azure.resourceGroup || this.pitrSettings.resourceGroupName;
|
||||
this.pitrSettings.location = this._azurecoreApi.getRegionDisplayName(this._controllerModel?.controllerConfig?.spec.settings.azure.location) || this.pitrSettings.location;
|
||||
this.pitrSettings.subscriptionId = this._controllerModel?.controllerConfig?.spec.settings.azure.subscription || this.pitrSettings.subscriptionId;
|
||||
this.pitrSettings.dbName = this._database.name;
|
||||
this.pitrSettings.restorePoint = this._database.lastBackup;
|
||||
this.pitrSettings.earliestPitr = '';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user