mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 11:01:37 -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
23
extensions/arc/images/pitr.svg
Normal file
23
extensions/arc/images/pitr.svg
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<svg id="b4d0e6e2-12c1-4275-b717-890060dd1560" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="f1a30f9e-7f48-44e2-9483-6a6db8928977" x1="11.13" y1="10.95" x2="11.13" y2="1.21" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#0078d4" />
|
||||||
|
<stop offset="0.16" stop-color="#1380da" />
|
||||||
|
<stop offset="0.53" stop-color="#3c91e5" />
|
||||||
|
<stop offset="0.82" stop-color="#559cec" />
|
||||||
|
<stop offset="1" stop-color="#5ea0ef" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="b4a5ed8d-a28d-4d26-9aad-a57e6ad0c0b9" x1="7.06" y1="16.79" x2="7.06" y2="6.78" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#198ab3" />
|
||||||
|
<stop offset="0.13" stop-color="#21a0c7" />
|
||||||
|
<stop offset="0.31" stop-color="#28b7db" />
|
||||||
|
<stop offset="0.5" stop-color="#2ec7ea" />
|
||||||
|
<stop offset="0.72" stop-color="#31d1f2" />
|
||||||
|
<stop offset="1" stop-color="#32d4f5" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<title>Icon-manage-315</title>
|
||||||
|
<path d="M18,7.9a3.09,3.09,0,0,0-2.68-3,3.89,3.89,0,0,0-4-3.72A4,4,0,0,0,7.5,3.81,3.68,3.68,0,0,0,4.26,7.36,3.74,3.74,0,0,0,8.13,11l.34,0h6.26l.17,0A3.13,3.13,0,0,0,18,7.9Z" fill="url(#f1a30f9e-7f48-44e2-9483-6a6db8928977)" />
|
||||||
|
<path d="M14.39,5.22,12.56,3.44c-.2-.2-.37-.13-.37.18v.8a.22.22,0,0,1-.23.22c-1.18,0-4.47.31-4.59,4.81a.23.23,0,0,0,.23.23H8.77A.23.23,0,0,0,9,9.43,2.76,2.76,0,0,1,12,6.1a.23.23,0,0,1,.23.23v.74c0,.37.12.43.37.18L14.39,5.6A.23.23,0,0,0,14.39,5.22Z" fill="#fff" />
|
||||||
|
<path d="M14.12,13.66a3.17,3.17,0,0,0-2.75-3.05A4,4,0,0,0,7.25,6.78,4.1,4.1,0,0,0,3.33,9.46,3.78,3.78,0,0,0,0,13.1a3.84,3.84,0,0,0,4,3.69h6.78a.47.47,0,0,0,.17,0A3.21,3.21,0,0,0,14.12,13.66Z" fill="url(#b4a5ed8d-a28d-4d26-9aad-a57e6ad0c0b9)" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -34,6 +34,7 @@ export class IconPathHelper {
|
|||||||
public static backup: IconPath;
|
public static backup: IconPath;
|
||||||
public static properties: IconPath;
|
public static properties: IconPath;
|
||||||
public static networking: IconPath;
|
public static networking: IconPath;
|
||||||
|
public static pitr: IconPath;
|
||||||
public static refresh: IconPath;
|
public static refresh: IconPath;
|
||||||
public static reset: IconPath;
|
public static reset: IconPath;
|
||||||
public static support: IconPath;
|
public static support: IconPath;
|
||||||
@@ -155,6 +156,10 @@ export class IconPathHelper {
|
|||||||
light: context.asAbsolutePath('images/gear-colored-gray.svg'),
|
light: context.asAbsolutePath('images/gear-colored-gray.svg'),
|
||||||
dark: context.asAbsolutePath('images/gear-colored-gray.svg'),
|
dark: context.asAbsolutePath('images/gear-colored-gray.svg'),
|
||||||
};
|
};
|
||||||
|
IconPathHelper.pitr = {
|
||||||
|
light: context.asAbsolutePath('images/pitr.svg'),
|
||||||
|
dark: context.asAbsolutePath('images/pitr.svg'),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export const properties = localize('arc.properties', "Properties");
|
|||||||
export const settings = localize('arc.settings', "Settings");
|
export const settings = localize('arc.settings', "Settings");
|
||||||
export const security = localize('arc.security', "Security");
|
export const security = localize('arc.security', "Security");
|
||||||
export const computeAndStorage = localize('arc.computeAndStorage', "Compute + Storage");
|
export const computeAndStorage = localize('arc.computeAndStorage', "Compute + Storage");
|
||||||
|
export const backups = localize('arc.backups', "Backups");
|
||||||
export const coordinatorNodeParameters = localize('arc.coordinatorNodeParameters', "Coordinator Node Parameters");
|
export const coordinatorNodeParameters = localize('arc.coordinatorNodeParameters', "Coordinator Node Parameters");
|
||||||
export const workerNodeParameters = localize('arc.workerNodeParameters', "Worker Node Parameters");
|
export const workerNodeParameters = localize('arc.workerNodeParameters', "Worker Node Parameters");
|
||||||
export const compute = localize('arc.compute', "Compute");
|
export const compute = localize('arc.compute', "Compute");
|
||||||
@@ -34,7 +35,6 @@ export const supportAndTroubleshooting = localize('arc.supportAndTroubleshooting
|
|||||||
export const resourceHealth = localize('arc.resourceHealth', "Resource health");
|
export const resourceHealth = localize('arc.resourceHealth', "Resource health");
|
||||||
export const parameterName = localize('arc.parameterName', "Parameter Name");
|
export const parameterName = localize('arc.parameterName', "Parameter Name");
|
||||||
export const value = localize('arc.value', "Value");
|
export const value = localize('arc.value', "Value");
|
||||||
|
|
||||||
export const newInstance = localize('arc.createNew', "New Instance");
|
export const newInstance = localize('arc.createNew', "New Instance");
|
||||||
export const deleteText = localize('arc.delete', "Delete");
|
export const deleteText = localize('arc.delete', "Delete");
|
||||||
export const learnMore = localize('arc.learnMore', "Learn More.");
|
export const learnMore = localize('arc.learnMore', "Learn More.");
|
||||||
@@ -43,12 +43,14 @@ export const saveText = localize('arc.save', "Save");
|
|||||||
export const discardText = localize('arc.discard', "Discard");
|
export const discardText = localize('arc.discard', "Discard");
|
||||||
export const resetPassword = localize('arc.resetPassword', "Reset Password");
|
export const resetPassword = localize('arc.resetPassword', "Reset Password");
|
||||||
export const loadExtensions = localize('arc.loadExtensions', "Load extensions");
|
export const loadExtensions = localize('arc.loadExtensions', "Load extensions");
|
||||||
|
export const configureRP = localize('arc.configureRP', "Configure retention policy");
|
||||||
export const unloadExtensions = localize('arc.unloadExtensions', "Unload extensions");
|
export const unloadExtensions = localize('arc.unloadExtensions', "Unload extensions");
|
||||||
export const noExtensions = localize('arc.noExtensions', "No extensions listed in configuration.");
|
export const noExtensions = localize('arc.noExtensions', "No extensions listed in configuration.");
|
||||||
export const openInAzurePortal = localize('arc.openInAzurePortal', "Open in Azure Portal");
|
export const openInAzurePortal = localize('arc.openInAzurePortal', "Open in Azure Portal");
|
||||||
export const resourceGroup = localize('arc.resourceGroup', "Resource Group");
|
export const resourceGroup = localize('arc.resourceGroup', "Resource Group");
|
||||||
export const region = localize('arc.region', "Region");
|
export const region = localize('arc.region', "Region");
|
||||||
export const subscriptionId = localize('arc.subscriptionId', "Subscription ID");
|
export const subscriptionId = localize('arc.subscriptionId', "Subscription ID");
|
||||||
|
export const subscription = localize('arc.subscription', "Subscription");
|
||||||
export const state = localize('arc.state', "State");
|
export const state = localize('arc.state', "State");
|
||||||
export const connectionMode = localize('arc.connectionMode', "Connection Mode");
|
export const connectionMode = localize('arc.connectionMode', "Connection Mode");
|
||||||
export const namespace = localize('arc.namespace', "Namespace");
|
export const namespace = localize('arc.namespace', "Namespace");
|
||||||
@@ -56,6 +58,21 @@ export const externalEndpoint = localize('arc.externalEndpoint', "External Endpo
|
|||||||
export const name = localize('arc.name', "Name");
|
export const name = localize('arc.name', "Name");
|
||||||
export const type = localize('arc.type', "Type");
|
export const type = localize('arc.type', "Type");
|
||||||
export const status = localize('arc.status', "Status");
|
export const status = localize('arc.status', "Status");
|
||||||
|
export const database = localize('arc.database', "Database");
|
||||||
|
export const sourceDatabase = localize('arc.sourceDatabase', "Source database");
|
||||||
|
export const earliestPitrRestorePoint = localize('arc.earliestPitrRestorePoint', "Earliest PITR restore point");
|
||||||
|
export const latestpitrRestorePoint = localize('arc.latestpitrRestorePoint', "Latest PITR restore point");
|
||||||
|
export const pitr = localize('arc.pitr', "Point-in-time restore (PITR)");
|
||||||
|
export const projectDetails = localize('arc.projectDetails', "Project Details");
|
||||||
|
export const projectDetailsText = localize('arc.projectDetailsText', "Select the subscription to manage deployed resources. Use resource groups like folders to organize and manage all your resources.");
|
||||||
|
export const sourceDetails = localize('arc.sourceDetails', "Source Details");
|
||||||
|
export const sourceDetailsText = localize('arc.sourceDetailsText', "Select a backup source and provide details. Additional settings will be defaulted where possible based on the selected backup.");
|
||||||
|
export const databaseDetails = localize('arc.databaseDetails', "Database Details");
|
||||||
|
export const databaseDetailsText = localize('arc.databaseDetailsText', "Enter the required settings for this database, including a name and a target managed instance. By default, the source instance is selected.");
|
||||||
|
export const restore = localize('arc.restore', "Restore");
|
||||||
|
export const instance = localize('arc.instance', "Instance");
|
||||||
|
export const restorePoint = localize('arc.restorePoint', "Restore point (UTC)");
|
||||||
|
export const restoreDatabase = localize('arc.restoreDatabase', "Restore Database");
|
||||||
export const miaaAdmin = localize('arc.miaaAdmin', "Managed instance admin");
|
export const miaaAdmin = localize('arc.miaaAdmin', "Managed instance admin");
|
||||||
export const extensionName = localize('arc.extensionName', "Extension name");
|
export const extensionName = localize('arc.extensionName', "Extension name");
|
||||||
export const extensionsDescription = localize('arc.extensionsDescription', "PostgreSQL provides the ability to extend the functionality of your database by using extensions. Extensions allow for bundling multiple related SQL objects together in a single package that can be loaded or removed from your database with a single command. After being loaded in the database, extensions can function like built-in features.");
|
export const extensionsDescription = localize('arc.extensionsDescription', "PostgreSQL provides the ability to extend the functionality of your database by using extensions. Extensions allow for bundling multiple related SQL objects together in a single package that can be loaded or removed from your database with a single command. After being loaded in the database, extensions can function like built-in features.");
|
||||||
@@ -91,6 +108,7 @@ export const workerOneNodeValidationMessage = localize('arc.workerOneNodeValidat
|
|||||||
export const vCores = localize('arc.vCores', "vCores");
|
export const vCores = localize('arc.vCores', "vCores");
|
||||||
export const ram = localize('arc.ram', "RAM");
|
export const ram = localize('arc.ram', "RAM");
|
||||||
export const refresh = localize('arc.refresh', "Refresh");
|
export const refresh = localize('arc.refresh', "Refresh");
|
||||||
|
export const configureRetentionPolicyButton = localize('arc.configureRetentionPolicyButton', "Configure Retention Policy");
|
||||||
export const resetAllToDefault = localize('arc.resetAllToDefault', "Reset all to default");
|
export const resetAllToDefault = localize('arc.resetAllToDefault', "Reset all to default");
|
||||||
export const resetToDefault = localize('arc.resetToDefault', "Reset to default");
|
export const resetToDefault = localize('arc.resetToDefault', "Reset to default");
|
||||||
export const troubleshoot = localize('arc.troubleshoot', "Troubleshoot");
|
export const troubleshoot = localize('arc.troubleshoot', "Troubleshoot");
|
||||||
@@ -130,6 +148,7 @@ export const password = localize('arc.password', "Password");
|
|||||||
export const rememberPassword = localize('arc.rememberPassword', "Remember Password");
|
export const rememberPassword = localize('arc.rememberPassword', "Remember Password");
|
||||||
export const connect = localize('arc.connect', "Connect");
|
export const connect = localize('arc.connect', "Connect");
|
||||||
export const cancel = localize('arc.cancel', "Cancel");
|
export const cancel = localize('arc.cancel', "Cancel");
|
||||||
|
export const apply = localize('arc.apply', "Apply");
|
||||||
export const ok = localize('arc.ok', "Ok");
|
export const ok = localize('arc.ok', "Ok");
|
||||||
export const on = localize('arc.on', "On");
|
export const on = localize('arc.on', "On");
|
||||||
export const off = localize('arc.off', "Off");
|
export const off = localize('arc.off', "Off");
|
||||||
@@ -171,6 +190,9 @@ export const searchToFilter = localize('arc.searchToFilter', "Search to filter i
|
|||||||
export const scalingCompute = localize('arc.scalingCompute', "scaling compute vCores and memory.");
|
export const scalingCompute = localize('arc.scalingCompute', "scaling compute vCores and memory.");
|
||||||
export const postgresComputeAndStorageDescriptionPartOne = localize('arc.postgresComputeAndStorageDescriptionPartOne', "You can scale your Azure Arc-enabled");
|
export const postgresComputeAndStorageDescriptionPartOne = localize('arc.postgresComputeAndStorageDescriptionPartOne', "You can scale your Azure Arc-enabled");
|
||||||
export const miaaComputeAndStorageDescriptionPartOne = localize('arc.miaaComputeAndStorageDescriptionPartOne', "You can scale your Azure SQL managed instance - Azure Arc by");
|
export const miaaComputeAndStorageDescriptionPartOne = localize('arc.miaaComputeAndStorageDescriptionPartOne', "You can scale your Azure SQL managed instance - Azure Arc by");
|
||||||
|
export const miaaBackupsDatabasesDescription = localize('arc.miaaBackupsDatabasesDescription', "Databases with available backups are displayed below. Restore databases to this instance or any other instance within the same custom location.");
|
||||||
|
export const pitrInfo = localize('arc.pitrInfo', "Specify how long you want to keep your point-in-time backups. Customize this for backup availability.");
|
||||||
|
export const restoreInfo = localize('arc.restoreInfo', "Restore a database to an Azure Arc enabled SQL Managed Instance of your choice.");
|
||||||
export const postgresComputeAndStorageDescriptionPartTwo = localize('arc.postgres.computeAndStorageDescriptionPartTwo', "PostgreSQL Hyperscale server group by");
|
export const postgresComputeAndStorageDescriptionPartTwo = localize('arc.postgres.computeAndStorageDescriptionPartTwo', "PostgreSQL Hyperscale server group by");
|
||||||
export const computeAndStorageDescriptionPartThree = localize('arc.computeAndStorageDescriptionPartThree', "without downtime and by");
|
export const computeAndStorageDescriptionPartThree = localize('arc.computeAndStorageDescriptionPartThree', "without downtime and by");
|
||||||
export const computeAndStorageDescriptionPartFour = localize('arc.computeAndStorageDescriptionPartFour', "Before doing so, you need to ensure");
|
export const computeAndStorageDescriptionPartFour = localize('arc.computeAndStorageDescriptionPartFour', "Before doing so, you need to ensure");
|
||||||
@@ -192,6 +214,7 @@ export const coresRequest = localize('arc.coresRequest', "CPU request");
|
|||||||
export const workerCoresRequest = localize('arc.workerCoresRequest', "Worker Nodes CPU request");
|
export const workerCoresRequest = localize('arc.workerCoresRequest', "Worker Nodes CPU request");
|
||||||
export const coordinatorCoresRequest = localize('arc.coordinatorCoresRequest', "Coordinator Node CPU request");
|
export const coordinatorCoresRequest = localize('arc.coordinatorCoresRequest', "Coordinator Node CPU request");
|
||||||
export const memoryLimit = localize('arc.memoryLimit', "Memory limit (in GB)");
|
export const memoryLimit = localize('arc.memoryLimit', "Memory limit (in GB)");
|
||||||
|
export const retentionDays = localize('arc.retentionDays', "Point-In-Time Recovery retention (days)");
|
||||||
export const workerMemoryLimit = localize('arc.workerMemoryLimit', "Worker Nodes Memory limit (in GB)");
|
export const workerMemoryLimit = localize('arc.workerMemoryLimit', "Worker Nodes Memory limit (in GB)");
|
||||||
export const coordinatorMemoryLimit = localize('arc.coordinatorMemoryLimit', "Coordinator Node Memory limit (in GB)");
|
export const coordinatorMemoryLimit = localize('arc.coordinatorMemoryLimit', "Coordinator Node Memory limit (in GB)");
|
||||||
export const memoryRequest = localize('arc.memoryRequest', "Memory request (in GB)");
|
export const memoryRequest = localize('arc.memoryRequest', "Memory request (in GB)");
|
||||||
|
|||||||
@@ -16,13 +16,23 @@ import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
|
|||||||
import { ControllerModel, Registration } from './controllerModel';
|
import { ControllerModel, Registration } from './controllerModel';
|
||||||
import { ResourceModel } from './resourceModel';
|
import { ResourceModel } from './resourceModel';
|
||||||
|
|
||||||
export type DatabaseModel = { name: string, status: string };
|
export type DatabaseModel = { name: string, status: string, lastBackup: string };
|
||||||
|
export type RPModel = { recoveryPointObjective: string, retentionDays: string };
|
||||||
|
export type PITRModel = {
|
||||||
|
instanceName: string,
|
||||||
|
resourceGroupName: string,
|
||||||
|
location: string,
|
||||||
|
subscriptionId: string,
|
||||||
|
dbName: string,
|
||||||
|
restorePoint: string,
|
||||||
|
earliestPitr: string,
|
||||||
|
latestPitr: string,
|
||||||
|
};
|
||||||
|
export const systemDbs = ['master', 'msdb', 'tempdb', 'model'];
|
||||||
export class MiaaModel extends ResourceModel {
|
export class MiaaModel extends ResourceModel {
|
||||||
|
|
||||||
private _config: azExt.SqlMiShowResult | undefined;
|
private _config: azExt.SqlMiShowResult | undefined;
|
||||||
private _databases: DatabaseModel[] = [];
|
private _databases: DatabaseModel[] = [];
|
||||||
|
|
||||||
private readonly _onConfigUpdated = new vscode.EventEmitter<azExt.SqlMiShowResult | undefined>();
|
private readonly _onConfigUpdated = new vscode.EventEmitter<azExt.SqlMiShowResult | undefined>();
|
||||||
private readonly _onDatabasesUpdated = new vscode.EventEmitter<DatabaseModel[]>();
|
private readonly _onDatabasesUpdated = new vscode.EventEmitter<DatabaseModel[]>();
|
||||||
private readonly _azApi: azExt.IExtension;
|
private readonly _azApi: azExt.IExtension;
|
||||||
@@ -30,6 +40,10 @@ export class MiaaModel extends ResourceModel {
|
|||||||
public onDatabasesUpdated = this._onDatabasesUpdated.event;
|
public onDatabasesUpdated = this._onDatabasesUpdated.event;
|
||||||
public configLastUpdated: Date | undefined;
|
public configLastUpdated: Date | undefined;
|
||||||
public databasesLastUpdated: Date | undefined;
|
public databasesLastUpdated: Date | undefined;
|
||||||
|
public rpSettings: RPModel = {
|
||||||
|
recoveryPointObjective: '',
|
||||||
|
retentionDays: ''
|
||||||
|
};
|
||||||
|
|
||||||
private _refreshPromise: Deferred<void> | undefined = undefined;
|
private _refreshPromise: Deferred<void> | undefined = undefined;
|
||||||
|
|
||||||
@@ -76,6 +90,7 @@ export class MiaaModel extends ResourceModel {
|
|||||||
const result = await this._azApi.az.sql.miarc.show(this.info.name, this.controllerModel.info.namespace, this.controllerModel.azAdditionalEnvVars);
|
const result = await this._azApi.az.sql.miarc.show(this.info.name, this.controllerModel.info.namespace, this.controllerModel.azAdditionalEnvVars);
|
||||||
this._config = result.stdout;
|
this._config = result.stdout;
|
||||||
this.configLastUpdated = new Date();
|
this.configLastUpdated = new Date();
|
||||||
|
this.rpSettings.retentionDays = this._config?.spec?.backup?.retentionPeriodInDays?.toString() ?? '';
|
||||||
this._onConfigUpdated.fire(this._config);
|
this._onConfigUpdated.fire(this._config);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// If an error occurs show a message so the user knows something failed but still
|
// If an error occurs show a message so the user knows something failed but still
|
||||||
@@ -111,6 +126,18 @@ export class MiaaModel extends ResourceModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async callGetDatabases(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.getDatabases();
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof UserCancelledError) {
|
||||||
|
vscode.window.showWarningMessage(loc.miaaConnectionRequired);
|
||||||
|
} else {
|
||||||
|
vscode.window.showErrorMessage(loc.fetchDatabasesFailed(this.info.name, error));
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
public async getDatabases(promptForConnection: boolean = true): Promise<void> {
|
public async getDatabases(promptForConnection: boolean = true): Promise<void> {
|
||||||
if (!this._connectionProfile) {
|
if (!this._connectionProfile) {
|
||||||
await this.getConnectionProfile(promptForConnection);
|
await this.getConnectionProfile(promptForConnection);
|
||||||
@@ -132,9 +159,9 @@ export class MiaaModel extends ResourceModel {
|
|||||||
throw new Error('Could not fetch databases');
|
throw new Error('Could not fetch databases');
|
||||||
}
|
}
|
||||||
if (databases.length > 0 && typeof (databases[0]) === 'object') {
|
if (databases.length > 0 && typeof (databases[0]) === 'object') {
|
||||||
this._databases = (<azdata.DatabaseInfo[]>databases).map(db => { return { name: db.options['name'], status: db.options['state'] }; });
|
this._databases = (<azdata.DatabaseInfo[]>databases).map(db => { return { name: db.options['name'], status: db.options['state'], lastBackup: db.options['lastBackup'] }; });
|
||||||
} else {
|
} else {
|
||||||
this._databases = (<string[]>databases).map(db => { return { name: db, status: '-' }; });
|
this._databases = (<string[]>databases).map(db => { return { name: db, status: '-', lastBackup: '' }; });
|
||||||
}
|
}
|
||||||
this.databasesLastUpdated = new Date();
|
this.databasesLastUpdated = new Date();
|
||||||
this._onDatabasesUpdated.fire(this._databases);
|
this._onDatabasesUpdated.fire(this._databases);
|
||||||
@@ -178,4 +205,5 @@ export class MiaaModel extends ResourceModel {
|
|||||||
this._miaaInfo.userName = connectionProfile.userName;
|
this._miaaInfo.userName = connectionProfile.userName;
|
||||||
await this._treeDataProvider.saveControllers();
|
await this._treeDataProvider.saveControllers();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
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 { MiaaConnectionStringsPage } from './miaaConnectionStringsPage';
|
||||||
import { MiaaModel } from '../../../models/miaaModel';
|
import { MiaaModel } from '../../../models/miaaModel';
|
||||||
import { MiaaComputeAndStoragePage } from './miaaComputeAndStoragePage';
|
import { MiaaComputeAndStoragePage } from './miaaComputeAndStoragePage';
|
||||||
|
import { MiaaBackupsPage } from './miaaBackupsPage';
|
||||||
|
|
||||||
export class MiaaDashboard extends Dashboard {
|
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 overviewPage = new MiaaDashboardOverviewPage(modelView, this.dashboard, this._controllerModel, this._miaaModel);
|
||||||
const connectionStringsPage = new MiaaConnectionStringsPage(modelView, this.dashboard, this._miaaModel);
|
const connectionStringsPage = new MiaaConnectionStringsPage(modelView, this.dashboard, this._miaaModel);
|
||||||
const computeAndStoragePage = new MiaaComputeAndStoragePage(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 [
|
return [
|
||||||
overviewPage.tab,
|
overviewPage.tab,
|
||||||
{
|
{
|
||||||
title: loc.settings,
|
title: loc.settings,
|
||||||
tabs: [
|
tabs: [
|
||||||
connectionStringsPage.tab,
|
connectionStringsPage.tab,
|
||||||
computeAndStoragePage.tab
|
computeAndStoragePage.tab,
|
||||||
|
miaaBackupsPage.tab
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import { ControllerModel } from '../../../models/controllerModel';
|
|||||||
import { MiaaModel } from '../../../models/miaaModel';
|
import { MiaaModel } from '../../../models/miaaModel';
|
||||||
import { DashboardPage } from '../../components/dashboardPage';
|
import { DashboardPage } from '../../components/dashboardPage';
|
||||||
import { ResourceType } from 'arc';
|
import { ResourceType } from 'arc';
|
||||||
import { UserCancelledError } from '../../../common/api';
|
|
||||||
|
|
||||||
export class MiaaDashboardOverviewPage extends DashboardPage {
|
export class MiaaDashboardOverviewPage extends DashboardPage {
|
||||||
|
|
||||||
@@ -212,7 +211,7 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
|||||||
this._connectToServerButton!.enabled = false;
|
this._connectToServerButton!.enabled = false;
|
||||||
this._databasesTableLoading!.loading = true;
|
this._databasesTableLoading!.loading = true;
|
||||||
try {
|
try {
|
||||||
await this.callGetDatabases();
|
await this._miaaModel.callGetDatabases();
|
||||||
} catch {
|
} catch {
|
||||||
this._connectToServerButton!.enabled = true;
|
this._connectToServerButton!.enabled = true;
|
||||||
}
|
}
|
||||||
@@ -322,19 +321,6 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
|
|||||||
).component();
|
).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 {
|
private handleRegistrationsUpdated(): void {
|
||||||
const config = this._controllerModel.controllerConfig;
|
const config = this._controllerModel.controllerConfig;
|
||||||
if (this._openInAzurePortalButton) {
|
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 = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -132,6 +132,20 @@ export function getAzApi(localAzDiscovered: Promise<IAzTool | undefined>, azTool
|
|||||||
validateAz(azToolService.localAz);
|
validateAz(azToolService.localAz);
|
||||||
return azToolService.localAz!.sql.miarc.edit(name, args, namespace, additionalEnvVars);
|
return azToolService.localAz!.sql.miarc.edit(name, args, namespace, additionalEnvVars);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
midbarc: {
|
||||||
|
restore: async (name: string,
|
||||||
|
args: {
|
||||||
|
destName?: string,
|
||||||
|
managedInstance?: string,
|
||||||
|
time?: string,
|
||||||
|
noWait?: boolean,
|
||||||
|
},
|
||||||
|
namespace: string, additionalEnvVars?: azExt.AdditionalEnvVars) => {
|
||||||
|
await localAzDiscovered;
|
||||||
|
validateAz(azToolService.localAz);
|
||||||
|
return azToolService.localAz!.sql.midbarc.restore(name, args, namespace, additionalEnvVars);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getPath: async () => {
|
getPath: async () => {
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ export class AzTool implements azExt.IAzApi {
|
|||||||
memoryLimit?: string,
|
memoryLimit?: string,
|
||||||
memoryRequest?: string,
|
memoryRequest?: string,
|
||||||
noWait?: boolean,
|
noWait?: boolean,
|
||||||
|
retentionDays?: string
|
||||||
},
|
},
|
||||||
namespace: string,
|
namespace: string,
|
||||||
additionalEnvVars?: azExt.AdditionalEnvVars
|
additionalEnvVars?: azExt.AdditionalEnvVars
|
||||||
@@ -172,11 +173,33 @@ export class AzTool implements azExt.IAzApi {
|
|||||||
if (args.memoryLimit) { argsArray.push('--memory-limit', args.memoryLimit); }
|
if (args.memoryLimit) { argsArray.push('--memory-limit', args.memoryLimit); }
|
||||||
if (args.memoryRequest) { argsArray.push('--memory-request', args.memoryRequest); }
|
if (args.memoryRequest) { argsArray.push('--memory-request', args.memoryRequest); }
|
||||||
if (args.noWait) { argsArray.push('--no-wait'); }
|
if (args.noWait) { argsArray.push('--no-wait'); }
|
||||||
|
if (args.retentionDays) { argsArray.push('--retention-days', args.retentionDays); }
|
||||||
|
return this.executeCommand<void>(argsArray, additionalEnvVars);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
midbarc: {
|
||||||
|
restore: (
|
||||||
|
name: string,
|
||||||
|
args: {
|
||||||
|
destName?: string,
|
||||||
|
managedInstance?: string,
|
||||||
|
time?: string,
|
||||||
|
noWait?: boolean,
|
||||||
|
},
|
||||||
|
namespace: string,
|
||||||
|
additionalEnvVars?: azExt.AdditionalEnvVars
|
||||||
|
): Promise<azExt.AzOutput<void>> => {
|
||||||
|
const argsArray = ['sql', 'midb-arc', 'restore', '--name', name, '--k8s-namespace', namespace, '--use-k8s'];
|
||||||
|
if (args.destName) { argsArray.push('--dest-name', args.destName); }
|
||||||
|
if (args.managedInstance) { argsArray.push('--managed-instance', args.managedInstance); }
|
||||||
|
if (args.time) { argsArray.push('--time', args.time); }
|
||||||
|
if (args.noWait) { argsArray.push('--no-wait'); }
|
||||||
return this.executeCommand<void>(argsArray, additionalEnvVars);
|
return this.executeCommand<void>(argsArray, additionalEnvVars);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the output of running '--version' command on the az tool.
|
* Gets the output of running '--version' command on the az tool.
|
||||||
* It also updates the cachedVersion property based on the return value from the tool.
|
* It also updates the cachedVersion property based on the return value from the tool.
|
||||||
|
|||||||
27
extensions/azcli/src/typings/az-ext.d.ts
vendored
27
extensions/azcli/src/typings/az-ext.d.ts
vendored
@@ -141,6 +141,9 @@ declare module 'az-ext' {
|
|||||||
uid: string // "cea737aa-3f82-4f6a-9bed-2b51c2c33dff"
|
uid: string // "cea737aa-3f82-4f6a-9bed-2b51c2c33dff"
|
||||||
},
|
},
|
||||||
spec: {
|
spec: {
|
||||||
|
backup?: {
|
||||||
|
retentionPeriodInDays: number, // 1
|
||||||
|
}
|
||||||
scheduling?: {
|
scheduling?: {
|
||||||
default?: {
|
default?: {
|
||||||
resources?: {
|
resources?: {
|
||||||
@@ -314,11 +317,25 @@ declare module 'az-ext' {
|
|||||||
edit(
|
edit(
|
||||||
name: string,
|
name: string,
|
||||||
args: {
|
args: {
|
||||||
coresLimit?: string,
|
coresLimit?: string, //2
|
||||||
coresRequest?: string,
|
coresRequest?: string, //1
|
||||||
memoryLimit?: string,
|
memoryLimit?: string, // 2Gi
|
||||||
memoryRequest?: string,
|
memoryRequest?: string, //1Gi
|
||||||
noWait?: boolean,
|
noWait?: boolean, //true
|
||||||
|
retentionDays?: string, //5
|
||||||
|
},
|
||||||
|
namespace?: string,
|
||||||
|
additionalEnvVars?: AdditionalEnvVars
|
||||||
|
): Promise<AzOutput<void>>
|
||||||
|
},
|
||||||
|
midbarc: {
|
||||||
|
restore(
|
||||||
|
name: string,
|
||||||
|
args: {
|
||||||
|
destName?: string, //testDb
|
||||||
|
managedInstance?: string, //sqlmi1
|
||||||
|
time?: string, //2021-10-12T11:16:30.000Z
|
||||||
|
noWait?: boolean, //true
|
||||||
},
|
},
|
||||||
namespace?: string,
|
namespace?: string,
|
||||||
additionalEnvVars?: AdditionalEnvVars
|
additionalEnvVars?: AdditionalEnvVars
|
||||||
|
|||||||
Reference in New Issue
Block a user