diff --git a/src/server_manager/web_app/app.spec.ts b/src/server_manager/web_app/app.spec.ts index 611147c4..f9b2ea43 100644 --- a/src/server_manager/web_app/app.spec.ts +++ b/src/server_manager/web_app/app.spec.ts @@ -80,7 +80,7 @@ describe('App', () => { expect(managedServers.length).toEqual(1); const manualServers = await manualServerRepo.listServers(); expect(manualServers.length).toEqual(2); - appRoot.getServerView(''); + await appRoot.getServerView(''); const serverList = appRoot.serverList; console.log(`managedServers.length: ${managedServers.length}`); @@ -114,7 +114,8 @@ describe('App', () => { await app.start(); await app.createDigitalOceanServer('fakeRegion'); expect(appRoot.currentPage).toEqual('serverView'); - expect(appRoot.getServerView(appRoot.selectedServerId).selectedPage).toEqual('progressView'); + const view = await appRoot.getServerView(appRoot.selectedServerId); + expect(view.selectedPage).toEqual('progressView'); }); it('shows progress screen when starting with DigitalOcean servers still being created', @@ -128,7 +129,8 @@ describe('App', () => { localStorage.setItem(LAST_DISPLAYED_SERVER_STORAGE_KEY, server.getId()); await app.start(); expect(appRoot.currentPage).toEqual('serverView'); - expect(appRoot.getServerView(appRoot.selectedServerId).selectedPage).toEqual('progressView'); + const view = await appRoot.getServerView(appRoot.selectedServerId); + expect(view.selectedPage).toEqual('progressView'); }); }); diff --git a/src/server_manager/web_app/app.ts b/src/server_manager/web_app/app.ts index 669ea573..fe081a2d 100644 --- a/src/server_manager/web_app/app.ts +++ b/src/server_manager/web_app/app.ts @@ -699,9 +699,9 @@ export class App { } // Show the server management screen. Assumes the server is healthy. - private setServerManagementView(server: server.Server): void { + private async setServerManagementView(server: server.Server): Promise { // Show view and initialize fields from selectedServer. - const view = this.appRoot.getServerView(server.getId()); + const view = await this.appRoot.getServerView(server.getId()); const version = server.getVersion(); view.selectedPage = 'managementView'; view.serverId = server.getId(); @@ -760,9 +760,9 @@ export class App { }, 0); } - private setServerUnreachableView(server: server.Server): void { + private async setServerUnreachableView(server: server.Server): Promise { // Display the unreachable server state within the server view. - const serverView = this.appRoot.getServerView(server.getId()); + const serverView = await this.appRoot.getServerView(server.getId()); serverView.selectedPage = 'unreachableView'; serverView.isServerManaged = isManagedServer(server); serverView.serverName = @@ -772,8 +772,8 @@ export class App { }; } - private setServerProgressView(server: server.Server): void { - const view = this.appRoot.getServerView(server.getId()); + private async setServerProgressView(server: server.Server): Promise { + const view = await this.appRoot.getServerView(server.getId()); view.serverName = this.makeDisplayName(server); view.selectedPage = 'progressView'; } @@ -874,17 +874,18 @@ export class App { }; } - private addAccessKey() { - this.selectedServer.addAccessKey() - .then((serverAccessKey: server.AccessKey) => { - const uiAccessKey = this.convertToUiAccessKey(serverAccessKey); - this.appRoot.getServerView(this.appRoot.selectedServerId).addAccessKey(uiAccessKey); - this.appRoot.showNotification(this.appRoot.localize('notification-key-added')); - }) - .catch((error) => { - console.error(`Failed to add access key: ${error}`); - this.appRoot.showError(this.appRoot.localize('error-key-add')); - }); + private async addAccessKey() { + const server = this.selectedServer; + try { + const serverAccessKey = await server.addAccessKey(); + const uiAccessKey = this.convertToUiAccessKey(serverAccessKey); + const serverView = await this.appRoot.getServerView(server.getId()); + serverView.addAccessKey(uiAccessKey); + this.appRoot.showNotification(this.appRoot.localize('notification-key-added')); + } catch (error) { + console.error(`Failed to add access key: ${error}`); + this.appRoot.showError(this.appRoot.localize('error-key-add')); + } } private renameAccessKey(accessKeyId: string, newName: string, entry: polymer.Base) { @@ -907,7 +908,7 @@ export class App { if (previousLimit && limit.bytes === previousLimit.bytes) { return; } - const serverView = this.appRoot.getServerView(this.appRoot.selectedServerId); + const serverView = await this.appRoot.getServerView(this.appRoot.selectedServerId); try { await this.selectedServer.setDefaultDataLimit(limit); this.appRoot.showNotification(this.appRoot.localize('saved')); @@ -927,7 +928,7 @@ export class App { } private async removeDefaultDataLimit() { - const serverView = this.appRoot.getServerView(this.appRoot.selectedServerId); + const serverView = await this.appRoot.getServerView(this.appRoot.selectedServerId); const previousLimit = this.selectedServer.getDefaultDataLimit(); try { await this.selectedServer.removeDefaultDataLimit(); @@ -960,7 +961,7 @@ export class App { Promise { this.appRoot.showNotification(this.appRoot.localize('saving')); const server = this.idServerMap.get(serverId); - const serverView = this.appRoot.getServerView(server.getId()); + const serverView = await this.appRoot.getServerView(server.getId()); try { await server.setAccessKeyDataLimit(keyId, {bytes: dataLimitBytes}); this.refreshTransferStats(server, serverView); @@ -976,7 +977,7 @@ export class App { private async removePerKeyDataLimit(serverId: string, keyId: string): Promise { this.appRoot.showNotification(this.appRoot.localize('saving')); const server = this.idServerMap.get(serverId); - const serverView = this.appRoot.getServerView(server.getId()); + const serverView = await this.appRoot.getServerView(server.getId()); try { await server.removeAccessKeyDataLimit(keyId); this.refreshTransferStats(server, serverView); @@ -1060,16 +1061,16 @@ export class App { } } - private removeAccessKey(accessKeyId: string) { - this.selectedServer.removeAccessKey(accessKeyId) - .then(() => { - this.appRoot.getServerView(this.appRoot.selectedServerId).removeAccessKey(accessKeyId); - this.appRoot.showNotification(this.appRoot.localize('notification-key-removed')); - }) - .catch((error) => { - console.error(`Failed to remove access key: ${error}`); - this.appRoot.showError(this.appRoot.localize('error-key-remove')); - }); + private async removeAccessKey(accessKeyId: string) { + const server = this.selectedServer; + try { + await server.removeAccessKey(accessKeyId); + (await this.appRoot.getServerView(server.getId())).removeAccessKey(accessKeyId); + this.appRoot.showNotification(this.appRoot.localize('notification-key-removed')); + } catch (error) { + console.error(`Failed to remove access key: ${error}`); + this.appRoot.showError(this.appRoot.localize('error-key-remove')); + } } private deleteServer(serverId: string) { @@ -1124,7 +1125,7 @@ export class App { } private async setMetricsEnabled(metricsEnabled: boolean) { - const serverView = this.appRoot.getServerView(this.appRoot.selectedServerId); + const serverView = await this.appRoot.getServerView(this.appRoot.selectedServerId); try { await this.selectedServer.setMetricsEnabled(metricsEnabled); this.appRoot.showNotification(this.appRoot.localize('saved')); @@ -1140,7 +1141,7 @@ export class App { private async renameServer(newName: string) { const serverToRename = this.selectedServer; const serverId = this.appRoot.selectedServerId; - const view = this.appRoot.getServerView(serverId); + const view = await this.appRoot.getServerView(serverId); try { await serverToRename.setName(newName); view.serverName = newName; 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 ed18d9cb..53be3a9f 100644 --- a/src/server_manager/web_app/ui_components/app-root.js +++ b/src/server_manager/web_app/ui_components/app-root.js @@ -38,6 +38,7 @@ import './outline-language-picker.js'; import './outline-manual-server-entry.js'; import './outline-modal-dialog.js'; import './outline-region-picker-step'; +import './outline-server-list'; import './outline-tos-view.js'; import {AppLocalizeBehavior} from '@polymer/app-localize-behavior/app-localize-behavior.js'; @@ -386,10 +387,7 @@ export class AppRoot extends mixinBehaviors -
- +
@@ -658,16 +656,10 @@ export class AppRoot extends mixinBehaviors /** * Gets the ServerView for the server given by its id * @param {string} displayServerId - * @returns {ServerView} + * @returns {Promise} */ - getServerView(displayServerId) { - if (!displayServerId) { - return null; - } - // Render to ensure that the server view has been added to the DOM. - this.$.serverView.querySelector('dom-repeat').render(); - const selectedServerId = this._base64Encode(displayServerId); - return this.$.serverView.querySelector(`#serverView-${selectedServerId}`); + async getServerView(displayServerId) { + return await this.shadowRoot.querySelector('#serverView').getServerView(displayServerId); } handleRegionSelected(/** @type {Event} */ e) { @@ -950,12 +942,5 @@ export class AppRoot extends mixinBehaviors this.fire('ShowServerRequested', {displayServerId: server.id}); this.maybeCloseDrawer(); } - - // Wrapper to encode a string in base64. This is necessary to set the server view IDs to - // the display server IDs, which are URLs, so they can be used with selector methods. The IDs - // are never decoded. - _base64Encode(s) { - return btoa(s).replace(/=/g, ''); - } } customElements.define(AppRoot.is, AppRoot); diff --git a/src/server_manager/web_app/ui_components/outline-server-list.ts b/src/server_manager/web_app/ui_components/outline-server-list.ts new file mode 100644 index 00000000..ac50ecf9 --- /dev/null +++ b/src/server_manager/web_app/ui_components/outline-server-list.ts @@ -0,0 +1,63 @@ +// Copyright 2021 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import {customElement, html, LitElement, property} from 'lit-element'; +import {repeat} from 'lit-html/directives/repeat.js'; +import {ServerView} from './outline-server-view.js'; + +export interface ServerListEntry { + id: string; + name: string; + isManaged: boolean; + isSynced: boolean; +} + +@customElement('outline-server-list') +export class OutlineServerList extends LitElement { + @property({type: Array}) serverList: ServerListEntry[]; + @property({type: String}) selectedServerId: string; + @property({type: Function}) localize: Function; + @property({type: String}) language: string; + + render() { + if (!this.serverList) { + return; + } + return html`
${repeat(this.serverList, e => e.id, e => html` + + + `)}
`; + } + + async getServerView(serverId: string): Promise { + if (!serverId) { + return null; + } + // We need to wait updates to be completed or the view may not yet be there. + await this.updateComplete; + const selector = `#${this.makeViewId(serverId)}`; + return this.shadowRoot.querySelector(selector); + } + + // Wrapper to encode a string in base64. This is necessary to set the server view IDs to + // the display server IDs, which are URLs, so they can be used with selector methods. The IDs + // are never decoded. + private makeViewId(serverId: string): string { + return `serverView-${btoa(serverId).replace(/=/g, '')}`; + } +} diff --git a/src/server_manager/web_app/ui_components/outline-server-view.js b/src/server_manager/web_app/ui_components/outline-server-view.js index 9be36b6d..375c96b2 100644 --- a/src/server_manager/web_app/ui_components/outline-server-view.js +++ b/src/server_manager/web_app/ui_components/outline-server-view.js @@ -493,7 +493,7 @@ export class ServerView extends DirMixin(PolymerElement) {
-

[[managedServerUtilzationPercentage]]

+

[[_computeManagedServerUtilzationPercentage(totalInboundBytes, monthlyOutboundTransferBytes)]]

/[[_formatBytesTransferred(monthlyOutboundTransferBytes, language)]]

[[localize('server-data-used')]]

@@ -623,38 +623,33 @@ export class ServerView extends DirMixin(PolymerElement) { serverName: String, serverHostname: String, serverVersion: String, - isHostnameEditable: {type: Boolean}, + isHostnameEditable: Boolean, serverManagementApiUrl: String, serverPortForNewAccessKeys: Number, - isAccessKeyPortEditable: {type: Boolean}, - serverCreationDate: {type: Date}, + isAccessKeyPortEditable: Boolean, + serverCreationDate: Date, serverLocation: String, defaultDataLimitBytes: Number, - isDefaultDataLimitEnabled: {type: Boolean}, - supportsDefaultDataLimit: {type: Boolean}, - showFeatureMetricsDisclaimer: {type: Boolean}, + isDefaultDataLimitEnabled: Boolean, + supportsDefaultDataLimit: Boolean, + showFeatureMetricsDisclaimer: Boolean, isServerManaged: Boolean, isServerReachable: Boolean, retryDisplayingServer: Function, myConnection: Object, totalInboundBytes: Number, baselineDataTransfer: Number, - accessKeyRows: {type: Array}, + accessKeyRows: Array, hasNonAdminAccessKeys: Boolean, metricsEnabled: Boolean, - monthlyOutboundTransferBytes: {type: Number}, - monthlyCost: {type: Number}, - managedServerUtilzationPercentage: { - type: Number, - computed: - '_computeManagedServerUtilzationPercentage(totalInboundBytes, monthlyOutboundTransferBytes)', - }, - accessKeySortBy: {type: String}, - accessKeySortDirection: {type: Number}, - language: {type: String}, - localize: {type: Function, readonly: true}, - selectedPage: {type: String}, - selectedTab: {type: String}, + monthlyOutboundTransferBytes: Number, + monthlyCost: Number, + accessKeySortBy: String, + accessKeySortDirection: Number, + language: String, + localize: Function, + selectedPage: String, + selectedTab: String, }; }