mirror of
https://github.com/OutlineFoundation/outline-server.git
synced 2026-08-27 03:42:01 +00:00
commit
ac0d26a813
9 changed files with 71 additions and 42 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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<void>;
|
||||
// Returns the virtual host ID.
|
||||
getHostId(): string;
|
||||
}
|
||||
|
||||
export class DataAmount {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|||
<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>
|
||||
|
|
@ -446,20 +453,20 @@ 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>
|
||||
|
|
@ -467,6 +474,7 @@ export class AppRoot extends mixinBehaviors
|
|||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- TODO(fortuna): Insert GCP servers here -->
|
||||
<!-- Manual servers -->
|
||||
<div class="servers-section" hidden\$="[[!_hasManualServers(serverList)]]">
|
||||
<div class="servers-header">
|
||||
|
|
@ -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">
|
||||
|
|
@ -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,
|
||||
|
|
@ -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;
|
||||
|
|
@ -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) {
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue