Merge pull request #859 from Jigsaw-Code/fortuna-fix

Fix server removal
This commit is contained in:
Vinicius Fortuna 2021-03-25 16:52:49 +00:00 committed by GitHub
commit 4b2cfbc573
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 123 additions and 77 deletions

View file

@ -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');
});
});

View file

@ -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<void> {
// 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<void> {
// 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<void> {
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<boolean> {
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<boolean> {
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;

View file

@ -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
<outline-gcp-oauth-step id="gcpOauth" localize="[[localize]]"></outline-gcp-oauth-step>
<outline-manual-server-entry id="manualEntry" localize="[[localize]]"></outline-manual-server-entry>
<outline-region-picker-step id="regionPicker" localize="[[localize]]"></outline-region-picker-step>
<div id="serverView">
<template is="dom-repeat" items="{{serverList}}" as="server">
<outline-server-view id="serverView-{{_base64Encode(server.id)}}" language="[[language]]" localize="[[localize]]" hidden\$="{{!_isServerSelected(selectedServerId, server)}}"></outline-server-view>
</template>
<outline-server-list id="serverView" server-list="[[serverList]]" selected-server-id="[[selectedServerId]]" language="[[language]]" localize="[[localize]]"></outline-server-list>
</div>
</iron-pages>
</div>
@ -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<ServerView>}
*/
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);

View file

@ -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`<div>${repeat(this.serverList, e => e.id, e => html`
<outline-server-view
.id="${this.makeViewId(e.id)}"
.language="${this.language}"
.localize="${this.localize}"
?hidden="${e.id !== this.selectedServerId}">
</outline-server-view>
`)}</div>`;
}
async getServerView(serverId: string): Promise<ServerView> {
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<ServerView>(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, '')}`;
}
}

View file

@ -493,7 +493,7 @@ export class ServerView extends DirMixin(PolymerElement) {
<img class="digital-ocean-icon" src="images/do_white_logo.svg">
</div>
<div class="stats">
<h3>[[managedServerUtilzationPercentage]]</h3>
<h3>[[_computeManagedServerUtilzationPercentage(totalInboundBytes, monthlyOutboundTransferBytes)]]</h3>
<p>/[[_formatBytesTransferred(monthlyOutboundTransferBytes, language)]]</p>
</div>
<p>[[localize('server-data-used')]]</p>
@ -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,
};
}