mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Arc - Fix KeyValueContainer refresh and disposal (#11090)
This commit is contained in:
@@ -9,115 +9,160 @@ import * as loc from '../../localizedConstants';
|
||||
import { IconPathHelper, cssStyles } from '../../constants';
|
||||
|
||||
/** A container with a single vertical column of KeyValue pairs */
|
||||
export class KeyValueContainer {
|
||||
export class KeyValueContainer extends vscode.Disposable {
|
||||
public container: azdata.DivContainer;
|
||||
private keyToComponent: Map<string, (azdata.TextComponent | azdata.InputBoxComponent)>;
|
||||
private pairs: KeyValue[] = [];
|
||||
|
||||
constructor(modelBuilder: azdata.ModelBuilder, pairs: KeyValue[]) {
|
||||
super(() => this.pairs.forEach(d => {
|
||||
try { d.dispose(); } catch { }
|
||||
}));
|
||||
|
||||
constructor(private modelBuilder: azdata.ModelBuilder, pairs: KeyValue[]) {
|
||||
this.container = modelBuilder.divContainer().component();
|
||||
this.keyToComponent = new Map<string, azdata.Component>();
|
||||
this.refresh(pairs);
|
||||
}
|
||||
|
||||
// TODO: Support removing KeyValues, and handle race conditions when
|
||||
// adding/removing KeyValues concurrently. For now this should only be used
|
||||
// when the set of keys won't change (though their values can be refreshed).
|
||||
// TODO: Support adding/removing KeyValues concurrently. For now this should only
|
||||
// be used when the set of keys won't change (though their values can be refreshed).
|
||||
public refresh(pairs: KeyValue[]) {
|
||||
pairs.forEach(p => {
|
||||
let component = this.keyToComponent.get(p.key);
|
||||
if (component) {
|
||||
component.value = p.value;
|
||||
} else {
|
||||
component = p.getComponent(this.modelBuilder);
|
||||
this.keyToComponent.set(p.key, component);
|
||||
pairs.forEach(newPair => {
|
||||
const pair = this.pairs.find(oldPair => oldPair.key === newPair.key);
|
||||
if (!pair) {
|
||||
this.pairs.push(newPair);
|
||||
this.container.addItem(
|
||||
component,
|
||||
{ CSSStyles: { 'margin-bottom': '15px', 'min-height': '30px' } }
|
||||
);
|
||||
newPair.container,
|
||||
{ CSSStyles: { 'margin-bottom': '15px', 'min-height': '30px' } });
|
||||
} else if (pair.value !== newPair.value) {
|
||||
pair.setValue(newPair.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** A key value pair in the KeyValueContainer */
|
||||
export abstract class KeyValue {
|
||||
constructor(public key: string, public value: string) { }
|
||||
export abstract class KeyValue extends vscode.Disposable {
|
||||
readonly container: azdata.FlexContainer;
|
||||
protected disposables: vscode.Disposable[] = [];
|
||||
protected valueFlex = { flex: '1 1 250px' };
|
||||
private keyFlex = { flex: `0 0 200px` };
|
||||
|
||||
/** Returns a component representing the entire KeyValue pair */
|
||||
public getComponent(modelBuilder: azdata.ModelBuilder) {
|
||||
const container = modelBuilder.flexContainer().withLayout({ flexWrap: 'wrap', alignItems: 'center' }).component();
|
||||
const key = modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: this.key,
|
||||
constructor(modelBuilder: azdata.ModelBuilder, readonly key: string, private _value: string) {
|
||||
super(() => this.disposables.forEach(d => {
|
||||
try { d.dispose(); } catch { }
|
||||
}));
|
||||
|
||||
this.container = modelBuilder.flexContainer().withLayout({
|
||||
flexWrap: 'wrap',
|
||||
alignItems: 'center'
|
||||
}).component();
|
||||
|
||||
const keyComponent = modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: key,
|
||||
CSSStyles: { ...cssStyles.text, 'font-weight': 'bold', 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
|
||||
container.addItem(key, { flex: `0 0 200px` });
|
||||
container.addItem(this.getValueComponent(modelBuilder), { flex: '1 1 250px' });
|
||||
return container;
|
||||
this.container.addItem(keyComponent, this.keyFlex);
|
||||
}
|
||||
|
||||
/** Returns a component representing the value of the KeyValue pair */
|
||||
protected abstract getValueComponent(modelBuilder: azdata.ModelBuilder): azdata.Component;
|
||||
get value(): string {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
public setValue(newValue: string) {
|
||||
this._value = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
/** Implementation of KeyValue where the value is text */
|
||||
export class TextKeyValue extends KeyValue {
|
||||
getValueComponent(modelBuilder: azdata.ModelBuilder): azdata.Component {
|
||||
return modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: this.value,
|
||||
private text: azdata.TextComponent;
|
||||
|
||||
constructor(modelBuilder: azdata.ModelBuilder, key: string, value: string) {
|
||||
super(modelBuilder, key, value);
|
||||
|
||||
this.text = modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: value,
|
||||
CSSStyles: { ...cssStyles.text, 'margin-block-start': '0px', 'margin-block-end': '0px' }
|
||||
}).component();
|
||||
|
||||
this.container.addItem(this.text, this.valueFlex);
|
||||
}
|
||||
|
||||
public setValue(newValue: string) {
|
||||
super.setValue(newValue);
|
||||
this.text.value = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
/** Implementation of KeyValue where the value is a readonly copyable input field */
|
||||
export abstract class BaseInputKeyValue extends KeyValue {
|
||||
constructor(key: string, value: string, private multiline: boolean) { super(key, value); }
|
||||
private input: azdata.InputBoxComponent;
|
||||
|
||||
getValueComponent(modelBuilder: azdata.ModelBuilder): azdata.Component {
|
||||
const container = modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
|
||||
container.addItem(modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
value: this.value,
|
||||
constructor(modelBuilder: azdata.ModelBuilder, key: string, value: string, multiline: boolean) {
|
||||
super(modelBuilder, key, value);
|
||||
|
||||
this.input = modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
|
||||
value: value,
|
||||
readOnly: true,
|
||||
multiline: this.multiline
|
||||
}).component());
|
||||
|
||||
const copy = modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
iconPath: IconPathHelper.copy, width: '17px', height: '17px'
|
||||
multiline: multiline
|
||||
}).component();
|
||||
|
||||
copy.onDidClick(async () => {
|
||||
vscode.env.clipboard.writeText(this.value);
|
||||
vscode.window.showInformationMessage(loc.copiedToClipboard(this.key));
|
||||
});
|
||||
const inputContainer = modelBuilder.flexContainer().withLayout({ alignItems: 'center' }).component();
|
||||
inputContainer.addItem(this.input);
|
||||
|
||||
container.addItem(copy, { CSSStyles: { 'margin-left': '10px' } });
|
||||
return container;
|
||||
const copy = modelBuilder.button().withProperties<azdata.ButtonProperties>({
|
||||
iconPath: IconPathHelper.copy,
|
||||
width: '17px',
|
||||
height: '17px'
|
||||
}).component();
|
||||
|
||||
this.disposables.push(copy.onDidClick(async () => {
|
||||
vscode.env.clipboard.writeText(value);
|
||||
vscode.window.showInformationMessage(loc.copiedToClipboard(key));
|
||||
}));
|
||||
|
||||
inputContainer.addItem(copy, { CSSStyles: { 'margin-left': '10px' } });
|
||||
this.container.addItem(inputContainer, this.valueFlex);
|
||||
}
|
||||
|
||||
public setValue(newValue: string) {
|
||||
super.setValue(newValue);
|
||||
this.input.value = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
/** Implementation of KeyValue where the value is a single line readonly copyable input field */
|
||||
export class InputKeyValue extends BaseInputKeyValue {
|
||||
constructor(key: string, value: string) { super(key, value, false); }
|
||||
constructor(modelBuilder: azdata.ModelBuilder, key: string, value: string) {
|
||||
super(modelBuilder, key, value, false);
|
||||
}
|
||||
}
|
||||
|
||||
/** Implementation of KeyValue where the value is a multi line readonly copyable input field */
|
||||
export class MultilineInputKeyValue extends BaseInputKeyValue {
|
||||
constructor(key: string, value: string) { super(key, value, true); }
|
||||
constructor(modelBuilder: azdata.ModelBuilder, key: string, value: string) {
|
||||
super(modelBuilder, key, value, true);
|
||||
}
|
||||
}
|
||||
|
||||
/** Implementation of KeyValue where the value is a clickable link */
|
||||
export class LinkKeyValue extends KeyValue {
|
||||
constructor(key: string, value: string, private onClick: (e: any) => any) {
|
||||
super(key, value);
|
||||
}
|
||||
private link: azdata.HyperlinkComponent;
|
||||
|
||||
getValueComponent(modelBuilder: azdata.ModelBuilder): azdata.Component {
|
||||
const link = modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
|
||||
label: this.value, url: ''
|
||||
constructor(modelBuilder: azdata.ModelBuilder, key: string, value: string, onClick: (e: any) => any) {
|
||||
super(modelBuilder, key, value);
|
||||
|
||||
this.link = modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({
|
||||
label: value,
|
||||
url: ''
|
||||
}).component();
|
||||
|
||||
link.onDidClick(this.onClick);
|
||||
return link;
|
||||
this.disposables.push(this.link.onDidClick(onClick));
|
||||
this.container.addItem(this.link, this.valueFlex);
|
||||
}
|
||||
|
||||
public setValue(newValue: string) {
|
||||
super.setValue(newValue);
|
||||
this.link.label = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,9 @@ export class MiaaConnectionStringsPage extends DashboardPage {
|
||||
{ CSSStyles: { display: 'inline-flex', 'margin-bottom': '25px' } });
|
||||
|
||||
this._keyValueContainer = new KeyValueContainer(this.modelView.modelBuilder, []);
|
||||
this.disposables.push(this._keyValueContainer);
|
||||
content.addItem(this._keyValueContainer.container);
|
||||
|
||||
this.updateConnectionStrings();
|
||||
this.initialized = true;
|
||||
return root;
|
||||
@@ -76,18 +78,18 @@ export class MiaaConnectionStringsPage extends DashboardPage {
|
||||
const username = this._miaaModel.username;
|
||||
|
||||
const pairs: KeyValue[] = [
|
||||
new InputKeyValue('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('C++ (libpq)', `host=${ip} port=${port} user=${username} password={your_password_here} sslmode=require`),
|
||||
new InputKeyValue('JDBC', `jdbc:sqlserver://${ip}:${port};user=${username};password={your_password_here};encrypt=true;trustServerCertificate=false;loginTimeout=30;`),
|
||||
new InputKeyValue('Node.js', `host=${ip} port=${port} dbname=master user=${username} password={your_password_here} sslmode=require`),
|
||||
new InputKeyValue('ODBC', `Driver={ODBC Driver 13 for SQL Server};Server=${ip},${port};Uid=${username};Pwd={your_password_here};Encrypt=yes;TrustServerCertificate=no;Connection Timeout=30;`),
|
||||
new MultilineInputKeyValue('PHP',
|
||||
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, 'JDBC', `jdbc:sqlserver://${ip}:${port};user=${username};password={your_password_here};encrypt=true;trustServerCertificate=false;loginTimeout=30;`),
|
||||
new InputKeyValue(this.modelView.modelBuilder, 'Node.js', `host=${ip} port=${port} dbname=master user=${username} password={your_password_here} sslmode=require`),
|
||||
new InputKeyValue(this.modelView.modelBuilder, 'ODBC', `Driver={ODBC Driver 13 for SQL Server};Server=${ip},${port};Uid=${username};Pwd={your_password_here};Encrypt=yes;TrustServerCertificate=no;Connection Timeout=30;`),
|
||||
new MultilineInputKeyValue(this.modelView.modelBuilder, 'PHP',
|
||||
`$connectionInfo = array("UID" => "${username}", "pwd" => "{your_password_here}", "LoginTimeout" => 30, "Encrypt" => 1, "TrustServerCertificate" => 0);
|
||||
$serverName = "${ip},${port}";
|
||||
$conn = sqlsrv_connect($serverName, $connectionInfo);`),
|
||||
new InputKeyValue('Python', `dbname='master' user='${username}' host='${ip}' password='{your_password_here}' port='${port}' sslmode='true'`),
|
||||
new InputKeyValue('Ruby', `host=${ip}; user=${username} password={your_password_here} port=${port} sslmode=require`),
|
||||
new InputKeyValue('Web App', `Database=master; Data Source=${ip}; User Id=${username}; Password={your_password_here}`)
|
||||
new InputKeyValue(this.modelView.modelBuilder, 'Python', `dbname='master' user='${username}' host='${ip}' password='{your_password_here}' port='${port}' sslmode='true'`),
|
||||
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}`)
|
||||
];
|
||||
this._keyValueContainer.refresh(pairs);
|
||||
}
|
||||
|
||||
@@ -60,6 +60,8 @@ export class PostgresConnectionStringsPage extends DashboardPage {
|
||||
content.addItem(infoAndLink, { CSSStyles: { 'margin-bottom': '25px' } });
|
||||
|
||||
this.keyValueContainer = new KeyValueContainer(this.modelView.modelBuilder, this.getConnectionStrings());
|
||||
this.disposables.push(this.keyValueContainer);
|
||||
|
||||
this.loading = this.modelView.modelBuilder.loadingComponent()
|
||||
.withItem(this.keyValueContainer.container)
|
||||
.withProperties<azdata.LoadingComponentProperties>({
|
||||
@@ -99,15 +101,15 @@ export class PostgresConnectionStringsPage extends DashboardPage {
|
||||
const endpoint: { ip?: string, port?: number } = this._postgresModel.endpoint;
|
||||
|
||||
return [
|
||||
new InputKeyValue('ADO.NET', `Server=${endpoint.ip};Database=postgres;Port=${endpoint.port};User Id=postgres;Password={your_password_here};Ssl Mode=Require;`),
|
||||
new InputKeyValue('C++ (libpq)', `host=${endpoint.ip} port=${endpoint.port} dbname=postgres user=postgres password={your_password_here} sslmode=require`),
|
||||
new InputKeyValue('JDBC', `jdbc:postgresql://${endpoint.ip}:${endpoint.port}/postgres?user=postgres&password={your_password_here}&sslmode=require`),
|
||||
new InputKeyValue('Node.js', `host=${endpoint.ip} port=${endpoint.port} dbname=postgres user=postgres password={your_password_here} sslmode=require`),
|
||||
new InputKeyValue('PHP', `host=${endpoint.ip} port=${endpoint.port} dbname=postgres user=postgres password={your_password_here} sslmode=require`),
|
||||
new InputKeyValue('psql', `psql "host=${endpoint.ip} port=${endpoint.port} dbname=postgres user=postgres password={your_password_here} sslmode=require"`),
|
||||
new InputKeyValue('Python', `dbname='postgres' user='postgres' host='${endpoint.ip}' password='{your_password_here}' port='${endpoint.port}' sslmode='true'`),
|
||||
new InputKeyValue('Ruby', `host=${endpoint.ip}; dbname=postgres user=postgres password={your_password_here} port=${endpoint.port} sslmode=require`),
|
||||
new InputKeyValue('Web App', `Database=postgres; Data Source=${endpoint.ip}; User Id=postgres; Password={your_password_here}`)
|
||||
new InputKeyValue(this.modelView.modelBuilder, 'ADO.NET', `Server=${endpoint.ip};Database=postgres;Port=${endpoint.port};User Id=postgres;Password={your_password_here};Ssl Mode=Require;`),
|
||||
new InputKeyValue(this.modelView.modelBuilder, 'C++ (libpq)', `host=${endpoint.ip} port=${endpoint.port} dbname=postgres user=postgres password={your_password_here} sslmode=require`),
|
||||
new InputKeyValue(this.modelView.modelBuilder, 'JDBC', `jdbc:postgresql://${endpoint.ip}:${endpoint.port}/postgres?user=postgres&password={your_password_here}&sslmode=require`),
|
||||
new InputKeyValue(this.modelView.modelBuilder, 'Node.js', `host=${endpoint.ip} port=${endpoint.port} dbname=postgres user=postgres password={your_password_here} sslmode=require`),
|
||||
new InputKeyValue(this.modelView.modelBuilder, 'PHP', `host=${endpoint.ip} port=${endpoint.port} dbname=postgres user=postgres password={your_password_here} sslmode=require`),
|
||||
new InputKeyValue(this.modelView.modelBuilder, 'psql', `psql "host=${endpoint.ip} port=${endpoint.port} dbname=postgres user=postgres password={your_password_here} sslmode=require"`),
|
||||
new InputKeyValue(this.modelView.modelBuilder, 'Python', `dbname='postgres' user='postgres' host='${endpoint.ip}' password='{your_password_here}' port='${endpoint.port}' sslmode='true'`),
|
||||
new InputKeyValue(this.modelView.modelBuilder, 'Ruby', `host=${endpoint.ip}; dbname=postgres user=postgres password={your_password_here} port=${endpoint.port} sslmode=require`),
|
||||
new InputKeyValue(this.modelView.modelBuilder, 'Web App', `Database=postgres; Data Source=${endpoint.ip}; User Id=postgres; Password={your_password_here}`)
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,8 @@ export class PostgresPropertiesPage extends DashboardPage {
|
||||
}).component());
|
||||
|
||||
this.keyValueContainer = new KeyValueContainer(this.modelView.modelBuilder, this.getProperties());
|
||||
this.disposables.push(this.keyValueContainer);
|
||||
|
||||
this.loading = this.modelView.modelBuilder.loadingComponent()
|
||||
.withItem(this.keyValueContainer.container)
|
||||
.withProperties<azdata.LoadingComponentProperties>({
|
||||
@@ -94,15 +96,15 @@ export class PostgresPropertiesPage extends DashboardPage {
|
||||
const registration = this._controllerModel.getRegistration(ResourceType.postgresInstances, this._postgresModel.namespace, this._postgresModel.name);
|
||||
|
||||
return [
|
||||
new InputKeyValue(loc.coordinatorEndpoint, connectionString),
|
||||
new InputKeyValue(loc.postgresAdminUsername, 'postgres'),
|
||||
new TextKeyValue(loc.status, this._postgresModel.service?.status?.state ?? 'Unknown'),
|
||||
new InputKeyValue(this.modelView.modelBuilder, loc.coordinatorEndpoint, connectionString),
|
||||
new InputKeyValue(this.modelView.modelBuilder, loc.postgresAdminUsername, 'postgres'),
|
||||
new TextKeyValue(this.modelView.modelBuilder, loc.status, this._postgresModel.service?.status?.state ?? 'Unknown'),
|
||||
// TODO: Make this a LinkKeyValue that opens the controller dashboard
|
||||
new TextKeyValue(loc.dataController, this._controllerModel.namespace ?? ''),
|
||||
new TextKeyValue(loc.nodeConfiguration, this._postgresModel.configuration),
|
||||
new TextKeyValue(loc.postgresVersion, this._postgresModel.service?.spec?.engine?.version?.toString() ?? ''),
|
||||
new TextKeyValue(loc.resourceGroup, registration?.resourceGroupName ?? ''),
|
||||
new TextKeyValue(loc.subscriptionId, registration?.subscriptionId ?? '')
|
||||
new TextKeyValue(this.modelView.modelBuilder, loc.dataController, this._controllerModel.namespace ?? ''),
|
||||
new TextKeyValue(this.modelView.modelBuilder, loc.nodeConfiguration, this._postgresModel.configuration),
|
||||
new TextKeyValue(this.modelView.modelBuilder, loc.postgresVersion, this._postgresModel.service?.spec?.engine?.version?.toString() ?? ''),
|
||||
new TextKeyValue(this.modelView.modelBuilder, loc.resourceGroup, registration?.resourceGroupName ?? ''),
|
||||
new TextKeyValue(this.modelView.modelBuilder, loc.subscriptionId, registration?.subscriptionId ?? '')
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user