Skip to content

Commit

Permalink
Merge pull request #852 from Jigsaw-Code/fortuna-cloud
Browse files Browse the repository at this point in the history
Use Account ids
  • Loading branch information
fortuna authored Mar 25, 2021
2 parents 4b2cfbc + 5a14fe6 commit ac0d26a
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 42 deletions.
2 changes: 2 additions & 0 deletions src/server_manager/model/digitalocean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>;
// Returns the status of the account.
Expand Down
4 changes: 1 addition & 3 deletions src/server_manager/model/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -122,8 +122,6 @@ export interface ManagedServerHost {
getRegionId(): RegionId;
// Deletes the server - cannot be undone.
delete(): Promise<void>;
// Returns the virtual host ID.
getHostId(): string;
}

export class DataAmount {
Expand Down
32 changes: 20 additions & 12 deletions src/server_manager/web_app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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(),
};
}
Expand All @@ -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()) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/server_manager/web_app/cloud_accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
9 changes: 7 additions & 2 deletions src/server_manager/web_app/digitalocean_account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> {
return (await this.digitalOcean.getAccount())?.email;
}
Expand Down Expand Up @@ -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;
}
Expand Down
9 changes: 3 additions & 6 deletions src/server_manager/web_app/digitalocean_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -306,10 +307,6 @@ class DigitalOceanHost implements server.ManagedServerHost {
this.deleteCallback();
});
}

getHostId(): string {
return `${this.dropletInfo.id}`;
}
}

function startsWithCaseInsensitive(text: string, prefix: string) {
Expand Down
7 changes: 4 additions & 3 deletions src/server_manager/web_app/manual_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions src/server_manager/web_app/testing/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export class FakeDigitalOceanAccount implements digitalocean.Account {

constructor(private accessToken = 'fake-access-token') {}

getId(): string {
return 'account-id';
}

async getName(): Promise<string> {
return 'fake-digitalocean-account-name';
}
Expand Down
44 changes: 29 additions & 15 deletions src/server_manager/web_app/ui_components/app-root.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/

Expand Down Expand Up @@ -382,7 +389,7 @@ export class AppRoot extends mixinBehaviors
<app-header-layout>
<div class="app-container">
<iron-pages attr-for-selected="id" selected="{{ currentPage }}">
<outline-intro-step id="intro" digital-ocean-account-name="{{digitalOceanAccountName}}" gcp-account-name="{{gcpAccountName}}" localize="[[localize]]"></outline-intro-step>
<outline-intro-step id="intro" digital-ocean-account-name="{{digitalOceanAccount.name}}" gcp-account-name="{{gcpAccountName}}" localize="[[localize]]"></outline-intro-step>
<outline-do-oauth-step id="digitalOceanOauth" localize="[[localize]]"></outline-do-oauth-step>
<outline-gcp-oauth-step id="gcpOauth" localize="[[localize]]"></outline-gcp-oauth-step>
<outline-manual-server-entry id="manualEntry" localize="[[localize]]"></outline-manual-server-entry>
Expand Down Expand Up @@ -446,27 +453,28 @@ export class AppRoot extends mixinBehaviors
static expandedServersTemplate() {
return html`
<!-- DigitalOcean servers -->
<div class="servers-section" hidden\$="[[!digitalOceanAccountName]]">
<div class="servers-section" hidden\$="[[!digitalOceanAccount]]">
<div class="servers-header">
<span>[[localize('servers-digitalocean')]]</span>
<paper-menu-button horizontal-align="left" class="" close-on-activate="" no-animations="" dynamic-align="" no-overlap="">
<paper-icon-button icon="more-vert" slot="dropdown-trigger"></paper-icon-button>
<div class="do-overflow-menu" slot="dropdown-content">
<h4>[[localize('digitalocean-disconnect-account')]]</h4>
<div class="account-info"><img src="images/digital_ocean_logo.svg">[[digitalOceanAccountName]]</div>
<div class="account-info"><img src="images/digital_ocean_logo.svg">[[digitalOceanAccount.name]]</div>
<div class="sign-out-button" on-tap="signOutTapped">[[localize('digitalocean-disconnect')]]</div>
</div>
</paper-menu-button>
</div>
<div class="servers-container">
<template is="dom-repeat" items="[[serverList]]" as="server" filter="_isServerManaged" sort="_sortServersByName">
<template is="dom-repeat" items="[[serverList]]" as="server" filter="[[_accountServerFilter(digitalOceanAccount)]]" sort="_sortServersByName">
<div class\$="server [[_computeServerClasses(selectedServerId, server)]]" data-server\$="[[server]]" on-tap="_showServer">
<img class="server-icon" src\$="images/[[_computeServerImage(selectedServerId, server)]]">
<span>[[server.name]]</span>
</div>
</template>
</div>
</div>
<!-- TODO(fortuna): Insert GCP servers here -->
<!-- Manual servers -->
<div class="servers-section" hidden\$="[[!_hasManualServers(serverList)]]">
<div class="servers-header">
Expand All @@ -487,14 +495,15 @@ export class AppRoot extends mixinBehaviors
static minimizedServersTemplate() {
return html`
<!-- DigitalOcean servers -->
<div class="side-bar-section servers-section" hidden\$="[[!digitalOceanAccountName]]">
<div class="side-bar-section servers-section" hidden\$="[[!digitalOceanAccount]]">
<img class="provider-icon" src="images/do_white_logo.svg">
<template is="dom-repeat" items="[[serverList]]" as="server" filter="_isServerManaged" sort="_sortServersByName">
<template is="dom-repeat" items="[[serverList]]" as="server" filter="[[_accountServerFilter(digitalOceanAccount)]]" sort="_sortServersByName">
<div class\$="server [[_computeServerClasses(selectedServerId, server)]]" data-server\$="[[server]]" on-tap="_showServer">
<img class="server-icon" src\$="images/[[_computeServerImage(selectedServerId, server)]]">
</div>
</template>
</div>
<!-- TODO(fortuna): Insert GCP servers here -->
<!-- Manual servers -->
<div class="side-bar-section servers-section" hidden\$="[[!_hasManualServers(serverList)]]">
<img class="provider-icon" src="images/cloud.svg">
Expand All @@ -520,8 +529,8 @@ export class AppRoot extends mixinBehaviors
useKeyIfMissing: {type: Boolean},
serverList: {type: Array},
selectedServerId: {type: String},
digitalOceanAccountName: String,
gcpAccountName: String,
digitalOceanAccount: Object,
gcpAccount: Object,
outlineVersion: String,
userAcceptedTos: {
type: Boolean,
Expand Down Expand Up @@ -550,8 +559,10 @@ export class AppRoot extends mixinBehaviors
this.useKeyIfMissing = true;
/** @type {ServerListEntry[]} */
this.serverList = [];
this.digitalOceanAccountName = '';
this.gcpAccountName = '';
/** @type {AccountListEntry} */
this.digitalOceanAccount = null;
/** @type {AccountListEntry} */
this.gcpAccount = null;
this.outlineVersion = '';
this.currentPage = 'intro';
this.shouldShowSideBar = false;
Expand Down Expand Up @@ -761,7 +772,7 @@ export class AppRoot extends mixinBehaviors
}

_hasManualServers(serverList) {
return serverList.filter(server => !server.isManaged).length > 0;
return serverList.filter(server => !server.accountId).length > 0;
}

_userAcceptedTosChanged(userAcceptedTos) {
Expand Down Expand Up @@ -896,12 +907,15 @@ export class AppRoot extends mixinBehaviors
return shouldShowSideBar ? 'side-bar-margin' : '';
}

_isServerManaged(server) {
return server.isManaged;
/**
* @param {AccountListEntry} account
*/
_accountServerFilter(account) {
return (server) => account && server.accountId === account.id;
}

_isServerManual(server) {
return !server.isManaged;
return !server.accountId;
}

_sortServersByName(a, b) {
Expand Down

0 comments on commit ac0d26a

Please sign in to comment.