From 8e64f0ffa1e31e83d60492fea8da2c50a9f4d46e Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Thu, 25 Mar 2021 03:18:35 -0400 Subject: [PATCH 1/4] Fix server removal --- src/server_manager/web_app/app.ts | 34 +++++----- .../web_app/ui_components/app-root.js | 25 ++------ .../ui_components/outline-server-list.ts | 62 +++++++++++++++++++ .../ui_components/outline-server-view.js | 37 +++++------ 4 files changed, 101 insertions(+), 57 deletions(-) create mode 100644 src/server_manager/web_app/ui_components/outline-server-list.ts diff --git a/src/server_manager/web_app/app.ts b/src/server_manager/web_app/app.ts index 669ea573..00fc478f 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'; } @@ -876,9 +876,10 @@ export class App { private addAccessKey() { this.selectedServer.addAccessKey() - .then((serverAccessKey: server.AccessKey) => { + .then(async (serverAccessKey: server.AccessKey) => { const uiAccessKey = this.convertToUiAccessKey(serverAccessKey); - this.appRoot.getServerView(this.appRoot.selectedServerId).addAccessKey(uiAccessKey); + (await this.appRoot.getServerView(this.appRoot.selectedServerId)) + .addAccessKey(uiAccessKey); this.appRoot.showNotification(this.appRoot.localize('notification-key-added')); }) .catch((error) => { @@ -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); @@ -1062,8 +1063,9 @@ export class App { private removeAccessKey(accessKeyId: string) { this.selectedServer.removeAccessKey(accessKeyId) - .then(() => { - this.appRoot.getServerView(this.appRoot.selectedServerId).removeAccessKey(accessKeyId); + .then(async () => { + (await this.appRoot.getServerView(this.appRoot.selectedServerId)) + .removeAccessKey(accessKeyId); this.appRoot.showNotification(this.appRoot.localize('notification-key-removed')); }) .catch((error) => { @@ -1124,7 +1126,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 +1142,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..785e8fb9 --- /dev/null +++ b/src/server_manager/web_app/ui_components/outline-server-list.ts @@ -0,0 +1,62 @@ +// 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; + return this.shadowRoot.querySelector(`#${this.makeViewId(serverId)}`); + } + + // 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, }; } From fa7acacc3611e935f6d11c118dc830fe2a1c6e9d Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Thu, 25 Mar 2021 03:41:18 -0400 Subject: [PATCH 2/4] Fix app.spec.ts --- src/server_manager/web_app/app.spec.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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'); }); }); From a7bec09ffa7da423688e127d542e658f58d48e65 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Thu, 25 Mar 2021 11:40:21 -0400 Subject: [PATCH 3/4] Minor cleanup --- .../web_app/ui_components/outline-server-list.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 index 785e8fb9..ac50ecf9 100644 --- a/src/server_manager/web_app/ui_components/outline-server-list.ts +++ b/src/server_manager/web_app/ui_components/outline-server-list.ts @@ -50,7 +50,8 @@ export class OutlineServerList extends LitElement { } // We need to wait updates to be completed or the view may not yet be there. await this.updateComplete; - return this.shadowRoot.querySelector(`#${this.makeViewId(serverId)}`); + 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 From 3bee7cdd0261f2a13c3a7aaa2d210412ee41f952 Mon Sep 17 00:00:00 2001 From: Vinicius Fortuna Date: Thu, 25 Mar 2021 11:55:04 -0400 Subject: [PATCH 4/4] make methods async --- src/server_manager/web_app/app.ts | 45 +++++++++++++++---------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/server_manager/web_app/app.ts b/src/server_manager/web_app/app.ts index 00fc478f..fe081a2d 100644 --- a/src/server_manager/web_app/app.ts +++ b/src/server_manager/web_app/app.ts @@ -874,18 +874,18 @@ export class App { }; } - private addAccessKey() { - this.selectedServer.addAccessKey() - .then(async (serverAccessKey: server.AccessKey) => { - const uiAccessKey = this.convertToUiAccessKey(serverAccessKey); - (await 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) { @@ -1061,17 +1061,16 @@ export class App { } } - private removeAccessKey(accessKeyId: string) { - this.selectedServer.removeAccessKey(accessKeyId) - .then(async () => { - (await 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) {