Arc - Fix sqlmiaa initial load (#11125)

This commit is contained in:
Brian Bergeron
2020-06-29 13:34:46 -07:00
committed by GitHub
parent 064ada1d2f
commit 1f558dd2aa
6 changed files with 183 additions and 172 deletions

View File

@@ -6,7 +6,7 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { Authentication, BasicAuth } from '../controller/auth'; import { Authentication, BasicAuth } from '../controller/auth';
import { EndpointsRouterApi, EndpointModel, RegistrationRouterApi, RegistrationResponse, TokenRouterApi, SqlInstanceRouterApi } from '../controller/generated/v1/api'; import { EndpointsRouterApi, EndpointModel, RegistrationRouterApi, RegistrationResponse, TokenRouterApi, SqlInstanceRouterApi } from '../controller/generated/v1/api';
import { parseEndpoint, parseInstanceName } from '../common/utils'; import { getAzurecoreApi, parseEndpoint, parseInstanceName } from '../common/utils';
import { ResourceType } from '../constants'; import { ResourceType } from '../constants';
import { ConnectToControllerDialog } from '../ui/dialogs/connectControllerDialog'; import { ConnectToControllerDialog } from '../ui/dialogs/connectControllerDialog';
import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider'; import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider';
@@ -29,6 +29,7 @@ export type ResourceInfo = {
export interface Registration extends RegistrationResponse { export interface Registration extends RegistrationResponse {
externalIp?: string; externalIp?: string;
externalPort?: string; externalPort?: string;
region?: string
} }
export class ControllerModel { export class ControllerModel {
@@ -101,7 +102,9 @@ export class ControllerModel {
}), }),
this._tokenRouter.apiV1TokenPost().then(async response => { this._tokenRouter.apiV1TokenPost().then(async response => {
this._namespace = response.body.namespace!; this._namespace = response.body.namespace!;
this._registrations = (await this._registrationRouter.apiV1RegistrationListResourcesNsGet(this._namespace)).body.map(mapRegistrationResponse); const registrationResponse = await this._registrationRouter.apiV1RegistrationListResourcesNsGet(this._namespace);
this._registrations = await Promise.all(registrationResponse.body.map(mapRegistrationResponse));
this._controllerRegistration = this._registrations.find(r => r.instanceType === ResourceType.dataControllers); this._controllerRegistration = this._registrations.find(r => r.instanceType === ResourceType.dataControllers);
this.registrationsLastUpdated = new Date(); this.registrationsLastUpdated = new Date();
this._onRegistrationsUpdated.fire(this._registrations); this._onRegistrationsUpdated.fire(this._registrations);
@@ -183,7 +186,12 @@ export class ControllerModel {
* Maps a RegistrationResponse to a Registration, * Maps a RegistrationResponse to a Registration,
* @param response The RegistrationResponse to map * @param response The RegistrationResponse to map
*/ */
function mapRegistrationResponse(response: RegistrationResponse): Registration { async function mapRegistrationResponse(response: RegistrationResponse): Promise<Registration> {
const parsedEndpoint = parseEndpoint(response.externalEndpoint); const parsedEndpoint = parseEndpoint(response.externalEndpoint);
return { ...response, externalIp: parsedEndpoint.ip, externalPort: parsedEndpoint.port }; return {
...response,
externalIp: parsedEndpoint.ip,
externalPort: parsedEndpoint.port,
region: (await getAzurecoreApi()).getRegionDisplayName(response.location)
};
} }

View File

@@ -27,13 +27,12 @@ export class MiaaModel extends ResourceModel {
// The ID of the active connection used to query the server // The ID of the active connection used to query the server
private _activeConnectionId: string | undefined = undefined; private _activeConnectionId: string | undefined = undefined;
private readonly _onPasswordUpdated = new vscode.EventEmitter<string>();
private readonly _onStatusUpdated = new vscode.EventEmitter<HybridSqlNsNameGetResponse | undefined>(); private readonly _onStatusUpdated = new vscode.EventEmitter<HybridSqlNsNameGetResponse | undefined>();
private readonly _onDatabasesUpdated = new vscode.EventEmitter<DatabaseModel[]>(); private readonly _onDatabasesUpdated = new vscode.EventEmitter<DatabaseModel[]>();
public onPasswordUpdated = this._onPasswordUpdated.event;
public onStatusUpdated = this._onStatusUpdated.event; public onStatusUpdated = this._onStatusUpdated.event;
public onDatabasesUpdated = this._onDatabasesUpdated.event; public onDatabasesUpdated = this._onDatabasesUpdated.event;
public passwordLastUpdated?: Date; public statusLastUpdated?: Date;
public databasesLastUpdated?: Date;
private _refreshPromise: Deferred<void> | undefined = undefined; private _refreshPromise: Deferred<void> | undefined = undefined;
@@ -78,12 +77,14 @@ export class MiaaModel extends ResourceModel {
try { try {
const instanceRefresh = this._sqlInstanceRouter.apiV1HybridSqlNsNameGet(this.info.namespace, this.info.name).then(response => { const instanceRefresh = this._sqlInstanceRouter.apiV1HybridSqlNsNameGet(this.info.namespace, this.info.name).then(response => {
this._status = response.body; this._status = response.body;
this.statusLastUpdated = new Date();
this._onStatusUpdated.fire(this._status); this._onStatusUpdated.fire(this._status);
}).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
// fire the event so callers can know to update (e.g. so dashboards don't show the // fire the event so callers can know to update (e.g. so dashboards don't show the
// loading icon forever) // loading icon forever)
vscode.window.showErrorMessage(loc.fetchStatusFailed(this.info.name, err)); vscode.window.showErrorMessage(loc.fetchStatusFailed(this.info.name, err));
this.statusLastUpdated = new Date();
this._onStatusUpdated.fire(undefined); this._onStatusUpdated.fire(undefined);
throw err; throw err;
}); });
@@ -111,6 +112,7 @@ export class MiaaModel extends ResourceModel {
} else { } else {
this._databases = (<string[]>databases).map(db => { return { name: db, status: '-' }; }); this._databases = (<string[]>databases).map(db => { return { name: db, status: '-' }; });
} }
this.databasesLastUpdated = new Date();
this._onDatabasesUpdated.fire(this._databases); this._onDatabasesUpdated.fire(this._databases);
}); });
}); });
@@ -125,6 +127,7 @@ export class MiaaModel extends ResourceModel {
} else { } else {
vscode.window.showErrorMessage(loc.fetchStatusFailed(this.info.name, err)); vscode.window.showErrorMessage(loc.fetchStatusFailed(this.info.name, err));
} }
this.databasesLastUpdated = new Date();
this._onDatabasesUpdated.fire(this._databases); this._onDatabasesUpdated.fire(this._databases);
throw err; throw err;
} }

View File

@@ -9,9 +9,7 @@ import * as loc from '../../../localizedConstants';
import { DashboardPage } from '../../components/dashboardPage'; import { DashboardPage } from '../../components/dashboardPage';
import { IconPathHelper, cssStyles, iconSize, ResourceType, Endpoints } from '../../../constants'; import { IconPathHelper, cssStyles, iconSize, ResourceType, Endpoints } from '../../../constants';
import { ControllerModel } from '../../../models/controllerModel'; import { ControllerModel } from '../../../models/controllerModel';
import { resourceTypeToDisplayName, getAzurecoreApi, getResourceTypeIcon, getConnectionModeDisplayText, parseInstanceName } from '../../../common/utils'; import { resourceTypeToDisplayName, getResourceTypeIcon, getConnectionModeDisplayText, parseInstanceName } from '../../../common/utils';
import { RegistrationResponse } from '../../../controller/generated/v1/model/registrationResponse';
import { EndpointModel } from '../../../controller/generated/v1/api';
export class ControllerDashboardOverviewPage extends DashboardPage { export class ControllerDashboardOverviewPage extends DashboardPage {
@@ -31,24 +29,12 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
instanceNamespace: '-', instanceNamespace: '-',
}; };
private _endpointsRetrieved = false;
private _registrationsRetrieved = false;
constructor(modelView: azdata.ModelView, private _controllerModel: ControllerModel) { constructor(modelView: azdata.ModelView, private _controllerModel: ControllerModel) {
super(modelView); super(modelView);
this._controllerModel.onRegistrationsUpdated((_: RegistrationResponse[]) => {
this.eventuallyRunOnInitialized(() => { this.disposables.push(
this.handleRegistrationsUpdated().catch(e => console.log(e)); this._controllerModel.onRegistrationsUpdated(() => this.eventuallyRunOnInitialized(() => this.handleRegistrationsUpdated())),
}); this._controllerModel.onEndpointsUpdated(() => this.eventuallyRunOnInitialized(() => this.handleEndpointsUpdated())));
});
this._controllerModel.onEndpointsUpdated(endpoints => {
this.eventuallyRunOnInitialized(() => {
this.handleEndpointsUpdated(endpoints);
});
});
this.refresh().catch(e => {
console.log(e);
});
} }
public get title(): string { public get title(): string {
@@ -68,27 +54,11 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
} }
public get container(): azdata.Component { public get container(): azdata.Component {
// Create loaded components
const rootContainer = this.modelView.modelBuilder.flexContainer()
.withLayout({ flexFlow: 'column' })
.component();
const contentContainer = this.modelView.modelBuilder.divContainer().component();
rootContainer.addItem(contentContainer, { CSSStyles: { 'margin': '10px 20px 0px 20px' } });
this._propertiesContainer = this.modelView.modelBuilder.propertiesContainer().component(); this._propertiesContainer = this.modelView.modelBuilder.propertiesContainer().component();
this._propertiesLoadingComponent = this.modelView.modelBuilder.loadingComponent().withItem(this._propertiesContainer).component(); this._propertiesLoadingComponent = this.modelView.modelBuilder.loadingComponent().component();
contentContainer.addItem(this._propertiesLoadingComponent);
const arcResourcesTitle = this.modelView.modelBuilder.text()
.withProperties<azdata.TextComponentProperties>({ value: loc.arcResources })
.component();
contentContainer.addItem(arcResourcesTitle, {
CSSStyles: cssStyles.title
});
this._arcResourcesLoadingComponent = this.modelView.modelBuilder.loadingComponent().component();
this._arcResourcesTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({ this._arcResourcesTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
data: [], data: [],
columns: [ columns: [
@@ -127,11 +97,33 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
ariaLabel: loc.arcResources ariaLabel: loc.arcResources
}).component(); }).component();
const arcResourcesTableContainer = this.modelView.modelBuilder.divContainer() // Update loaded components with data
.withItems([this._arcResourcesTable]) this.handleRegistrationsUpdated();
this.handleEndpointsUpdated();
// Assign the loading component after it has data
this._propertiesLoadingComponent.component = this._propertiesContainer;
this._arcResourcesLoadingComponent.component = this._arcResourcesTable;
// Assemble the container
const rootContainer = this.modelView.modelBuilder.flexContainer()
.withLayout({ flexFlow: 'column' })
.component(); .component();
this._arcResourcesLoadingComponent = this.modelView.modelBuilder.loadingComponent().withItem(arcResourcesTableContainer).component(); const contentContainer = this.modelView.modelBuilder.divContainer().component();
rootContainer.addItem(contentContainer, { CSSStyles: { 'margin': '10px 20px 0px 20px' } });
// Properties
contentContainer.addItem(this._propertiesLoadingComponent);
// Resources
const arcResourcesTitle = this.modelView.modelBuilder.text()
.withProperties<azdata.TextComponentProperties>({ value: loc.arcResources })
.component();
contentContainer.addItem(arcResourcesTitle, {
CSSStyles: cssStyles.title
});
contentContainer.addItem(this._arcResourcesLoadingComponent); contentContainer.addItem(this._arcResourcesLoadingComponent);
this.initialized = true; this.initialized = true;
@@ -193,19 +185,18 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
).component(); ).component();
} }
private async handleRegistrationsUpdated(): Promise<void> { private handleRegistrationsUpdated(): void {
const reg = this._controllerModel.controllerRegistration; const reg = this._controllerModel.controllerRegistration;
this.controllerProperties.instanceName = reg?.instanceName || this.controllerProperties.instanceName; this.controllerProperties.instanceName = reg?.instanceName || this.controllerProperties.instanceName;
this.controllerProperties.resourceGroupName = reg?.resourceGroupName || this.controllerProperties.resourceGroupName; this.controllerProperties.resourceGroupName = reg?.resourceGroupName || this.controllerProperties.resourceGroupName;
this.controllerProperties.location = (await getAzurecoreApi()).getRegionDisplayName(reg?.location) || this.controllerProperties.location; this.controllerProperties.location = reg?.region || this.controllerProperties.location;
this.controllerProperties.subscriptionId = reg?.subscriptionId || this.controllerProperties.subscriptionId; this.controllerProperties.subscriptionId = reg?.subscriptionId || this.controllerProperties.subscriptionId;
this.controllerProperties.connectionMode = getConnectionModeDisplayText(reg?.connectionMode) || this.controllerProperties.connectionMode; this.controllerProperties.connectionMode = getConnectionModeDisplayText(reg?.connectionMode) || this.controllerProperties.connectionMode;
this.controllerProperties.instanceNamespace = reg?.instanceNamespace || this.controllerProperties.instanceNamespace; this.controllerProperties.instanceNamespace = reg?.instanceNamespace || this.controllerProperties.instanceNamespace;
this._registrationsRetrieved = true;
this.refreshDisplayedProperties(); this.refreshDisplayedProperties();
this._arcResourcesTable.data = this._controllerModel.registrations this._arcResourcesTable.data = this._controllerModel.registrations
.filter(r => r.instanceType !== ResourceType.dataControllers) .filter(r => r.instanceType !== ResourceType.dataControllers && !r.isDeleted)
.map(r => { .map(r => {
const iconPath = getResourceTypeIcon(r.instanceType ?? ''); const iconPath = getResourceTypeIcon(r.instanceType ?? '');
const imageComponent = this.modelView.modelBuilder.image() const imageComponent = this.modelView.modelBuilder.image()
@@ -226,55 +217,51 @@ export class ControllerDashboardOverviewPage extends DashboardPage {
}); });
return [imageComponent, nameLink, resourceTypeToDisplayName(r.instanceType), loc.numVCores(r.vCores)]; return [imageComponent, nameLink, resourceTypeToDisplayName(r.instanceType), loc.numVCores(r.vCores)];
}); });
this._arcResourcesLoadingComponent.loading = false; this._arcResourcesLoadingComponent.loading = !this._controllerModel.registrationsLastUpdated;
} }
private handleEndpointsUpdated(endpoints: EndpointModel[]): void { private handleEndpointsUpdated(): void {
const controllerEndpoint = endpoints.find(endpoint => endpoint.name === Endpoints.controller); const controllerEndpoint = this._controllerModel.getEndpoint(Endpoints.controller);
this.controllerProperties.controllerEndpoint = controllerEndpoint?.endpoint || this.controllerProperties.controllerEndpoint; this.controllerProperties.controllerEndpoint = controllerEndpoint?.endpoint || this.controllerProperties.controllerEndpoint;
this._endpointsRetrieved = true;
this.refreshDisplayedProperties(); this.refreshDisplayedProperties();
} }
private refreshDisplayedProperties(): void { private refreshDisplayedProperties(): void {
// Only update once we've retrieved all the necessary properties this._propertiesContainer.propertyItems = [
if (this._endpointsRetrieved && this._registrationsRetrieved) { {
this._propertiesContainer.propertyItems = [ displayName: loc.name,
{ value: this.controllerProperties.instanceName
displayName: loc.name, },
value: this.controllerProperties.instanceName {
}, displayName: loc.resourceGroup,
{ value: this.controllerProperties.resourceGroupName
displayName: loc.resourceGroup, },
value: this.controllerProperties.resourceGroupName {
}, displayName: loc.region,
{ value: this.controllerProperties.location
displayName: loc.region, },
value: this.controllerProperties.location {
}, displayName: loc.subscriptionId,
{ value: this.controllerProperties.subscriptionId
displayName: loc.subscriptionId, },
value: this.controllerProperties.subscriptionId {
}, displayName: loc.type,
{ value: loc.dataControllersType
displayName: loc.type, },
value: loc.dataControllersType {
}, displayName: loc.controllerEndpoint,
{ value: this.controllerProperties.controllerEndpoint
displayName: loc.controllerEndpoint, },
value: this.controllerProperties.controllerEndpoint {
}, displayName: loc.connectionMode,
{ value: this.controllerProperties.connectionMode
displayName: loc.connectionMode, },
value: this.controllerProperties.connectionMode {
}, displayName: loc.namespace,
{ value: this.controllerProperties.instanceNamespace
displayName: loc.namespace, }
value: this.controllerProperties.instanceNamespace ];
}
];
this._propertiesLoadingComponent.loading = false;
}
this._propertiesLoadingComponent.loading = !this._controllerModel.registrationsLastUpdated && !this._controllerModel.endpointsLastUpdated;
} }
} }

View File

@@ -8,20 +8,17 @@ import * as loc from '../../../localizedConstants';
import { IconPathHelper, cssStyles, ResourceType } from '../../../constants'; import { IconPathHelper, cssStyles, ResourceType } from '../../../constants';
import { KeyValueContainer, KeyValue, InputKeyValue, MultilineInputKeyValue } from '../../components/keyValueContainer'; import { KeyValueContainer, KeyValue, InputKeyValue, MultilineInputKeyValue } from '../../components/keyValueContainer';
import { DashboardPage } from '../../components/dashboardPage'; import { DashboardPage } from '../../components/dashboardPage';
import { ControllerModel, Registration } from '../../../models/controllerModel'; import { ControllerModel } from '../../../models/controllerModel';
import { MiaaModel } from '../../../models/miaaModel'; import { MiaaModel } from '../../../models/miaaModel';
export class MiaaConnectionStringsPage extends DashboardPage { export class MiaaConnectionStringsPage extends DashboardPage {
private _keyValueContainer!: KeyValueContainer; private _keyValueContainer!: KeyValueContainer;
private _instanceRegistration: Registration | undefined;
constructor(modelView: azdata.ModelView, private _controllerModel: ControllerModel, private _miaaModel: MiaaModel) { constructor(modelView: azdata.ModelView, private _controllerModel: ControllerModel, private _miaaModel: MiaaModel) {
super(modelView); super(modelView);
this.disposables.push(this._controllerModel.onRegistrationsUpdated(_ => { this.disposables.push(this._controllerModel.onRegistrationsUpdated(_ =>
this._instanceRegistration = this._controllerModel.getRegistration(ResourceType.sqlManagedInstances, this._miaaModel.info.namespace, this._miaaModel.info.name); this.eventuallyRunOnInitialized(() => this.updateConnectionStrings())));
this.eventuallyRunOnInitialized(() => this.updateConnectionStrings());
}));
} }
protected get title(): string { protected get title(): string {
@@ -55,11 +52,9 @@ export class MiaaConnectionStringsPage extends DashboardPage {
this.modelView.modelBuilder.flexContainer().withItems([info]).withLayout({ flexWrap: 'wrap' }).component(), this.modelView.modelBuilder.flexContainer().withItems([info]).withLayout({ flexWrap: 'wrap' }).component(),
{ CSSStyles: { display: 'inline-flex', 'margin-bottom': '25px' } }); { CSSStyles: { display: 'inline-flex', 'margin-bottom': '25px' } });
this._keyValueContainer = new KeyValueContainer(this.modelView.modelBuilder, []); this._keyValueContainer = new KeyValueContainer(this.modelView.modelBuilder, this.getConnectionStrings());
this.disposables.push(this._keyValueContainer); this.disposables.push(this._keyValueContainer);
content.addItem(this._keyValueContainer.container); content.addItem(this._keyValueContainer.container);
this.updateConnectionStrings();
this.initialized = true; this.initialized = true;
return root; return root;
} }
@@ -68,16 +63,17 @@ export class MiaaConnectionStringsPage extends DashboardPage {
return this.modelView.modelBuilder.toolbarContainer().component(); return this.modelView.modelBuilder.toolbarContainer().component();
} }
private updateConnectionStrings(): void { private getConnectionStrings(): KeyValue[] {
if (!this._instanceRegistration) { const instanceRegistration = this._controllerModel.getRegistration(ResourceType.sqlManagedInstances, this._miaaModel.info.namespace, this._miaaModel.info.name);
return; if (!instanceRegistration) {
return [];
} }
const ip = this._instanceRegistration.externalIp; const ip = instanceRegistration.externalIp;
const port = this._instanceRegistration.externalPort; const port = instanceRegistration.externalPort;
const username = this._miaaModel.username; const username = this._miaaModel.username;
const pairs: KeyValue[] = [ return [
new InputKeyValue(this.modelView.modelBuilder, 'ADO.NET', `Server=tcp:${ip},${port};Persist Security Info=False;User ID=${username};Password={your_password_here};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;`), new InputKeyValue(this.modelView.modelBuilder, 'ADO.NET', `Server=tcp:${ip},${port};Persist Security Info=False;User ID=${username};Password={your_password_here};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;`),
new InputKeyValue(this.modelView.modelBuilder, 'C++ (libpq)', `host=${ip} port=${port} user=${username} password={your_password_here} sslmode=require`), new InputKeyValue(this.modelView.modelBuilder, 'C++ (libpq)', `host=${ip} port=${port} user=${username} password={your_password_here} sslmode=require`),
new InputKeyValue(this.modelView.modelBuilder, 'JDBC', `jdbc:sqlserver://${ip}:${port};user=${username};password={your_password_here};encrypt=true;trustServerCertificate=false;loginTimeout=30;`), new InputKeyValue(this.modelView.modelBuilder, 'JDBC', `jdbc:sqlserver://${ip}:${port};user=${username};password={your_password_here};encrypt=true;trustServerCertificate=false;loginTimeout=30;`),
@@ -91,6 +87,9 @@ $conn = sqlsrv_connect($serverName, $connectionInfo);`),
new InputKeyValue(this.modelView.modelBuilder, 'Ruby', `host=${ip}; user=${username} password={your_password_here} port=${port} sslmode=require`), new InputKeyValue(this.modelView.modelBuilder, 'Ruby', `host=${ip}; user=${username} password={your_password_here} port=${port} sslmode=require`),
new InputKeyValue(this.modelView.modelBuilder, 'Web App', `Database=master; Data Source=${ip}; User Id=${username}; Password={your_password_here}`) new InputKeyValue(this.modelView.modelBuilder, 'Web App', `Database=master; Data Source=${ip}; User Id=${username}; Password={your_password_here}`)
]; ];
this._keyValueContainer.refresh(pairs); }
private updateConnectionStrings(): void {
this._keyValueContainer.refresh(this.getConnectionStrings());
} }
} }

View File

@@ -7,12 +7,10 @@ import * as azdata from 'azdata';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as loc from '../../../localizedConstants'; import * as loc from '../../../localizedConstants';
import { DashboardPage } from '../../components/dashboardPage'; import { DashboardPage } from '../../components/dashboardPage';
import { IconPathHelper, cssStyles, ResourceType } from '../../../constants'; import { IconPathHelper, cssStyles, ResourceType, Endpoints } from '../../../constants';
import { ControllerModel, Registration } from '../../../models/controllerModel'; import { ControllerModel } from '../../../models/controllerModel';
import { getAzurecoreApi, promptForResourceDeletion, getDatabaseStateDisplayText } from '../../../common/utils'; import { promptForResourceDeletion, getDatabaseStateDisplayText } from '../../../common/utils';
import { MiaaModel, DatabaseModel } from '../../../models/miaaModel'; import { MiaaModel } from '../../../models/miaaModel';
import { HybridSqlNsNameGetResponse } from '../../../controller/generated/v1/model/hybridSqlNsNameGetResponse';
import { EndpointModel } from '../../../controller/generated/v1/api';
export class MiaaDashboardOverviewPage extends DashboardPage { export class MiaaDashboardOverviewPage extends DashboardPage {
@@ -41,14 +39,10 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
super(modelView); super(modelView);
this._instanceProperties.miaaAdmin = this._miaaModel.username || this._instanceProperties.miaaAdmin; this._instanceProperties.miaaAdmin = this._miaaModel.username || this._instanceProperties.miaaAdmin;
this.disposables.push( this.disposables.push(
this._controllerModel.onRegistrationsUpdated((_: Registration[]) => { this._controllerModel.onRegistrationsUpdated(() => this.handleRegistrationsUpdated()),
this.eventuallyRunOnInitialized(() => { this._controllerModel.onEndpointsUpdated(() => this.eventuallyRunOnInitialized(() => this.handleEndpointsUpdated())),
this.handleRegistrationsUpdated().catch(e => console.log(e)); this._miaaModel.onStatusUpdated(() => this.eventuallyRunOnInitialized(() => this.handleMiaaStatusUpdated())),
}); this._miaaModel.onDatabasesUpdated(() => this.eventuallyRunOnInitialized(() => this.handleDatabasesUpdated()))
}),
this._controllerModel.onEndpointsUpdated(endpoints => this.eventuallyRunOnInitialized(() => this.handleEndpointsUpdated(endpoints))),
this._miaaModel.onStatusUpdated(status => this.eventuallyRunOnInitialized(() => this.handleMiaaStatusUpdated(status))),
this._miaaModel.onDatabasesUpdated(databases => this.eventuallyRunOnInitialized(() => this.handleDatabasesUpdated(databases)))
); );
} }
@@ -69,26 +63,65 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
} }
public get container(): azdata.Component { public get container(): azdata.Component {
// Create loaded components
this._propertiesContainer = this.modelView.modelBuilder.propertiesContainer().component();
this._propertiesLoading = this.modelView.modelBuilder.loadingComponent().component();
this._kibanaLink = this.modelView.modelBuilder.hyperlink().component();
this._kibanaLoading = this.modelView.modelBuilder.loadingComponent().component();
this._grafanaLink = this.modelView.modelBuilder.hyperlink().component();
this._grafanaLoading = this.modelView.modelBuilder.loadingComponent().component();
this._databasesTableLoading = this.modelView.modelBuilder.loadingComponent().component();
this._databasesTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
width: '100%',
columns: [
{
displayName: loc.name,
valueType: azdata.DeclarativeDataType.string,
isReadOnly: true,
width: '80%',
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: cssStyles.tableRow
},
{
displayName: loc.status,
valueType: azdata.DeclarativeDataType.string,
isReadOnly: true,
width: '20%',
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: cssStyles.tableRow
}
],
data: []
}).component();
// Update loaded components with data
this.handleRegistrationsUpdated();
this.handleMiaaStatusUpdated();
this.handleEndpointsUpdated();
this.handleDatabasesUpdated();
// Assign the loading component after it has data
this._propertiesLoading.component = this._propertiesContainer;
this._kibanaLoading.component = this._kibanaLink;
this._grafanaLoading.component = this._grafanaLink;
this._databasesTableLoading.component = this._databasesTable;
// Assemble the container
const rootContainer = this.modelView.modelBuilder.flexContainer() const rootContainer = this.modelView.modelBuilder.flexContainer()
.withLayout({ flexFlow: 'column' }) .withLayout({ flexFlow: 'column' })
.withProperties({ CSSStyles: { 'margin': '18px' } }) .withProperties({ CSSStyles: { 'margin': '18px' } })
.component(); .component();
// Properties // Properties
this._propertiesContainer = this.modelView.modelBuilder.propertiesContainer().component();
this._propertiesLoading = this.modelView.modelBuilder.loadingComponent().withItem(this._propertiesContainer).component();
rootContainer.addItem(this._propertiesLoading, { CSSStyles: cssStyles.text }); rootContainer.addItem(this._propertiesLoading, { CSSStyles: cssStyles.text });
// Service endpoints // Service endpoints
const titleCSS = { ...cssStyles.title, 'margin-block-start': '2em', 'margin-block-end': '0' }; const titleCSS = { ...cssStyles.title, 'margin-block-start': '2em', 'margin-block-end': '0' };
rootContainer.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.serviceEndpoints, CSSStyles: titleCSS }).component()); rootContainer.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.serviceEndpoints, CSSStyles: titleCSS }).component());
this._kibanaLink = this.modelView.modelBuilder.hyperlink().component();
this._grafanaLink = this.modelView.modelBuilder.hyperlink().component();
this._kibanaLoading = this.modelView.modelBuilder.loadingComponent().withItem(this._kibanaLink).component();
this._grafanaLoading = this.modelView.modelBuilder.loadingComponent().withItem(this._grafanaLink).component();
const endpointsTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({ const endpointsTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
width: '100%', width: '100%',
columns: [ columns: [
@@ -132,30 +165,6 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
// Databases // Databases
rootContainer.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.databases, CSSStyles: titleCSS }).component()); rootContainer.addItem(this.modelView.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: loc.databases, CSSStyles: titleCSS }).component());
this._databasesTable = this.modelView.modelBuilder.declarativeTable().withProperties<azdata.DeclarativeTableProperties>({
width: '100%',
columns: [
{
displayName: loc.name,
valueType: azdata.DeclarativeDataType.string,
isReadOnly: true,
width: '80%',
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: cssStyles.tableRow
},
{
displayName: loc.status,
valueType: azdata.DeclarativeDataType.string,
isReadOnly: true,
width: '20%',
headerCssStyles: cssStyles.tableHeader,
rowCssStyles: cssStyles.tableRow
}
],
data: []
}).component();
this._databasesTableLoading = this.modelView.modelBuilder.loadingComponent().withItem(this._databasesTable).component();
rootContainer.addItem(this._databasesTableLoading, { CSSStyles: { 'margin-bottom': '20px' } }); rootContainer.addItem(this._databasesTableLoading, { CSSStyles: { 'margin-bottom': '20px' } });
this.initialized = true; this.initialized = true;
@@ -230,12 +239,12 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
).component(); ).component();
} }
private async handleRegistrationsUpdated(): Promise<void> { private handleRegistrationsUpdated(): void {
const reg = this._controllerModel.getRegistration(ResourceType.sqlManagedInstances, this._miaaModel.info.namespace, this._miaaModel.info.name); const reg = this._controllerModel.getRegistration(ResourceType.sqlManagedInstances, this._miaaModel.info.namespace, this._miaaModel.info.name);
if (reg) { if (reg) {
this._instanceProperties.resourceGroup = reg.resourceGroupName || '-'; this._instanceProperties.resourceGroup = reg.resourceGroupName || '-';
this._instanceProperties.dataController = this._controllerModel.controllerRegistration?.instanceName || '-'; this._instanceProperties.dataController = this._controllerModel.controllerRegistration?.instanceName || '-';
this._instanceProperties.region = (await getAzurecoreApi()).getRegionDisplayName(reg.location); this._instanceProperties.region = reg.region || '-';
this._instanceProperties.subscriptionId = reg.subscriptionId || '-'; this._instanceProperties.subscriptionId = reg.subscriptionId || '-';
this._instanceProperties.vCores = reg.vCores || ''; this._instanceProperties.vCores = reg.vCores || '';
this._instanceProperties.host = reg.externalEndpoint || '-'; this._instanceProperties.host = reg.externalEndpoint || '-';
@@ -243,31 +252,34 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
} }
} }
private async handleMiaaStatusUpdated(status: HybridSqlNsNameGetResponse | undefined): Promise<void> { private handleMiaaStatusUpdated(): void {
this._instanceProperties.status = status?.status || '-'; this._instanceProperties.status = this._miaaModel.status;
this.refreshDisplayedProperties(); this.refreshDisplayedProperties();
} }
private handleEndpointsUpdated(endpoints: EndpointModel[]): void { private handleEndpointsUpdated(): void {
const kibanaEndpoint = this._controllerModel.getEndpoint(Endpoints.logsui);
const kibanaQuery = `kubernetes_namespace:"${this._miaaModel.info.namespace}" and instance_name :"${this._miaaModel.info.name}"`; const kibanaQuery = `kubernetes_namespace:"${this._miaaModel.info.namespace}" and instance_name :"${this._miaaModel.info.name}"`;
const kibanaUrl = `${endpoints.find(e => e.name === 'logsui')?.endpoint}/app/kibana#/discover?_a=(query:(language:kuery,query:'${kibanaQuery}'))`; const kibanaUrl = kibanaEndpoint ? `${kibanaEndpoint.endpoint}/app/kibana#/discover?_a=(query:(language:kuery,query:'${kibanaQuery}'))` : '';
this._kibanaLink.label = kibanaUrl; this._kibanaLink.label = kibanaUrl;
this._kibanaLink.url = kibanaUrl; this._kibanaLink.url = kibanaUrl;
const grafanaUrl = `${endpoints.find(e => e.name === 'metricsui')?.endpoint}/d/wZx3OUdmz/azure-sql-db-managed-instance-metrics?var-hostname=${this._miaaModel.info.name}-0`; const grafanaEndpoint = this._controllerModel.getEndpoint(Endpoints.metricsui);
const grafanaQuery = `var-hostname=${this._miaaModel.info.name}-0`;
const grafanaUrl = grafanaEndpoint ? `${grafanaEndpoint.endpoint}/d/wZx3OUdmz/azure-sql-db-managed-instance-metrics?${grafanaQuery}` : '';
this._grafanaLink.label = grafanaUrl; this._grafanaLink.label = grafanaUrl;
this._grafanaLink.url = grafanaUrl; this._grafanaLink.url = grafanaUrl;
this._kibanaLoading!.loading = false; this._kibanaLoading!.loading = !this._controllerModel.endpointsLastUpdated;
this._grafanaLoading!.loading = false; this._grafanaLoading!.loading = !this._controllerModel.endpointsLastUpdated;
} }
private handleDatabasesUpdated(databases: DatabaseModel[]): void { private handleDatabasesUpdated(): void {
// If we were able to get the databases it means we have a good connection so update the username too // If we were able to get the databases it means we have a good connection so update the username too
this._instanceProperties.miaaAdmin = this._miaaModel.username || this._instanceProperties.miaaAdmin; this._instanceProperties.miaaAdmin = this._miaaModel.username || this._instanceProperties.miaaAdmin;
this.refreshDisplayedProperties(); this.refreshDisplayedProperties();
this._databasesTable.data = databases.map(d => [d.name, getDatabaseStateDisplayText(d.status)]); this._databasesTable.data = this._miaaModel.databases.map(d => [d.name, getDatabaseStateDisplayText(d.status)]);
this._databasesTableLoading.loading = false; this._databasesTableLoading.loading = !this._miaaModel.databasesLastUpdated;
} }
private refreshDisplayedProperties(): void { private refreshDisplayedProperties(): void {
@@ -306,7 +318,9 @@ export class MiaaDashboardOverviewPage extends DashboardPage {
} }
]; ];
this._propertiesLoading.loading = false; this._propertiesLoading.loading =
!this._controllerModel.registrationsLastUpdated &&
!this._miaaModel.statusLastUpdated &&
!this._miaaModel.databasesLastUpdated;
} }
} }

View File

@@ -6,7 +6,7 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as loc from '../../../localizedConstants'; import * as loc from '../../../localizedConstants';
import { IconPathHelper, cssStyles, ResourceType } from '../../../constants'; import { IconPathHelper, cssStyles, ResourceType, Endpoints } from '../../../constants';
import { V1Pod, DuskyObjectModelsDatabaseServiceArcPayload } from '../../../controller/generated/dusky/api'; import { V1Pod, DuskyObjectModelsDatabaseServiceArcPayload } from '../../../controller/generated/dusky/api';
import { DashboardPage } from '../../components/dashboardPage'; import { DashboardPage } from '../../components/dashboardPage';
import { ControllerModel } from '../../../models/controllerModel'; import { ControllerModel } from '../../../models/controllerModel';
@@ -311,13 +311,13 @@ export class PostgresOverviewPage extends DashboardPage {
private getKibanaLink(): string { private getKibanaLink(): string {
const kibanaQuery = `kubernetes_namespace:"${this._postgresModel.namespace}" and cluster_name:"${this._postgresModel.name}"`; const kibanaQuery = `kubernetes_namespace:"${this._postgresModel.namespace}" and cluster_name:"${this._postgresModel.name}"`;
return `${this._controllerModel.getEndpoint('logsui')?.endpoint}/app/kibana#/discover?_a=(query:(language:kuery,query:'${kibanaQuery}'))`; return `${this._controllerModel.getEndpoint(Endpoints.logsui)?.endpoint}/app/kibana#/discover?_a=(query:(language:kuery,query:'${kibanaQuery}'))`;
} }
private getGrafanaLink(): string { private getGrafanaLink(): string {
const grafanaQuery = `var-Namespace=${this._postgresModel.namespace}&var-Name=${this._postgresModel.name}`; const grafanaQuery = `var-Namespace=${this._postgresModel.namespace}&var-Name=${this._postgresModel.name}`;
return `${this._controllerModel.getEndpoint('metricsui')?.endpoint}/d/postgres-metrics?${grafanaQuery}`; return `${this._controllerModel.getEndpoint(Endpoints.metricsui)?.endpoint}/d/postgres-metrics?${grafanaQuery}`;
} }
private getNodes(): string[][] { private getNodes(): string[][] {