diff --git a/src/server_manager/model/digitalocean.ts b/src/server_manager/model/digitalocean.ts index 79fc3cde4..fb6758bce 100644 --- a/src/server_manager/model/digitalocean.ts +++ b/src/server_manager/model/digitalocean.ts @@ -26,6 +26,8 @@ export enum Status { } export interface Account { + // Gets a globally unique identifier for this Account. + getId(): string; // Returns a user-friendly name (email address) associated with the account. getName(): Promise; // Returns the status of the account. diff --git a/src/server_manager/model/server.ts b/src/server_manager/model/server.ts index 7f3c81841..a860ccb57 100644 --- a/src/server_manager/model/server.ts +++ b/src/server_manager/model/server.ts @@ -13,7 +13,7 @@ // limitations under the License. export interface Server { - // Gets the server ID. + // Gets a globally unique identifier for this Server. getId(): string; // Gets the server's name for display. @@ -122,8 +122,6 @@ export interface ManagedServerHost { getRegionId(): RegionId; // Deletes the server - cannot be undone. delete(): Promise; - // Returns the virtual host ID. - getHostId(): string; } export class DataAmount { diff --git a/src/server_manager/web_app/app.ts b/src/server_manager/web_app/app.ts index fe081a2d1..eff741702 100644 --- a/src/server_manager/web_app/app.ts +++ b/src/server_manager/web_app/app.ts @@ -340,14 +340,17 @@ export class App { } try { this.digitalOceanAccount = digitalOceanAccount; - this.appRoot.digitalOceanAccountName = await this.digitalOceanAccount.getName(); + this.appRoot.digitalOceanAccount = { + id: this.digitalOceanAccount.getId(), + name: await this.digitalOceanAccount.getName() + }; const status = await this.digitalOceanAccount.getStatus(); if (status !== digitalocean.Status.ACTIVE) { return []; } const servers = await this.digitalOceanAccount.listServers(); for (const server of servers) { - this.addServer(server); + this.addServer(this.digitalOceanAccount.getId(), server); } return servers; } catch (error) { @@ -360,15 +363,15 @@ export class App { private async loadManualServers() { for (const server of await this.manualServerRepository.listServers()) { - this.addServer(server); + this.addServer(null, server); } } - private makeServerListEntry(server: server.Server): ServerListEntry { + private makeServerListEntry(accountId: string, server: server.Server): ServerListEntry { return { id: server.getId(), + accountId, name: this.makeDisplayName(server), - isManaged: isManagedServer(server), isSynced: !!server.getName(), }; } @@ -384,10 +387,10 @@ export class App { return name; } - private addServer(server: server.Server): void { + private addServer(accountId: string, server: server.Server): void { console.log('Loading server', server); this.idServerMap.set(server.getId(), server); - const serverEntry = this.makeServerListEntry(server); + const serverEntry = this.makeServerListEntry(accountId, server); this.appRoot.serverList = this.appRoot.serverList.concat([serverEntry]); if (isManagedServer(server) && !server.isInstallCompleted()) { @@ -428,7 +431,7 @@ export class App { private updateServerEntry(server: server.Server): void { this.appRoot.serverList = this.appRoot.serverList.map( - (ds) => ds.id === server.getId() ? this.makeServerListEntry(server) : ds); + (ds) => ds.id === server.getId() ? this.makeServerListEntry(ds.accountId, server) : ds); } private getServerById(serverId: string): server.Server { @@ -607,14 +610,19 @@ export class App { // Clears the DigitalOcean credentials and returns to the intro screen. private disconnectDigitalOceanAccount(): void { + if (!this.digitalOceanAccount) { + // Not connected. + return; + } + const accountId = this.digitalOceanAccount.getId(); this.cloudAccounts.disconnectDigitalOceanAccount(); this.digitalOceanAccount = null; for (const serverEntry of this.appRoot.serverList) { - if (serverEntry.isManaged) { + if (serverEntry.accountId === accountId) { this.removeServer(serverEntry.id); } } - this.appRoot.digitalOceanAccountName = ''; + this.appRoot.digitalOceanAccount = null; } // Clears the GCP credentials and returns to the intro screen. @@ -665,7 +673,7 @@ export class App { const server = await this.digitalOceanRetry(() => { return this.digitalOceanAccount.createServer(regionId, serverName); }); - this.addServer(server); + this.addServer(this.digitalOceanAccount.getId(), server); this.showServer(server); } catch (error) { console.error('Error from createDigitalOceanServer', error); @@ -1051,7 +1059,7 @@ export class App { } const manualServer = await this.manualServerRepository.addServer(serverConfig); if (await manualServer.isHealthy()) { - this.addServer(manualServer); + this.addServer(null, manualServer); this.showServer(manualServer); } else { // Remove inaccessible manual server from local storage if it was just created. diff --git a/src/server_manager/web_app/cloud_accounts.ts b/src/server_manager/web_app/cloud_accounts.ts index 6f3104914..1f1f85f4d 100644 --- a/src/server_manager/web_app/cloud_accounts.ts +++ b/src/server_manager/web_app/cloud_accounts.ts @@ -119,7 +119,7 @@ export class CloudAccounts implements accounts.CloudAccounts { } private createDigitalOceanAccount(accessToken: string): DigitalOceanAccount { - return new DigitalOceanAccount(accessToken, this.shadowboxSettings, this.isDebugMode); + return new DigitalOceanAccount('do', accessToken, this.shadowboxSettings, this.isDebugMode); } private createGcpAccount(refreshToken: string): GcpAccount { diff --git a/src/server_manager/web_app/digitalocean_account.ts b/src/server_manager/web_app/digitalocean_account.ts index a4624f16b..8670a2d22 100644 --- a/src/server_manager/web_app/digitalocean_account.ts +++ b/src/server_manager/web_app/digitalocean_account.ts @@ -36,11 +36,15 @@ export class DigitalOceanAccount implements digitalocean.Account { private servers: DigitalOceanServer[] = []; constructor( - private accessToken: string, private shadowboxSettings: ShadowboxSettings, + private id: string, private accessToken: string, private shadowboxSettings: ShadowboxSettings, private debugMode: boolean) { this.digitalOcean = new RestApiSession(accessToken); } + getId(): string { + return this.id; + } + async getName(): Promise { return (await this.digitalOcean.getAccount())?.email; } @@ -121,7 +125,8 @@ export class DigitalOceanAccount implements digitalocean.Account { // Creates a DigitalOceanServer object and adds it to the in-memory server list. private createDigitalOceanServer(digitalOcean: DigitalOceanSession, dropletInfo: DropletInfo) { - const server = new DigitalOceanServer(digitalOcean, dropletInfo); + const server = + new DigitalOceanServer(`${this.id}:${dropletInfo.id}`, digitalOcean, dropletInfo); this.servers.push(server); return server; } diff --git a/src/server_manager/web_app/digitalocean_server.ts b/src/server_manager/web_app/digitalocean_server.ts index 856a9b606..ba1eb6998 100644 --- a/src/server_manager/web_app/digitalocean_server.ts +++ b/src/server_manager/web_app/digitalocean_server.ts @@ -60,10 +60,11 @@ export class DigitalOceanServer extends ShadowboxServer implements server.Manage private eventQueue = new EventEmitter(); private installState: InstallState = InstallState.UNKNOWN; - constructor(private digitalOcean: DigitalOceanSession, private dropletInfo: DropletInfo) { + constructor( + id: string, private digitalOcean: DigitalOceanSession, private dropletInfo: DropletInfo) { // Consider passing a RestEndpoint object to the parent constructor, // to better encapsulate the management api address logic. - super(String(dropletInfo.id)); + super(id); console.info('DigitalOceanServer created'); this.eventQueue.once('server-active', () => console.timeEnd('activeServer')); this.pollInstallState(); @@ -306,10 +307,6 @@ class DigitalOceanHost implements server.ManagedServerHost { this.deleteCallback(); }); } - - getHostId(): string { - return `${this.dropletInfo.id}`; - } } function startsWithCaseInsensitive(text: string, prefix: string) { diff --git a/src/server_manager/web_app/manual_server.ts b/src/server_manager/web_app/manual_server.ts index dd6a02832..fe2cf5b51 100644 --- a/src/server_manager/web_app/manual_server.ts +++ b/src/server_manager/web_app/manual_server.ts @@ -19,8 +19,9 @@ import {ShadowboxServer} from './shadowbox_server'; class ManualServer extends ShadowboxServer implements server.ManualServer { constructor( - private manualServerConfig: server.ManualServerConfig, private forgetCallback: Function) { - super(manualServerConfig.apiUrl); + id: string, private manualServerConfig: server.ManualServerConfig, + private forgetCallback: Function) { + super(id); this.setManagementApiUrl(manualServerConfig.apiUrl); // manualServerConfig.certSha256 is expected to be in hex format (install script). // Electron requires that this be decoded from hex (to unprintable binary), @@ -92,7 +93,7 @@ export class ManualServerRepository implements server.ManualServerRepository { } private createServer(config: server.ManualServerConfig) { - const server = new ManualServer(config, () => { + const server = new ManualServer(`manual:${config.apiUrl}`, config, () => { this.forgetServer(server); }); return server; diff --git a/src/server_manager/web_app/testing/models.ts b/src/server_manager/web_app/testing/models.ts index a2c040a2a..27678818d 100644 --- a/src/server_manager/web_app/testing/models.ts +++ b/src/server_manager/web_app/testing/models.ts @@ -22,6 +22,10 @@ export class FakeDigitalOceanAccount implements digitalocean.Account { constructor(private accessToken = 'fake-access-token') {} + getId(): string { + return 'account-id'; + } + async getName(): Promise { return 'fake-digitalocean-account-name'; } diff --git a/src/server_manager/web_app/ui_components/app-root.js b/src/server_manager/web_app/ui_components/app-root.js index 53be3a9f2..0b0a3b6d4 100644 --- a/src/server_manager/web_app/ui_components/app-root.js +++ b/src/server_manager/web_app/ui_components/app-root.js @@ -52,12 +52,19 @@ import {ServerView} from './outline-server-view.js'; const TOS_ACK_LOCAL_STORAGE_KEY = 'tos-ack'; +/** + * A cloud account to be displayed + * @typedef {Object} AccountListEntry + * @prop {string} id + * @prop {string} name + */ + /** * An access key to be displayed * @typedef {Object} ServerListEntry * @prop {string} id + * @prop {string|null} accountId * @prop {string} name - * @prop {boolean} isManaged * @prop {boolean} isSynced */ @@ -382,7 +389,7 @@ export class AppRoot extends mixinBehaviors
- + @@ -446,20 +453,20 @@ export class AppRoot extends mixinBehaviors static expandedServersTemplate() { return html` -
+
[[localize('servers-digitalocean')]]

[[localize('digitalocean-disconnect-account')]]

- +
[[localize('digitalocean-disconnect')]]
-