mirror of
https://github.com/OutlineFoundation/outline-server.git
synced 2026-10-23 13:00:57 +00:00
Simplify app
This commit is contained in:
parent
d274d4ba98
commit
8df249535f
8 changed files with 1034 additions and 700 deletions
|
|
@ -19,7 +19,6 @@ import * as server from '../model/server';
|
|||
|
||||
import {App} from './app';
|
||||
import {TokenManager} from './digitalocean_oauth';
|
||||
import {DisplayServerRepository, makeDisplayServer} from './display_server';
|
||||
import {AppRoot} from './ui_components/app-root.js';
|
||||
|
||||
const TOKEN_WITH_NO_SERVERS = 'no-server-token';
|
||||
|
|
@ -69,106 +68,97 @@ describe('App', () => {
|
|||
expect(appRoot.currentPage).toEqual('serverView');
|
||||
});
|
||||
|
||||
it('initially shows and stores server display metadata', async (done) => {
|
||||
// Create fake servers and simulate their metadata being cached before creating the app.
|
||||
const tokenManager = new InMemoryDigitalOceanTokenManager();
|
||||
tokenManager.token = TOKEN_WITH_NO_SERVERS;
|
||||
const managedServerRepo = new FakeManagedServerRepository();
|
||||
const managedServer = await managedServerRepo.createServer();
|
||||
managedServer.apiUrl = 'fake-managed-server-api-url';
|
||||
const managedDisplayServer = await makeDisplayServer(managedServer);
|
||||
const manualServerRepo = new FakeManualServerRepository();
|
||||
const manualServer1 = await manualServerRepo.addServer(
|
||||
{certSha256: 'cert', apiUrl: 'fake-manual-server-api-url-1'});
|
||||
const manualServer2 = await manualServerRepo.addServer(
|
||||
{certSha256: 'cert', apiUrl: 'fake-manual-server-api-url-2'});
|
||||
const manualDisplayServer1 = await makeDisplayServer(manualServer1);
|
||||
const manualDisplayServer2 = await makeDisplayServer(manualServer2);
|
||||
// it('initially shows and stores server display metadata', async (done) => {
|
||||
// // Create fake servers and simulate their metadata being cached before creating the app.
|
||||
// const tokenManager = new InMemoryDigitalOceanTokenManager();
|
||||
// tokenManager.token = TOKEN_WITH_NO_SERVERS;
|
||||
// const managedServerRepo = new FakeManagedServerRepository();
|
||||
// const managedServer = await managedServerRepo.createServer();
|
||||
// managedServer.apiUrl = 'fake-managed-server-api-url';
|
||||
// const manualServerRepo = new FakeManualServerRepository();
|
||||
// const manualServer1 = await manualServerRepo.addServer(
|
||||
// {certSha256: 'cert', apiUrl: 'fake-manual-server-api-url-1'});
|
||||
// const manualServer2 = await manualServerRepo.addServer(
|
||||
// {certSha256: 'cert', apiUrl: 'fake-manual-server-api-url-2'});
|
||||
|
||||
const displayServerRepo = new DisplayServerRepository(new InMemoryStorage());
|
||||
const appRoot = document.getElementById('appRoot') as unknown as AppRoot;
|
||||
const app = createTestApp(
|
||||
appRoot, tokenManager, manualServerRepo, displayServerRepo, managedServerRepo);
|
||||
// const appRoot = document.getElementById('appRoot') as unknown as AppRoot;
|
||||
// const app = createTestApp(
|
||||
// appRoot, tokenManager, manualServerRepo, managedServerRepo);
|
||||
|
||||
await app.start();
|
||||
// Validate that server metadata is shown.
|
||||
const managedServers = await managedServerRepo.listServers();
|
||||
const manualServers = await manualServerRepo.listServers();
|
||||
const serverList = appRoot.serverList;
|
||||
expect(serverList.length).toEqual(manualServers.length + managedServers.length);
|
||||
expect(serverList).toContain(manualDisplayServer1);
|
||||
expect(serverList).toContain(manualDisplayServer2);
|
||||
expect(serverList).toContain(managedDisplayServer);
|
||||
// await app.start();
|
||||
// // Validate that server metadata is shown.
|
||||
// const managedServers = await managedServerRepo.listServers();
|
||||
// const manualServers = await manualServerRepo.listServers();
|
||||
// const serverList = appRoot.serverList;
|
||||
// expect(serverList.length).toEqual(manualServers.length + managedServers.length);
|
||||
// expect(serverList).toContain(manualDisplayServer1);
|
||||
// expect(serverList).toContain(manualDisplayServer2);
|
||||
// expect(serverList).toContain(managedDisplayServer);
|
||||
|
||||
// Validate that display servers are stored.
|
||||
const displayServers = await displayServerRepo.listServers();
|
||||
for (const displayServer of displayServers) {
|
||||
expect(serverList).toContain(displayServer);
|
||||
}
|
||||
done();
|
||||
});
|
||||
// done();
|
||||
// });
|
||||
|
||||
it('initially shows stored server display metadata', async (done) => {
|
||||
// Create fake servers without caching their display metadata.
|
||||
const tokenManager = new InMemoryDigitalOceanTokenManager();
|
||||
tokenManager.token = TOKEN_WITH_NO_SERVERS;
|
||||
const managedServerRepo = new FakeManagedServerRepository();
|
||||
const managedServer = await managedServerRepo.createServer();
|
||||
managedServer.apiUrl = 'fake-managed-server-api-url';
|
||||
const managedDisplayServer = await makeDisplayServer(managedServer);
|
||||
const manualServerRepo = new FakeManualServerRepository();
|
||||
const manualServer1 = await manualServerRepo.addServer(
|
||||
{certSha256: 'cert', apiUrl: 'fake-manual-server-api-url-1'});
|
||||
const manualServer2 = await manualServerRepo.addServer(
|
||||
{certSha256: 'cert', apiUrl: 'fake-manual-server-api-url-2'});
|
||||
const manualDisplayServer1 = await makeDisplayServer(manualServer1);
|
||||
const manualDisplayServer2 = await makeDisplayServer(manualServer2);
|
||||
const store = new Map([[
|
||||
DisplayServerRepository.SERVERS_STORAGE_KEY,
|
||||
JSON.stringify([manualDisplayServer1, manualDisplayServer2, managedDisplayServer])
|
||||
]]);
|
||||
const displayServerRepo = new DisplayServerRepository(new InMemoryStorage(store));
|
||||
const appRoot = document.getElementById('appRoot') as unknown as AppRoot;
|
||||
const app = createTestApp(
|
||||
appRoot, tokenManager, manualServerRepo, displayServerRepo, managedServerRepo);
|
||||
// it('initially shows stored server display metadata', async (done) => {
|
||||
// // Create fake servers without caching their display metadata.
|
||||
// const tokenManager = new InMemoryDigitalOceanTokenManager();
|
||||
// tokenManager.token = TOKEN_WITH_NO_SERVERS;
|
||||
// const managedServerRepo = new FakeManagedServerRepository();
|
||||
// const managedServer = await managedServerRepo.createServer();
|
||||
// managedServer.apiUrl = 'fake-managed-server-api-url';
|
||||
// const managedDisplayServer = await makeDisplayServer(managedServer);
|
||||
// const manualServerRepo = new FakeManualServerRepository();
|
||||
// const manualServer1 = await manualServerRepo.addServer(
|
||||
// {certSha256: 'cert', apiUrl: 'fake-manual-server-api-url-1'});
|
||||
// const manualServer2 = await manualServerRepo.addServer(
|
||||
// {certSha256: 'cert', apiUrl: 'fake-manual-server-api-url-2'});
|
||||
// const manualDisplayServer1 = await makeDisplayServer(manualServer1);
|
||||
// const manualDisplayServer2 = await makeDisplayServer(manualServer2);
|
||||
// const store = new Map([[
|
||||
// DisplayServerRepository.SERVERS_STORAGE_KEY,
|
||||
// JSON.stringify([manualDisplayServer1, manualDisplayServer2, managedDisplayServer])
|
||||
// ]]);
|
||||
// const displayServerRepo = new DisplayServerRepository(new InMemoryStorage(store));
|
||||
// const appRoot = document.getElementById('appRoot') as unknown as AppRoot;
|
||||
// const app = createTestApp(
|
||||
// appRoot, tokenManager, manualServerRepo, displayServerRepo, managedServerRepo);
|
||||
|
||||
await app.start();
|
||||
const managedServers = await managedServerRepo.listServers();
|
||||
const manualServers = await manualServerRepo.listServers();
|
||||
const serverList = appRoot.serverList;
|
||||
expect(serverList.length).toEqual(manualServers.length + managedServers.length);
|
||||
expect(serverList).toContain(manualDisplayServer1);
|
||||
expect(serverList).toContain(manualDisplayServer2);
|
||||
expect(serverList).toContain(managedDisplayServer);
|
||||
done();
|
||||
});
|
||||
// await app.start();
|
||||
// const managedServers = await managedServerRepo.listServers();
|
||||
// const manualServers = await manualServerRepo.listServers();
|
||||
// const serverList = appRoot.serverList;
|
||||
// expect(serverList.length).toEqual(manualServers.length + managedServers.length);
|
||||
// expect(serverList).toContain(manualDisplayServer1);
|
||||
// expect(serverList).toContain(manualDisplayServer2);
|
||||
// expect(serverList).toContain(managedDisplayServer);
|
||||
// done();
|
||||
// });
|
||||
|
||||
it('initially shows the last selected server', async () => {
|
||||
const tokenManager = new InMemoryDigitalOceanTokenManager();
|
||||
tokenManager.token = TOKEN_WITH_ONE_SERVER;
|
||||
// it('initially shows the last selected server', async () => {
|
||||
// const tokenManager = new InMemoryDigitalOceanTokenManager();
|
||||
// tokenManager.token = TOKEN_WITH_ONE_SERVER;
|
||||
|
||||
const LAST_DISPLAYED_SERVER_ID = 'fake-manual-server-api-url-1';
|
||||
const manualServerRepo = new FakeManualServerRepository();
|
||||
const lastDisplayedServer =
|
||||
await manualServerRepo.addServer({certSha256: 'cert', apiUrl: LAST_DISPLAYED_SERVER_ID});
|
||||
const manualServer = await manualServerRepo.addServer(
|
||||
{certSha256: 'cert', apiUrl: 'fake-manual-server-api-url-2'});
|
||||
const manualDisplayServer1 = await makeDisplayServer(lastDisplayedServer);
|
||||
const manualDisplayServer2 = await makeDisplayServer(manualServer);
|
||||
const store = new Map([[
|
||||
DisplayServerRepository.SERVERS_STORAGE_KEY,
|
||||
JSON.stringify([manualDisplayServer1, manualDisplayServer2])
|
||||
]]);
|
||||
const displayServerRepo = new DisplayServerRepository(new InMemoryStorage(store));
|
||||
displayServerRepo.storeLastDisplayedServerId(LAST_DISPLAYED_SERVER_ID);
|
||||
// const LAST_DISPLAYED_SERVER_ID = 'fake-manual-server-api-url-1';
|
||||
// const manualServerRepo = new FakeManualServerRepository();
|
||||
// const lastDisplayedServer =
|
||||
// await manualServerRepo.addServer({certSha256: 'cert', apiUrl: LAST_DISPLAYED_SERVER_ID});
|
||||
// const manualServer = await manualServerRepo.addServer(
|
||||
// {certSha256: 'cert', apiUrl: 'fake-manual-server-api-url-2'});
|
||||
// const manualDisplayServer1 = await makeDisplayServer(lastDisplayedServer);
|
||||
// const manualDisplayServer2 = await makeDisplayServer(manualServer);
|
||||
// const store = new Map([[
|
||||
// DisplayServerRepository.SERVERS_STORAGE_KEY,
|
||||
// JSON.stringify([manualDisplayServer1, manualDisplayServer2])
|
||||
// ]]);
|
||||
// const displayServerRepo = new DisplayServerRepository(new InMemoryStorage(store));
|
||||
// displayServerRepo.storeLastDisplayedServerId(LAST_DISPLAYED_SERVER_ID);
|
||||
|
||||
const appRoot = document.getElementById('appRoot') as unknown as AppRoot;
|
||||
const app = createTestApp(appRoot, tokenManager, manualServerRepo, displayServerRepo);
|
||||
await app.start();
|
||||
await sleep(2000); // TODO: refactor test to remove
|
||||
expect(appRoot.currentPage).toEqual('serverView');
|
||||
expect(appRoot.selectedServer.id).toEqual(lastDisplayedServer.getManagementApiUrl());
|
||||
});
|
||||
// const appRoot = document.getElementById('appRoot') as unknown as AppRoot;
|
||||
// const app = createTestApp(appRoot, tokenManager, manualServerRepo, displayServerRepo);
|
||||
// await app.start();
|
||||
// await sleep(2000); // TODO: refactor test to remove
|
||||
// expect(appRoot.currentPage).toEqual('serverView');
|
||||
// expect(appRoot.selectedServer.id).toEqual(lastDisplayedServer.getManagementApiUrl());
|
||||
// });
|
||||
|
||||
it('shows progress screen once DigitalOcean droplets are created', async () => {
|
||||
// Start the app with a fake DigitalOcean token.
|
||||
|
|
@ -189,7 +179,7 @@ describe('App', () => {
|
|||
const managedSeverRepository = new FakeManagedServerRepository();
|
||||
// Manually create the server since the DO repository server factory function is synchronous.
|
||||
await managedSeverRepository.createUninstalledServer();
|
||||
const app = createTestApp(appRoot, tokenManager, null, null, managedSeverRepository);
|
||||
const app = createTestApp(appRoot, tokenManager, null, managedSeverRepository);
|
||||
await app.start();
|
||||
expect(appRoot.currentPage).toEqual('serverProgressStep');
|
||||
});
|
||||
|
|
@ -198,7 +188,6 @@ describe('App', () => {
|
|||
function createTestApp(
|
||||
appRoot: AppRoot, digitalOceanTokenManager: InMemoryDigitalOceanTokenManager,
|
||||
manualServerRepo?: server.ManualServerRepository,
|
||||
displayServerRepository?: FakeDisplayServerRepository,
|
||||
managedServerRepository?: FakeManagedServerRepository) {
|
||||
const VERSION = '0.0.1';
|
||||
const fakeDigitalOceanSessionFactory = (accessToken: string) => {
|
||||
|
|
@ -215,12 +204,9 @@ function createTestApp(
|
|||
if (!manualServerRepo) {
|
||||
manualServerRepo = new FakeManualServerRepository();
|
||||
}
|
||||
if (!displayServerRepository) {
|
||||
displayServerRepository = new FakeDisplayServerRepository();
|
||||
}
|
||||
return new App(
|
||||
appRoot, VERSION, fakeDigitalOceanSessionFactory, fakeDigitalOceanServerRepositoryFactory,
|
||||
manualServerRepo, displayServerRepository, digitalOceanTokenManager);
|
||||
manualServerRepo, digitalOceanTokenManager);
|
||||
}
|
||||
|
||||
class FakeServer implements server.Server {
|
||||
|
|
@ -414,9 +400,3 @@ class FakeManagedServerRepository implements server.ManagedServerRepository {
|
|||
return Promise.resolve(newServer);
|
||||
}
|
||||
}
|
||||
|
||||
class FakeDisplayServerRepository extends DisplayServerRepository {
|
||||
constructor() {
|
||||
super(new InMemoryStorage());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import * as server from '../model/server';
|
|||
|
||||
import {TokenManager} from './digitalocean_oauth';
|
||||
import * as digitalocean_server from './digitalocean_server';
|
||||
import {DisplayServer, DisplayServerRepository, makeDisplayServer} from './display_server';
|
||||
import {DisplayServer} from './display_server';
|
||||
import {parseManualServerConfig} from './management_urls';
|
||||
import {AppRoot} from './ui_components/app-root.js';
|
||||
import {Location} from './ui_components/outline-region-picker-step';
|
||||
|
|
@ -37,6 +37,7 @@ const CHANGE_KEYS_PORT_VERSION = '1.0.0';
|
|||
const DATA_LIMITS_VERSION = '1.1.0';
|
||||
const CHANGE_HOSTNAME_VERSION = '1.2.0';
|
||||
const MAX_ACCESS_KEY_DATA_LIMIT_BYTES = 50 * (10 ** 9); // 50GB
|
||||
const LAST_DISPLAYED_SERVER_STORAGE_KEY = 'lastDisplayedServer';
|
||||
|
||||
// DigitalOcean mapping of regions to flags
|
||||
const FLAG_IMAGE_DIR = 'images/flags';
|
||||
|
|
@ -74,6 +75,14 @@ function displayDataAmountToDataLimit(dataAmount: DisplayDataAmount): server.Dat
|
|||
return {bytes: dataAmount.value};
|
||||
}
|
||||
|
||||
function localId(server: server.Server): string {
|
||||
if (isManagedServer(server)) {
|
||||
return (server as server.ManagedServer).getHost().getHostId();
|
||||
} else {
|
||||
return server.getManagementApiUrl();
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the suggested data limit based on the server's transfer capacity and number of access
|
||||
// keys.
|
||||
async function computeDefaultAccessKeyDataLimit(
|
||||
|
|
@ -140,19 +149,22 @@ type DigitalOceanServerRepositoryFactory = (session: digitalocean_api.DigitalOce
|
|||
export class App {
|
||||
private digitalOceanRepository: server.ManagedServerRepository;
|
||||
private selectedServer: server.Server;
|
||||
private serverBeingCreated: server.ManagedServer;
|
||||
private idServer = new Map<string, server.Server>();
|
||||
|
||||
constructor(
|
||||
private appRoot: AppRoot, private readonly version: string,
|
||||
private createDigitalOceanSession: DigitalOceanSessionFactory,
|
||||
private createDigitalOceanServerRepository: DigitalOceanServerRepositoryFactory,
|
||||
private manualServerRepository: server.ManualServerRepository,
|
||||
private displayServerRepository: DisplayServerRepository,
|
||||
private digitalOceanTokenManager: TokenManager) {
|
||||
appRoot.setAttribute('outline-version', this.version);
|
||||
|
||||
appRoot.addEventListener('ConnectToDigitalOcean', (event: CustomEvent) => {
|
||||
this.connectToDigitalOcean();
|
||||
appRoot.addEventListener('StartDigitalOceanConnectAccountRequested', (event: CustomEvent) => {
|
||||
this.startDigitalOceanConnectAccount();
|
||||
// this.connectToDigitalOcean();
|
||||
});
|
||||
appRoot.addEventListener('StartDigitalOceanCreateServerRequested', (event: CustomEvent) => {
|
||||
this.startDigitalOceanCreateServer();
|
||||
});
|
||||
appRoot.addEventListener('SignOutRequested', (event: CustomEvent) => {
|
||||
this.clearCredentialsAndShowIntro();
|
||||
|
|
@ -289,8 +301,14 @@ export class App {
|
|||
this.appRoot.openGetConnectedDialog(this.getS3InviteUrl(event.detail.accessKey, true));
|
||||
});
|
||||
|
||||
appRoot.addEventListener('ShowServerRequested', (event: CustomEvent) => {
|
||||
this.handleShowServerRequested(event.detail.displayServerId);
|
||||
appRoot.addEventListener('ShowServerRequested', async (event: CustomEvent) => {
|
||||
const server = this.getServerById(event.detail.displayServerId);
|
||||
if (server) {
|
||||
await this.showServer(server);
|
||||
} else {
|
||||
console.error(
|
||||
`Could not find server for display server ID ${event.detail.displayServerId}`);
|
||||
}
|
||||
});
|
||||
|
||||
onUpdateDownloaded(this.displayAppUpdateNotification.bind(this));
|
||||
|
|
@ -298,189 +316,228 @@ export class App {
|
|||
|
||||
async start(): Promise<void> {
|
||||
this.showIntro();
|
||||
await this.syncDisplayServersToUi();
|
||||
|
||||
const manualServersPromise = this.manualServerRepository.listServers();
|
||||
// Load server list. Fetch manual and managed servers in parallel.
|
||||
await Promise.all([this.loadDigitalOceanAccount(), this.loadManualServers()]);
|
||||
|
||||
// Show last displayed server, if any.
|
||||
const serverIdToSelect = localStorage.getItem(LAST_DISPLAYED_SERVER_STORAGE_KEY);
|
||||
if (serverIdToSelect) {
|
||||
this.showServer(this.getServerById(serverIdToSelect));
|
||||
}
|
||||
}
|
||||
|
||||
private async loadDigitalOceanAccount() {
|
||||
const accessToken = this.digitalOceanTokenManager.getStoredToken();
|
||||
const managedServersPromise = !!accessToken ?
|
||||
this.enterDigitalOceanMode(accessToken).catch(e => [] as server.ManagedServer[]) :
|
||||
Promise.resolve([]);
|
||||
|
||||
const managedServers = await managedServersPromise;
|
||||
const serverBeingCreated = managedServers.find(server => !server.isInstallCompleted());
|
||||
if (!!serverBeingCreated) {
|
||||
this.syncServerCreationToUi(serverBeingCreated);
|
||||
if (!accessToken) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const doSession = this.createDigitalOceanSession(accessToken);
|
||||
const doAccount = await doSession.getAccount();
|
||||
this.appRoot.adminEmail = doAccount.email;
|
||||
this.digitalOceanRepository = this.createDigitalOceanServerRepository(doSession);
|
||||
for (const server of await this.digitalOceanRepository.listServers()) {
|
||||
this.addServer(server);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load DigitalOcean Account:', error);
|
||||
}
|
||||
|
||||
const installedManagedServers = managedServers.filter(server => server.isInstallCompleted());
|
||||
const manualServers = await manualServersPromise;
|
||||
await this.syncServersToDisplay(manualServers.concat(installedManagedServers));
|
||||
|
||||
await this.maybeShowLastDisplayedServer();
|
||||
}
|
||||
|
||||
private async syncServersToDisplay(servers: server.Server[]) {
|
||||
for (const server of servers) {
|
||||
await this.syncServerToDisplay(server);
|
||||
private async loadManualServers() {
|
||||
for (const server of await this.manualServerRepository.listServers()) {
|
||||
this.addServer(server);
|
||||
}
|
||||
|
||||
// Remove any unsynced servers from display and alert the user.
|
||||
const displayServers = await this.displayServerRepository.listServers();
|
||||
const unsyncedServers = displayServers.filter(s => !s.isSynced);
|
||||
if (unsyncedServers.length > 0) {
|
||||
for (const displayServer of unsyncedServers) {
|
||||
this.displayServerRepository.removeServer(displayServer);
|
||||
}
|
||||
const unsyncedServerNames = unsyncedServers.map(s => s.name).join(', ');
|
||||
let messageKey = 'error-server-removed';
|
||||
let placeholder = 'serverName';
|
||||
if (unsyncedServers.length > 1) {
|
||||
// Pluralize localized message.
|
||||
messageKey = 'error-servers-removed';
|
||||
placeholder = 'serverNames';
|
||||
}
|
||||
this.appRoot.showError(this.appRoot.localize(messageKey, placeholder, unsyncedServerNames));
|
||||
}
|
||||
|
||||
await this.syncDisplayServersToUi();
|
||||
}
|
||||
|
||||
private makeDisplayServer(server: server.Server): DisplayServer {
|
||||
let name = server.getName() ?? server.getHostnameForAccessKeys();
|
||||
if (!name) {
|
||||
if (isManagedServer(server)) {
|
||||
// Newly created servers will not have a name.
|
||||
name =
|
||||
this.makeLocalizedServerName((server as server.ManagedServer).getHost().getRegionId());
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: localId(server),
|
||||
name,
|
||||
isManaged: isManagedServer(server),
|
||||
isSynced: !!server.getName(),
|
||||
};
|
||||
}
|
||||
|
||||
private addServer(server: server.Server) {
|
||||
const serverId = localId(server);
|
||||
this.idServer.set(serverId, server);
|
||||
const displayServer = this.makeDisplayServer(server);
|
||||
console.log('Added DisplayServer', displayServer);
|
||||
this.appRoot.serverList = this.appRoot.serverList.concat([displayServer]);
|
||||
// Update name and loading status asynchronously.
|
||||
server.isHealthy().then((isHealthy: boolean) => {
|
||||
console.log('Added ', server, 'IsHealthy: ', isHealthy, 'getName(): ', server.getName());
|
||||
if (!isHealthy) {
|
||||
return;
|
||||
}
|
||||
const newDisplayServer = {...displayServer, name: server.getName(), isSynced: true};
|
||||
this.appRoot.serverList =
|
||||
this.appRoot.serverList.map((ds) => ds.id === serverId ? newDisplayServer : ds);
|
||||
});
|
||||
}
|
||||
|
||||
private removeServer(serverId: string) {
|
||||
this.idServer.delete(serverId);
|
||||
this.appRoot.serverList = this.appRoot.serverList.filter((ds) => ds.id !== serverId);
|
||||
if (this.appRoot.selectedServer.id === serverId) {
|
||||
this.appRoot.selectedServer = null;
|
||||
this.selectedServer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private updateServer(server: server.Server) {
|
||||
const serverId = localId(server);
|
||||
this.appRoot.serverList = this.appRoot.serverList.map(
|
||||
(ds) => ds.id === serverId ? this.makeDisplayServer(server) : ds);
|
||||
}
|
||||
|
||||
private getServerById(serverId: string): server.Server {
|
||||
return this.idServer.get(serverId);
|
||||
}
|
||||
|
||||
// private async syncServerList(servers: server.Server[]) {
|
||||
// for (const server of servers) {
|
||||
// await this.syncServerToDisplay(server);
|
||||
// }
|
||||
|
||||
// // Remove any unsynced servers from display and alert the user.
|
||||
// const displayServers = await this.displayServerRepository.listServers();
|
||||
// const unsyncedServers = displayServers.filter(s => !s.isSynced);
|
||||
// if (unsyncedServers.length > 0) {
|
||||
// for (const displayServer of unsyncedServers) {
|
||||
// this.displayServerRepository.removeServer(displayServer);
|
||||
// }
|
||||
// const unsyncedServerNames = unsyncedServers.map(s => s.name).join(', ');
|
||||
// let messageKey = 'error-server-removed';
|
||||
// let placeholder = 'serverName';
|
||||
// if (unsyncedServers.length > 1) {
|
||||
// // Pluralize localized message.
|
||||
// messageKey = 'error-servers-removed';
|
||||
// placeholder = 'serverNames';
|
||||
// }
|
||||
// this.appRoot.showError(this.appRoot.localize(messageKey, placeholder,
|
||||
// unsyncedServerNames));
|
||||
// }
|
||||
|
||||
// await this.syncDisplayServersToUi();
|
||||
// }
|
||||
|
||||
// Syncs the locally persisted server metadata for `server`. Creates a DisplayServer for `server`
|
||||
// if one is not found in storage. Updates the UI to show the DisplayServer.
|
||||
// While this method does not make any assumptions on whether the server is reachable, it does
|
||||
// assume that its management API URL is available.
|
||||
private async syncServerToDisplay(server: server.Server): Promise<DisplayServer> {
|
||||
// We key display servers by the server management API URL, which can be retrieved independently
|
||||
// of the server health.
|
||||
const displayServerId = server.getManagementApiUrl();
|
||||
let displayServer = this.displayServerRepository.findServer(displayServerId);
|
||||
if (!displayServer) {
|
||||
console.debug(`Could not find display server with ID ${displayServerId}`);
|
||||
displayServer = await makeDisplayServer(server);
|
||||
this.displayServerRepository.addServer(displayServer);
|
||||
} else {
|
||||
// We may need to update the stored display server if it was persisted when the server was not
|
||||
// healthy, or the server has been renamed.
|
||||
try {
|
||||
const remoteServerName = server.getName();
|
||||
if (displayServer.name !== remoteServerName) {
|
||||
displayServer.name = remoteServerName;
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore, we may not have the server config yet.
|
||||
}
|
||||
// Mark the server as synced.
|
||||
this.displayServerRepository.removeServer(displayServer);
|
||||
displayServer.isSynced = true;
|
||||
this.displayServerRepository.addServer(displayServer);
|
||||
}
|
||||
return displayServer;
|
||||
}
|
||||
// private async syncServerToDisplay(server: server.Server): Promise<DisplayServer> {
|
||||
// // We key display servers by the server management API URL, which can be retrieved
|
||||
// independently
|
||||
// // of the server health.
|
||||
// const displayServerId = server.getManagementApiUrl();
|
||||
// let displayServer = this.displayServerRepository.findServer(displayServerId);
|
||||
// if (!displayServer) {
|
||||
// console.debug(`Could not find display server with ID ${displayServerId}`);
|
||||
// displayServer = await makeDisplayServer(server);
|
||||
// this.displayServerRepository.addServer(displayServer);
|
||||
// } else {
|
||||
// // We may need to update the stored display server if it was persisted when the server was
|
||||
// not
|
||||
// // healthy, or the server has been renamed.
|
||||
// try {
|
||||
// const remoteServerName = server.getName();
|
||||
// if (displayServer.name !== remoteServerName) {
|
||||
// displayServer.name = remoteServerName;
|
||||
// }
|
||||
// } catch (e) {
|
||||
// // Ignore, we may not have the server config yet.
|
||||
// }
|
||||
// // Mark the server as synced.
|
||||
// this.displayServerRepository.removeServer(displayServer);
|
||||
// displayServer.isSynced = true;
|
||||
// this.displayServerRepository.addServer(displayServer);
|
||||
// }
|
||||
// return displayServer;
|
||||
// }
|
||||
|
||||
// Updates the UI with the stored display servers and server creation in progress, if any.
|
||||
private async syncDisplayServersToUi() {
|
||||
const displayServerBeingCreated = this.getDisplayServerBeingCreated();
|
||||
const displayServers = await this.displayServerRepository.listServers();
|
||||
if (!!displayServerBeingCreated) {
|
||||
displayServers.push(displayServerBeingCreated);
|
||||
}
|
||||
this.appRoot.serverList = displayServers;
|
||||
}
|
||||
|
||||
// Removes `displayServer` from the UI.
|
||||
private async removeServerFromDisplay(displayServer: DisplayServer) {
|
||||
this.displayServerRepository.removeServer(displayServer);
|
||||
await this.syncDisplayServersToUi();
|
||||
}
|
||||
// Removes server from the server list.
|
||||
// private async removeServerFromDisplayList(server: server.Server) {
|
||||
// const id = localId(server);
|
||||
// this.appRoot.serverList = this.appRoot.serverList.filter((ds) => ds.id !== id)
|
||||
// }
|
||||
|
||||
// Retrieves the server associated with `displayServer`.
|
||||
private async getServerFromRepository(displayServer: DisplayServer): Promise<server.Server|null> {
|
||||
const apiManagementUrl = displayServer.id;
|
||||
let server: server.Server = null;
|
||||
if (displayServer.isManaged) {
|
||||
if (!!this.digitalOceanRepository) {
|
||||
// Fetch the servers from memory to prevent a leak that happens due to polling when creating
|
||||
// a new object for a server whose creation has been cancelled.
|
||||
const managedServers = await this.digitalOceanRepository.listServers(false);
|
||||
server = managedServers.find(
|
||||
(managedServer) => managedServer.getManagementApiUrl() === apiManagementUrl);
|
||||
}
|
||||
} else {
|
||||
server =
|
||||
this.manualServerRepository.findServer({'apiUrl': apiManagementUrl, 'certSha256': ''});
|
||||
}
|
||||
return server;
|
||||
}
|
||||
// private async getServerFromRepository(displayServer: DisplayServer):
|
||||
// Promise<server.Server|null> {
|
||||
// const apiManagementUrl = displayServer.id;
|
||||
// let server: server.Server = null;
|
||||
// if (displayServer.isManaged) {
|
||||
// if (!!this.digitalOceanRepository) {
|
||||
// // Fetch the servers from memory to prevent a leak that happens due to polling when
|
||||
// creating
|
||||
// // a new object for a server whose creation has been cancelled.
|
||||
// const managedServers = await this.digitalOceanRepository.listServers(false);
|
||||
// server = managedServers.find(
|
||||
// (managedServer) => managedServer.getManagementApiUrl() === apiManagementUrl);
|
||||
// }
|
||||
// } else {
|
||||
// server =
|
||||
// this.manualServerRepository.findServer({'apiUrl': apiManagementUrl, 'certSha256': ''});
|
||||
// }
|
||||
// return server;
|
||||
// }
|
||||
|
||||
private syncServerCreationToUi(server: server.ManagedServer) {
|
||||
this.serverBeingCreated = server;
|
||||
this.syncDisplayServersToUi();
|
||||
// Show creation progress for new servers only after we have a ManagedServer object,
|
||||
// otherwise the cancel action will not be available.
|
||||
this.showServerCreationProgress();
|
||||
this.waitForManagedServerCreation();
|
||||
}
|
||||
// private getDisplayServerBeingCreated(): DisplayServer {
|
||||
// if (!this.serverBeingCreated) {
|
||||
// return null;
|
||||
// }
|
||||
// // Set name to the default server name for this region. Because the server
|
||||
// // is still being created, the getName REST API will not yet be available.
|
||||
// const regionId = this.serverBeingCreated.getHost().getRegionId();
|
||||
// const serverName = this.makeLocalizedServerName(regionId);
|
||||
// return {
|
||||
// // Use the droplet ID until the API URL is available.
|
||||
// id: this.serverBeingCreated.getHost().getHostId(),
|
||||
// name: serverName,
|
||||
// isManaged: true
|
||||
// };
|
||||
// }
|
||||
|
||||
private getDisplayServerBeingCreated(): DisplayServer {
|
||||
if (!this.serverBeingCreated) {
|
||||
return null;
|
||||
}
|
||||
// Set name to the default server name for this region. Because the server
|
||||
// is still being created, the getName REST API will not yet be available.
|
||||
const regionId = this.serverBeingCreated.getHost().getRegionId();
|
||||
const serverName = this.makeLocalizedServerName(regionId);
|
||||
return {
|
||||
// Use the droplet ID until the API URL is available.
|
||||
id: this.serverBeingCreated.getHost().getHostId(),
|
||||
name: serverName,
|
||||
isManaged: true
|
||||
};
|
||||
}
|
||||
// Returns the last server displayed, if there is one in local storage and it still exists.
|
||||
// private async lastDisplayedServer(): Promise<DisplayServer|undefined> {
|
||||
// const lastDisplayedServerId = this.displayServerRepository.getLastDisplayedServerId();
|
||||
// if (!lastDisplayedServerId) {
|
||||
// return; // No server was displayed when user quit the app.
|
||||
// }
|
||||
// const lastDisplayedServer = this.displayServerRepository.findServer(lastDisplayedServerId);
|
||||
// if (!lastDisplayedServer) {
|
||||
// console.debug('Last displayed server ID not found in display sever repository');
|
||||
// return;
|
||||
// }
|
||||
// return lastDisplayedServer;
|
||||
// }
|
||||
|
||||
// Shows the last server displayed, if there is one in local storage and it still exists.
|
||||
private async maybeShowLastDisplayedServer() {
|
||||
if (!!this.serverBeingCreated) {
|
||||
// The server being created should be shown regardless of the last user selection.
|
||||
this.displayServerRepository.removeLastDisplayedServerId();
|
||||
return;
|
||||
}
|
||||
const lastDisplayedServerId = this.displayServerRepository.getLastDisplayedServerId();
|
||||
if (!lastDisplayedServerId) {
|
||||
return; // No server was displayed when user quit the app.
|
||||
}
|
||||
const lastDisplayedServer = this.displayServerRepository.findServer(lastDisplayedServerId);
|
||||
if (!lastDisplayedServer) {
|
||||
return console.debug('Last displayed server ID not found in display sever repository');
|
||||
}
|
||||
await this.showServerFromRepository(lastDisplayedServer);
|
||||
}
|
||||
|
||||
private async showServerFromRepository(displayServer: DisplayServer) {
|
||||
const server = await this.getServerFromRepository(displayServer);
|
||||
if (!!server) {
|
||||
await this.showServer(server, displayServer);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleShowServerRequested(displayServerId: string) {
|
||||
const displayServer = this.displayServerRepository.findServer(displayServerId) ||
|
||||
this.getDisplayServerBeingCreated();
|
||||
if (!displayServer) {
|
||||
// This shouldn't happen since the displayed servers are fetched from the repository.
|
||||
console.error('Display server not found in storage');
|
||||
return;
|
||||
}
|
||||
const server = await this.getServerFromRepository(displayServer);
|
||||
if (!!server) {
|
||||
this.showServer(server, displayServer);
|
||||
} else if (!!this.serverBeingCreated) {
|
||||
this.showServerCreationProgress();
|
||||
} else {
|
||||
// This should not happen, since we remove unsynced servers from display.
|
||||
console.error(`Could not find server for display server ID ${displayServerId}`);
|
||||
}
|
||||
}
|
||||
// private async handleShowServerRequested(serverId: string) {
|
||||
// const displayServer = this.displayServerRepository.findServer(serverId);
|
||||
// if (!displayServer) {
|
||||
// // This shouldn't happen since the displayed servers are fetched from the repository.
|
||||
// console.error('Display server not found in storage');
|
||||
// return;
|
||||
// }
|
||||
// const server = await this.getServerFromRepository(displayServer);
|
||||
// if (!!server) {
|
||||
// this.showServer(server);
|
||||
// } else {
|
||||
// // This should not happen, since we remove unsynced servers from display.
|
||||
// console.error(`Could not find server for display server ID ${serverId}`);
|
||||
// }
|
||||
// }
|
||||
|
||||
// Signs in to DigitalOcean through the OAuthFlow. Creates a `ManagedServerRepository` and
|
||||
// resolves with the servers present in the account.
|
||||
|
|
@ -562,47 +619,6 @@ export class App {
|
|||
});
|
||||
}
|
||||
|
||||
private async showServer(server: server.Server, displayServer: DisplayServer): Promise<void> {
|
||||
if (await server.isHealthy()) {
|
||||
// Sync the server display in case it was previously unreachable.
|
||||
await this.syncServerToDisplay(server);
|
||||
await this.showServerManagement(server, displayServer);
|
||||
} else {
|
||||
this.showServerUnreachable(server, displayServer);
|
||||
}
|
||||
}
|
||||
|
||||
private showServerUnreachable(server: server.Server, displayServer: DisplayServer) {
|
||||
// Display the unreachable server state within the server view.
|
||||
const serverView = this.appRoot.getServerView(displayServer.id) as ServerView;
|
||||
serverView.isServerReachable = false;
|
||||
serverView.isServerManaged = isManagedServer(server);
|
||||
serverView.serverName = displayServer.name; // Don't get the name from the remote server.
|
||||
serverView.retryDisplayingServer = async () => {
|
||||
// Refresh the server list if the server is managed, it may have been deleted outside the
|
||||
// app.
|
||||
let serverExists = true;
|
||||
if (serverView.isServerManaged && !!this.digitalOceanRepository) {
|
||||
await this.digitalOceanRepository.listServers();
|
||||
serverExists = !!(await this.getServerFromRepository(displayServer));
|
||||
}
|
||||
if (serverExists) {
|
||||
await this.showServer(server, displayServer);
|
||||
} else {
|
||||
// Server has been deleted outside the app.
|
||||
this.appRoot.showError(
|
||||
this.appRoot.localize('error-server-removed', 'serverName', displayServer.name));
|
||||
this.removeServerFromDisplay(displayServer);
|
||||
this.selectedServer = null;
|
||||
this.appRoot.selectedServer = null;
|
||||
this.showIntro();
|
||||
}
|
||||
};
|
||||
this.selectedServer = server;
|
||||
this.appRoot.selectedServer = displayServer;
|
||||
this.appRoot.showServerView();
|
||||
}
|
||||
|
||||
// Intended to add a "retry or re-authenticate?" prompt to DigitalOcean
|
||||
// operations. Specifically, any operation rejecting with an digitalocean_api.XhrError will
|
||||
// result in a dialog asking the user whether to retry the operation or
|
||||
|
|
@ -643,28 +659,15 @@ export class App {
|
|||
this.appRoot.showNotification(this.appRoot.localize('notification-app-update'), 60000);
|
||||
}
|
||||
|
||||
private async connectToDigitalOcean() {
|
||||
let accessToken = this.digitalOceanTokenManager.getStoredToken();
|
||||
if (accessToken) {
|
||||
const managedServers = await this.enterDigitalOceanMode(accessToken);
|
||||
if (!!this.serverBeingCreated) {
|
||||
// Disallow creating multiple servers simultaneously.
|
||||
this.showServerCreationProgress();
|
||||
return;
|
||||
}
|
||||
this.syncServersToDisplay(managedServers);
|
||||
this.showCreateServer();
|
||||
return;
|
||||
}
|
||||
|
||||
private async startDigitalOceanConnectAccount() {
|
||||
const session = runDigitalOceanOauth();
|
||||
const handleOauthFlowCanceled = () => {
|
||||
const handleOauthFlowCancelled = () => {
|
||||
session.cancel();
|
||||
this.clearCredentialsAndShowIntro();
|
||||
};
|
||||
this.appRoot.getAndShowDigitalOceanOauthFlow(handleOauthFlowCanceled);
|
||||
this.appRoot.getAndShowDigitalOceanOauthFlow(handleOauthFlowCancelled);
|
||||
try {
|
||||
accessToken = await session.result;
|
||||
const accessToken = await session.result;
|
||||
// Save accessToken to storage. DigitalOcean tokens
|
||||
// expire after 30 days, unless they are manually revoked by the user.
|
||||
// After 30 days the user will have to sign into DigitalOcean again.
|
||||
|
|
@ -675,11 +678,10 @@ export class App {
|
|||
this.digitalOceanTokenManager.writeTokenToStorage(accessToken);
|
||||
const managedServers = await this.enterDigitalOceanMode(accessToken);
|
||||
if (managedServers.length > 0) {
|
||||
await this.syncServersToDisplay(managedServers);
|
||||
// Show the first server in the list since the user just signed in to DO.
|
||||
const displayServer =
|
||||
this.appRoot.serverList.find((displayServer: DisplayServer) => displayServer.isManaged);
|
||||
this.showServerFromRepository(displayServer);
|
||||
for (const server of managedServers) {
|
||||
this.addServer(server);
|
||||
}
|
||||
this.showServer(managedServers[0]);
|
||||
} else {
|
||||
this.showCreateServer();
|
||||
}
|
||||
|
|
@ -692,18 +694,66 @@ export class App {
|
|||
}
|
||||
}
|
||||
}
|
||||
private async startDigitalOceanCreateServer() {
|
||||
const accessToken = this.digitalOceanTokenManager.getStoredToken();
|
||||
if (!accessToken) {
|
||||
console.error('DigitalOcean accessToken not found');
|
||||
return;
|
||||
}
|
||||
this.showCreateServer();
|
||||
}
|
||||
// private async connectToDigitalOcean() {
|
||||
// let accessToken = this.digitalOceanTokenManager.getStoredToken();
|
||||
// if (accessToken) {
|
||||
// const managedServers = await this.enterDigitalOceanMode(accessToken);
|
||||
// for (let server of managedServers) {
|
||||
// // TODO: This is duplicating the servers
|
||||
// this.addServer(server);
|
||||
// }
|
||||
// this.showCreateServer();
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const session = runDigitalOceanOauth();
|
||||
// const handleOauthFlowCancelled = () => {
|
||||
// session.cancel();
|
||||
// this.clearCredentialsAndShowIntro();
|
||||
// };
|
||||
// this.appRoot.getAndShowDigitalOceanOauthFlow(handleOauthFlowCancelled);
|
||||
// try {
|
||||
// accessToken = await session.result;
|
||||
// // Save accessToken to storage. DigitalOcean tokens
|
||||
// // expire after 30 days, unless they are manually revoked by the user.
|
||||
// // After 30 days the user will have to sign into DigitalOcean again.
|
||||
// // Note we cannot yet use DigitalOcean refresh tokens, as they require
|
||||
// // a client_secret to be stored on a server and not visible to end users
|
||||
// // in client-side JS. More details at:
|
||||
// // https://developers.digitalocean.com/documentation/oauth/#refresh-token-flow
|
||||
// this.digitalOceanTokenManager.writeTokenToStorage(accessToken);
|
||||
// const managedServers = await this.enterDigitalOceanMode(accessToken);
|
||||
// if (managedServers.length > 0) {
|
||||
// for (let server of managedServers) {
|
||||
// this.addServer(server);
|
||||
// }
|
||||
// this.showServer(managedServers[0]);
|
||||
// } else {
|
||||
// this.showCreateServer();
|
||||
// }
|
||||
// } catch (error) {
|
||||
// if (!session.isCancelled()) {
|
||||
// this.clearCredentialsAndShowIntro();
|
||||
// bringToFront();
|
||||
// console.error(`DigitalOcean authentication failed: ${error}`);
|
||||
// this.appRoot.showError(this.appRoot.localize('error-do-auth'));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// Clears the credentials and returns to the intro screen.
|
||||
private clearCredentialsAndShowIntro() {
|
||||
this.digitalOceanTokenManager.removeTokenFromStorage();
|
||||
// Remove display servers from storage.
|
||||
this.displayServerRepository.listServers().then((displayServers: DisplayServer[]) => {
|
||||
for (const displayServer of displayServers) {
|
||||
if (displayServer.isManaged) {
|
||||
this.removeServerFromDisplay(displayServer);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Clear list
|
||||
this.appRoot.serverList = this.appRoot.serverList.filter((ds) => !ds.isManaged);
|
||||
// Reset UI
|
||||
this.appRoot.adminEmail = '';
|
||||
if (!!this.appRoot.selectedServer && this.appRoot.selectedServer.isManaged) {
|
||||
|
|
@ -716,6 +766,7 @@ export class App {
|
|||
|
||||
// Opens the screen to create a server.
|
||||
private showCreateServer() {
|
||||
// TODO: Should validate account here.
|
||||
const regionPicker = this.appRoot.getAndShowRegionPicker();
|
||||
// The region picker initially shows all options as disabled. Options are enabled by this code,
|
||||
// after checking which regions are available.
|
||||
|
|
@ -735,55 +786,63 @@ export class App {
|
|||
});
|
||||
}
|
||||
|
||||
private showServerCreationProgress() {
|
||||
// Set selected server, needed for cancel button.
|
||||
this.selectedServer = this.serverBeingCreated;
|
||||
this.appRoot.selectedServer = this.getDisplayServerBeingCreated();
|
||||
// Update UI. Only show cancel button if the server has not yet finished
|
||||
// installation, to prevent accidental deletion when restarting.
|
||||
const showCancelButton = !this.serverBeingCreated.isInstallCompleted();
|
||||
this.appRoot.showProgress(this.appRoot.selectedServer.name, showCancelButton);
|
||||
}
|
||||
|
||||
private waitForManagedServerCreation(tryAgain = false): void {
|
||||
this.serverBeingCreated.waitOnInstall(tryAgain)
|
||||
.then(() => {
|
||||
// Unset the instance variable before syncing the server so the UI does not display it.
|
||||
const server = this.serverBeingCreated;
|
||||
this.serverBeingCreated = null;
|
||||
return this.syncAndShowServer(server);
|
||||
// Returns a promise which fulfills once the DigitalOcean droplet is created.
|
||||
// Shadowbox may not be fully installed once this promise is fulfilled.
|
||||
public async createDigitalOceanServer(regionId: server.RegionId) {
|
||||
const serverName = this.makeLocalizedServerName(regionId);
|
||||
return this
|
||||
.digitalOceanRetry(() => {
|
||||
return this.digitalOceanRepository.createServer(regionId, serverName);
|
||||
})
|
||||
.then(async (server) => {
|
||||
this.addServer(server);
|
||||
this.showServer(server);
|
||||
// TODO: Needs to poll as in waitForManagedServerCreation.
|
||||
await server.waitOnInstall(false);
|
||||
this.updateServer(server);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
if (e instanceof errors.DeletedServerError) {
|
||||
// The user deleted this server, no need to show an error or delete it again.
|
||||
this.serverBeingCreated = null;
|
||||
return;
|
||||
}
|
||||
let errorMessage = this.serverBeingCreated.isInstallCompleted() ?
|
||||
this.appRoot.localize('error-server-unreachable-title') :
|
||||
this.appRoot.localize('error-server-creation');
|
||||
errorMessage += ` ${this.appRoot.localize('digitalocean-unreachable')}`;
|
||||
this.appRoot
|
||||
.showModalDialog(
|
||||
null, // Don't display any title.
|
||||
errorMessage,
|
||||
[this.appRoot.localize('server-destroy'), this.appRoot.localize('retry')])
|
||||
.then((clickedButtonIndex: number) => {
|
||||
if (clickedButtonIndex === 0) { // user clicked 'Delete this server'
|
||||
console.info('Deleting unreachable server');
|
||||
this.serverBeingCreated.getHost().delete().then(() => {
|
||||
this.serverBeingCreated = null;
|
||||
this.showCreateServer();
|
||||
});
|
||||
} else if (clickedButtonIndex === 1) { // user clicked 'Try again'.
|
||||
console.info('Retrying unreachable server');
|
||||
this.waitForManagedServerCreation(true);
|
||||
}
|
||||
});
|
||||
// Sanity check - this error is not expected to occur, as waitForManagedServerCreation
|
||||
// has it's own error handling.
|
||||
console.error('error from waitForManagedServerCreation');
|
||||
return Promise.reject(e);
|
||||
});
|
||||
}
|
||||
|
||||
// private waitForManagedServerCreation(server: server.ManagedServer, tryAgain = false): void {
|
||||
// server.waitOnInstall(tryAgain)
|
||||
// .then(() => {
|
||||
// // Update name on the server list.
|
||||
// this.updateServer(server);
|
||||
// })
|
||||
// .catch((e) => {
|
||||
// console.log(e);
|
||||
// if (e instanceof errors.DeletedServerError) {
|
||||
// return;
|
||||
// }
|
||||
// let errorMessage = server.isInstallCompleted() ?
|
||||
// this.appRoot.localize('error-server-unreachable-title') :
|
||||
// this.appRoot.localize('error-server-creation');
|
||||
// errorMessage += ` ${this.appRoot.localize('digitalocean-unreachable')}`;
|
||||
// this.appRoot
|
||||
// .showModalDialog(
|
||||
// null, // Don't display any title.
|
||||
// errorMessage,
|
||||
// [this.appRoot.localize('server-destroy'), this.appRoot.localize('retry')])
|
||||
// .then((clickedButtonIndex: number) => {
|
||||
// if (clickedButtonIndex === 0) { // user clicked 'Delete this server'
|
||||
// console.info('Deleting unreachable server');
|
||||
// server.getHost().delete().then(() => {
|
||||
// this.showCreateServer();
|
||||
// });
|
||||
// } else if (clickedButtonIndex === 1) { // user clicked 'Try again'.
|
||||
// console.info('Retrying unreachable server');
|
||||
// this.waitForManagedServerCreation(server, true);
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
private getLocalizedCityName(regionId: server.RegionId) {
|
||||
const cityId = digitalocean_server.GetCityId(regionId);
|
||||
return this.appRoot.localize(`city-${cityId}`);
|
||||
|
|
@ -794,53 +853,43 @@ export class App {
|
|||
return this.appRoot.localize('server-name', 'serverLocation', serverLocation);
|
||||
}
|
||||
|
||||
// Returns a promise which fulfills once the DigitalOcean droplet is created.
|
||||
// Shadowbox may not be fully installed once this promise is fulfilled.
|
||||
public createDigitalOceanServer(regionId: server.RegionId) {
|
||||
const serverName = this.makeLocalizedServerName(regionId);
|
||||
return this
|
||||
.digitalOceanRetry(() => {
|
||||
return this.digitalOceanRepository.createServer(regionId, serverName);
|
||||
})
|
||||
.then((server) => {
|
||||
this.syncServerCreationToUi(server);
|
||||
})
|
||||
.catch((e) => {
|
||||
// Sanity check - this error is not expected to occur, as waitForManagedServerCreation
|
||||
// has it's own error handling.
|
||||
console.error('error from waitForManagedServerCreation');
|
||||
return Promise.reject(e);
|
||||
});
|
||||
}
|
||||
private async showServer(server: server.Server): Promise<void> {
|
||||
this.selectedServer = server;
|
||||
const displayServer = this.makeDisplayServer(server);
|
||||
this.appRoot.selectedServer = displayServer;
|
||||
localStorage.setItem(LAST_DISPLAYED_SERVER_STORAGE_KEY, localId(server));
|
||||
|
||||
// Syncs a healthy `server` to the display and shows it.
|
||||
private async syncAndShowServer(server: server.Server, timeoutMs = 250) {
|
||||
const displayServer = await this.syncServerToDisplay(server);
|
||||
await this.syncDisplayServersToUi();
|
||||
this.showServerManagement(server, displayServer);
|
||||
if (await server.isHealthy()) {
|
||||
// Sync the server display in case it was previously unreachable.
|
||||
await this.showServerManagement(server);
|
||||
return;
|
||||
}
|
||||
if (isManagedServer(server)) {
|
||||
const managedServer = server as server.ManagedServer;
|
||||
if (!managedServer.isInstallCompleted()) {
|
||||
this.appRoot.showProgress(displayServer.name, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.showServerUnreachable(server, displayServer);
|
||||
}
|
||||
|
||||
// Show the server management screen. Assumes the server is healthy.
|
||||
private async showServerManagement(
|
||||
selectedServer: server.Server, selectedDisplayServer: DisplayServer) {
|
||||
this.selectedServer = selectedServer;
|
||||
this.appRoot.selectedServer = selectedDisplayServer;
|
||||
this.displayServerRepository.storeLastDisplayedServerId(selectedDisplayServer.id);
|
||||
|
||||
private async showServerManagement(server: server.Server) {
|
||||
// Show view and initialize fields from selectedServer.
|
||||
const view = this.appRoot.getServerView(selectedDisplayServer.id);
|
||||
const view = this.appRoot.getServerView(localId(server));
|
||||
view.isServerReachable = true;
|
||||
view.serverId = selectedServer.getServerId();
|
||||
view.serverName = selectedServer.getName();
|
||||
view.serverHostname = selectedServer.getHostnameForAccessKeys();
|
||||
view.serverManagementApiUrl = selectedServer.getManagementApiUrl();
|
||||
view.serverPortForNewAccessKeys = selectedServer.getPortForNewAccessKeys();
|
||||
view.serverCreationDate = localizeDate(selectedServer.getCreatedDate(), this.appRoot.language);
|
||||
view.serverVersion = selectedServer.getVersion();
|
||||
view.accessKeyDataLimit = dataLimitToDisplayDataAmount(selectedServer.getAccessKeyDataLimit());
|
||||
view.serverId = server.getServerId();
|
||||
view.serverName = server.getName();
|
||||
view.serverHostname = server.getHostnameForAccessKeys();
|
||||
view.serverManagementApiUrl = server.getManagementApiUrl();
|
||||
view.serverPortForNewAccessKeys = server.getPortForNewAccessKeys();
|
||||
view.serverCreationDate = localizeDate(server.getCreatedDate(), this.appRoot.language);
|
||||
view.serverVersion = server.getVersion();
|
||||
view.accessKeyDataLimit = dataLimitToDisplayDataAmount(server.getAccessKeyDataLimit());
|
||||
view.isAccessKeyDataLimitEnabled = !!view.accessKeyDataLimit;
|
||||
view.showFeatureMetricsDisclaimer = selectedServer.getMetricsEnabled() &&
|
||||
!selectedServer.getAccessKeyDataLimit() && !hasSeenFeatureMetricsNotification();
|
||||
view.showFeatureMetricsDisclaimer = server.getMetricsEnabled() &&
|
||||
!server.getAccessKeyDataLimit() && !hasSeenFeatureMetricsNotification();
|
||||
|
||||
const version = this.selectedServer.getVersion();
|
||||
if (version) {
|
||||
|
|
@ -849,9 +898,9 @@ export class App {
|
|||
view.isHostnameEditable = semver.gte(version, CHANGE_HOSTNAME_VERSION);
|
||||
}
|
||||
|
||||
if (isManagedServer(selectedServer)) {
|
||||
if (isManagedServer(server)) {
|
||||
view.isServerManaged = true;
|
||||
const host = selectedServer.getHost();
|
||||
const host = server.getHost();
|
||||
view.monthlyCost = host.getMonthlyCost().usd;
|
||||
view.monthlyOutboundTransferBytes =
|
||||
host.getMonthlyOutboundTransferLimit().terabytes * (10 ** 12);
|
||||
|
|
@ -860,17 +909,17 @@ export class App {
|
|||
view.isServerManaged = false;
|
||||
}
|
||||
|
||||
view.metricsEnabled = selectedServer.getMetricsEnabled();
|
||||
view.metricsEnabled = server.getMetricsEnabled();
|
||||
this.appRoot.showServerView();
|
||||
this.showMetricsOptInWhenNeeded(selectedServer, view);
|
||||
this.showMetricsOptInWhenNeeded(server, view);
|
||||
|
||||
// Load "My Connection" and other access keys.
|
||||
try {
|
||||
const serverAccessKeys = await selectedServer.listAccessKeys();
|
||||
const serverAccessKeys = await server.listAccessKeys();
|
||||
view.accessKeyRows = serverAccessKeys.map(this.convertToUiAccessKey.bind(this));
|
||||
if (!view.accessKeyDataLimit) {
|
||||
view.accessKeyDataLimit = dataLimitToDisplayDataAmount(
|
||||
await computeDefaultAccessKeyDataLimit(selectedServer, serverAccessKeys));
|
||||
await computeDefaultAccessKeyDataLimit(server, serverAccessKeys));
|
||||
}
|
||||
// Show help bubbles once the page has rendered.
|
||||
setTimeout(() => {
|
||||
|
|
@ -881,7 +930,45 @@ export class App {
|
|||
this.appRoot.showError(this.appRoot.localize('error-keys-get'));
|
||||
}
|
||||
|
||||
this.showTransferStats(selectedServer, view);
|
||||
this.showTransferStats(server, view);
|
||||
}
|
||||
|
||||
// private showServerCreationProgress(server: server.ManagedServer, displayServer:
|
||||
// DisplayServer) {
|
||||
// this.appRoot.showProgress(server.getName(), true);
|
||||
// this.waitForManagedServerCreation();
|
||||
// }
|
||||
|
||||
private showServerUnreachable(server: server.Server, displayServer: DisplayServer) {
|
||||
// Display the unreachable server state within the server view.
|
||||
const serverView = this.appRoot.getServerView(displayServer.id) as ServerView;
|
||||
serverView.isServerReachable = false;
|
||||
serverView.isServerManaged = isManagedServer(server);
|
||||
serverView.serverName = displayServer.name; // Don't get the name from the remote server.
|
||||
serverView.retryDisplayingServer = async () => {
|
||||
this.showServer(server);
|
||||
// // Refresh the server list if the server is managed, it may have been deleted outside the
|
||||
// // app.
|
||||
// let serverExists = true;
|
||||
// if (serverView.isServerManaged && !!this.digitalOceanRepository) {
|
||||
// await this.digitalOceanRepository.listServers();
|
||||
// serverExists = !!(await this.getServerFromRepository(displayServer));
|
||||
// }
|
||||
// if (serverExists) {
|
||||
// await this.showServer(server);
|
||||
// } else {
|
||||
// // Server has been deleted outside the app.
|
||||
// this.appRoot.showError(
|
||||
// this.appRoot.localize('error-server-removed', 'serverName', displayServer.name));
|
||||
// this.removeServerFromDisplayList(server);
|
||||
// this.selectedServer = null;
|
||||
// this.appRoot.selectedServer = null;
|
||||
// this.showIntro();
|
||||
// }
|
||||
};
|
||||
this.selectedServer = server;
|
||||
this.appRoot.selectedServer = displayServer;
|
||||
this.appRoot.showServerView();
|
||||
}
|
||||
|
||||
private showMetricsOptInWhenNeeded(selectedServer: server.Server, serverView: ServerView) {
|
||||
|
|
@ -1108,15 +1195,13 @@ export class App {
|
|||
// are adding an existing server. Query the repository instead to treat the UI accordingly.
|
||||
const storedServer = this.manualServerRepository.findServer(serverConfig);
|
||||
if (!!storedServer) {
|
||||
return this.syncServerToDisplay(storedServer).then((displayServer) => {
|
||||
this.appRoot.showNotification(this.appRoot.localize('notification-server-exists'), 5000);
|
||||
this.showServer(storedServer, displayServer);
|
||||
});
|
||||
this.appRoot.showNotification(this.appRoot.localize('notification-server-exists'), 5000);
|
||||
return this.showServer(storedServer);
|
||||
}
|
||||
return this.manualServerRepository.addServer(serverConfig).then((manualServer) => {
|
||||
return manualServer.isHealthy().then((isHealthy) => {
|
||||
if (isHealthy) {
|
||||
return this.syncAndShowServer(manualServer);
|
||||
return this.showServer(manualServer);
|
||||
} else {
|
||||
// Remove inaccessible manual server from local storage if it was just created.
|
||||
manualServer.forget();
|
||||
|
|
@ -1141,6 +1226,7 @@ export class App {
|
|||
|
||||
private deleteSelectedServer() {
|
||||
const serverToDelete = this.selectedServer;
|
||||
const serverId = localId(serverToDelete);
|
||||
if (!isManagedServer(serverToDelete)) {
|
||||
const msg = 'cannot delete non-ManagedServer';
|
||||
console.error(msg);
|
||||
|
|
@ -1156,7 +1242,7 @@ export class App {
|
|||
})
|
||||
.then(
|
||||
() => {
|
||||
this.removeServerFromDisplay(this.appRoot.selectedServer);
|
||||
this.removeServer(serverId);
|
||||
this.appRoot.selectedServer = null;
|
||||
this.selectedServer = null;
|
||||
this.showIntro();
|
||||
|
|
@ -1175,6 +1261,7 @@ export class App {
|
|||
|
||||
private forgetSelectedServer() {
|
||||
const serverToForget = this.selectedServer;
|
||||
const serverId = localId(serverToForget);
|
||||
if (!isManualServer(serverToForget)) {
|
||||
const msg = 'cannot forget non-ManualServer';
|
||||
console.error(msg);
|
||||
|
|
@ -1186,7 +1273,7 @@ export class App {
|
|||
const confirmationButton = this.appRoot.localize('remove');
|
||||
this.appRoot.getConfirmation(confirmationTitle, confirmationText, confirmationButton, () => {
|
||||
serverToForget.forget();
|
||||
this.removeServerFromDisplay(this.appRoot.selectedServer);
|
||||
this.removeServer(serverId);
|
||||
this.appRoot.selectedServer = null;
|
||||
this.selectedServer = null;
|
||||
this.showIntro();
|
||||
|
|
@ -1209,11 +1296,13 @@ export class App {
|
|||
}
|
||||
|
||||
private async renameServer(newName: string) {
|
||||
const view = this.appRoot.getServerView(this.appRoot.selectedServer.id);
|
||||
const serverToRename = this.selectedServer;
|
||||
const serverId = this.appRoot.selectedServer.id;
|
||||
const view = this.appRoot.getServerView(serverId);
|
||||
try {
|
||||
await this.selectedServer.setName(newName);
|
||||
await serverToRename.setName(newName);
|
||||
view.serverName = newName;
|
||||
this.syncAndShowServer(this.selectedServer);
|
||||
this.updateServer(serverToRename);
|
||||
} catch (error) {
|
||||
console.error(`Failed to rename server: ${error}`);
|
||||
this.appRoot.showError(this.appRoot.localize('error-server-rename'));
|
||||
|
|
@ -1231,8 +1320,7 @@ export class App {
|
|||
throw new Error(msg);
|
||||
}
|
||||
serverToCancel.getHost().delete().then(() => {
|
||||
this.serverBeingCreated = null;
|
||||
this.removeServerFromDisplay(this.appRoot.selectedServer);
|
||||
this.removeServer(localId(serverToCancel));
|
||||
this.appRoot.selectedServer = null;
|
||||
this.showCreateServer();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,138 +0,0 @@
|
|||
// Copyright 2018 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 {InMemoryStorage} from '../infrastructure/memory_storage';
|
||||
import {DisplayServerRepository} from './display_server';
|
||||
|
||||
// Use this helper to compare `DisplayServer`s when we don't care about the
|
||||
// `isSynced` property.
|
||||
const objectContaining = jasmine.objectContaining;
|
||||
|
||||
describe('DisplayServerRepository', () => {
|
||||
it('adds and finds servers', () => {
|
||||
const displayServer = {id: 'id', name: 'name', isManaged: false};
|
||||
const repository = new DisplayServerRepository(new InMemoryStorage());
|
||||
repository.addServer(displayServer);
|
||||
|
||||
expect(displayServer).toEqual(repository.findServer(displayServer.id));
|
||||
});
|
||||
|
||||
it('lists servers', () => {
|
||||
const displayServer1 = {id: 'id1', name: 'name1', isManaged: false};
|
||||
const displayServer2 = {id: 'id2', name: 'name2', isManaged: true};
|
||||
const store = new Map([[
|
||||
DisplayServerRepository.SERVERS_STORAGE_KEY,
|
||||
JSON.stringify([displayServer1, displayServer2])
|
||||
]]);
|
||||
const repository = new DisplayServerRepository(new InMemoryStorage(store));
|
||||
|
||||
repository.listServers().then((servers) => {
|
||||
expect(servers).toContain(objectContaining(displayServer1));
|
||||
expect(servers).toContain(objectContaining(displayServer2));
|
||||
});
|
||||
});
|
||||
|
||||
it('throws when adding duplicate servers', () => {
|
||||
const displayServer1 = {id: 'id', name: 'name', isManaged: true};
|
||||
const displayServer2 = {id: 'id', name: 'name', isManaged: true};
|
||||
const repository = new DisplayServerRepository(new InMemoryStorage());
|
||||
repository.addServer(displayServer1);
|
||||
|
||||
const addDuplicateServer = () => {
|
||||
repository.addServer(displayServer2);
|
||||
};
|
||||
expect(addDuplicateServer).toThrow();
|
||||
});
|
||||
|
||||
it('throws when adding servers missing properties', () => {
|
||||
const displayServer = {id: 'id', name: 'name', isManaged: true};
|
||||
delete displayServer['name']; // Appease compiler.
|
||||
const repository = new DisplayServerRepository(new InMemoryStorage());
|
||||
|
||||
const addMalformedServer = () => {
|
||||
repository.addServer(displayServer);
|
||||
};
|
||||
expect(addMalformedServer).toThrow();
|
||||
});
|
||||
|
||||
it('loads existing servers', () => {
|
||||
const displayServer1 = {id: 'id1', name: 'name1', isManaged: false};
|
||||
const displayServer2 = {id: 'id2', name: 'name2', isManaged: true};
|
||||
const store = new Map([[
|
||||
DisplayServerRepository.SERVERS_STORAGE_KEY,
|
||||
JSON.stringify([displayServer1, displayServer2])
|
||||
]]);
|
||||
const repository = new DisplayServerRepository(new InMemoryStorage(store));
|
||||
expect(repository.findServer(displayServer1.id)).toEqual(objectContaining(displayServer1));
|
||||
expect(repository.findServer(displayServer2.id)).toEqual(objectContaining(displayServer2));
|
||||
});
|
||||
|
||||
it('loads existing servers unsynced', () => {
|
||||
// Initialize isSynced to true to simulate a persisted synced server.
|
||||
const displayServer = {id: 'id', name: 'name', isManaged: false, isSynced: false};
|
||||
const store =
|
||||
new Map([[DisplayServerRepository.SERVERS_STORAGE_KEY, JSON.stringify([displayServer])]]);
|
||||
const repository = new DisplayServerRepository(new InMemoryStorage(store));
|
||||
const foundServer = repository.findServer(displayServer.id);
|
||||
expect(foundServer.isSynced).toBeFalsy();
|
||||
});
|
||||
|
||||
it('removes servers', () => {
|
||||
const displayServerToKeep = {id: 'id0', name: 'name0', isManaged: false};
|
||||
const displayServerToRemove = {id: 'id', name: 'name', isManaged: false};
|
||||
const repository = new DisplayServerRepository(new InMemoryStorage());
|
||||
repository.addServer(displayServerToKeep);
|
||||
repository.addServer(displayServerToRemove);
|
||||
|
||||
expect(repository.findServer(displayServerToRemove.id)).toEqual(displayServerToRemove);
|
||||
repository.removeServer(displayServerToRemove);
|
||||
expect(repository.findServer(displayServerToRemove.id)).toBeUndefined();
|
||||
expect(repository.findServer(displayServerToKeep.id))
|
||||
.toEqual(objectContaining(displayServerToKeep));
|
||||
});
|
||||
|
||||
it('persists servers', () => {
|
||||
const displayServer = {id: 'id', name: 'name', isManaged: false};
|
||||
const storage = new InMemoryStorage();
|
||||
let repository = new DisplayServerRepository(storage);
|
||||
repository.addServer(displayServer);
|
||||
|
||||
// Instantiate a new repository to validate that servers have been persisted to storage.
|
||||
repository = new DisplayServerRepository(storage);
|
||||
expect(repository.findServer(displayServer.id)).toEqual(objectContaining(displayServer));
|
||||
});
|
||||
|
||||
it('persists the last displayed server ID', () => {
|
||||
const serverId = 'serverId';
|
||||
const storage = new InMemoryStorage();
|
||||
let repository = new DisplayServerRepository(storage);
|
||||
repository.storeLastDisplayedServerId(serverId);
|
||||
|
||||
expect(repository.getLastDisplayedServerId()).toEqual(serverId);
|
||||
|
||||
repository = new DisplayServerRepository(storage);
|
||||
expect(repository.getLastDisplayedServerId()).toEqual(serverId);
|
||||
});
|
||||
|
||||
it('removes the last displayed server ID', () => {
|
||||
const serverId = 'serverId';
|
||||
const storage = new InMemoryStorage();
|
||||
const repository = new DisplayServerRepository(storage);
|
||||
repository.storeLastDisplayedServerId(serverId);
|
||||
|
||||
expect(repository.getLastDisplayedServerId()).toEqual(serverId);
|
||||
repository.removeLastDisplayedServerId();
|
||||
expect(repository.getLastDisplayedServerId()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
@ -12,96 +12,9 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import * as server from '../model/server';
|
||||
|
||||
export interface DisplayServer {
|
||||
id: string;
|
||||
name: string;
|
||||
isManaged: boolean;
|
||||
isSynced?: boolean;
|
||||
}
|
||||
|
||||
// Returns a `DisplayServer` corresponding to `server`.
|
||||
export async function makeDisplayServer(server: server.Server) {
|
||||
return {
|
||||
id: server.getManagementApiUrl(),
|
||||
name: await server.isHealthy().catch((e) => false) ? server.getName() :
|
||||
server.getHostnameForAccessKeys(),
|
||||
isManaged: !!(server as server.ManagedServer).getHost,
|
||||
isSynced: true
|
||||
};
|
||||
}
|
||||
|
||||
// Persistence layer for `DisplayServer`. Caches the list of servers shown in the UI in case they
|
||||
// cannot be accessed due to connectivity issues.
|
||||
export class DisplayServerRepository {
|
||||
static readonly SERVERS_STORAGE_KEY = 'displayServers';
|
||||
static readonly LAST_DISPLAYED_SERVER_STORAGE_KEY = 'lastDisplayedServer';
|
||||
|
||||
private servers: DisplayServer[] = [];
|
||||
|
||||
constructor(private storage: Storage = localStorage) {
|
||||
this.loadServers();
|
||||
}
|
||||
|
||||
listServers(): Promise<DisplayServer[]> {
|
||||
// Copy the server array; resolving with the instance variable may lead to races in `findServer`
|
||||
return Promise.resolve(JSON.parse(JSON.stringify(this.servers)));
|
||||
}
|
||||
|
||||
addServer(server: DisplayServer) {
|
||||
if (!server || !server.id || !server.name) {
|
||||
throw new Error('Failed to add display server, one or more properties missing');
|
||||
}
|
||||
if (this.findServer(server.id)) {
|
||||
throw new Error(`Display server already stored`);
|
||||
}
|
||||
this.servers.push(server);
|
||||
this.storeServers();
|
||||
}
|
||||
|
||||
removeServer(serverToRemove: DisplayServer) {
|
||||
this.servers = this.servers.filter((server: DisplayServer) => {
|
||||
return server.id !== serverToRemove.id;
|
||||
});
|
||||
this.storeServers();
|
||||
}
|
||||
|
||||
findServer(serverId: string): DisplayServer {
|
||||
return this.servers.find(server => server.id === serverId);
|
||||
}
|
||||
|
||||
storeLastDisplayedServerId(serverId: string) {
|
||||
this.storage.setItem(DisplayServerRepository.LAST_DISPLAYED_SERVER_STORAGE_KEY, serverId);
|
||||
}
|
||||
|
||||
getLastDisplayedServerId(): string {
|
||||
return this.storage.getItem(DisplayServerRepository.LAST_DISPLAYED_SERVER_STORAGE_KEY);
|
||||
}
|
||||
|
||||
removeLastDisplayedServerId() {
|
||||
this.storage.removeItem(DisplayServerRepository.LAST_DISPLAYED_SERVER_STORAGE_KEY);
|
||||
}
|
||||
|
||||
// Loads the servers from storage. Does *not* throw because it is only called on the constructor,
|
||||
// and the repository is created before the app starts.
|
||||
private loadServers() {
|
||||
const serversJson = this.storage.getItem(DisplayServerRepository.SERVERS_STORAGE_KEY);
|
||||
if (!serversJson) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// We need the servers' `isSynced` property be false on load.
|
||||
this.servers = JSON.parse(serversJson).map((server: DisplayServer) => {
|
||||
server.isSynced = false;
|
||||
return server;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Error loading local servers from storage');
|
||||
}
|
||||
}
|
||||
|
||||
private storeServers() {
|
||||
this.storage.setItem(DisplayServerRepository.SERVERS_STORAGE_KEY, JSON.stringify(this.servers));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import {getSentryApiUrl} from '../infrastructure/sentry';
|
|||
import {App} from './app';
|
||||
import {DigitalOceanTokenManager} from './digitalocean_oauth';
|
||||
import * as digitalocean_server from './digitalocean_server';
|
||||
import {DisplayServerRepository} from './display_server';
|
||||
import {ManualServerRepository} from './manual_server';
|
||||
import {AppRoot} from './ui_components/app-root.js';
|
||||
|
||||
|
|
@ -123,7 +122,7 @@ document.addEventListener('WebComponentsReady', () => {
|
|||
new App(
|
||||
appRoot, version, digitalocean_api.createDigitalOceanSession,
|
||||
digitalOceanServerRepositoryFactory, new ManualServerRepository('manualServers'),
|
||||
new DisplayServerRepository(), new DigitalOceanTokenManager())
|
||||
new DigitalOceanTokenManager())
|
||||
.start();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ export class ShadowboxServer implements server.Server {
|
|||
}
|
||||
|
||||
getName(): string {
|
||||
return this.serverConfig.name;
|
||||
return this.serverConfig?.name;
|
||||
}
|
||||
|
||||
setName(name: string): Promise<void> {
|
||||
|
|
@ -178,7 +178,8 @@ export class ShadowboxServer implements server.Server {
|
|||
|
||||
getHostnameForAccessKeys(): string {
|
||||
try {
|
||||
return this.serverConfig.hostnameForAccessKeys || new URL(this.managementApiAddress).hostname;
|
||||
return this.serverConfig?.hostnameForAccessKeys ??
|
||||
new URL(this.managementApiAddress).hostname;
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
|
|
@ -207,9 +208,9 @@ export class ShadowboxServer implements server.Server {
|
|||
});
|
||||
}
|
||||
|
||||
private getServerConfig(): Promise<ServerConfig> {
|
||||
private async getServerConfig(): Promise<ServerConfig> {
|
||||
console.info('Retrieving server configuration');
|
||||
return this.apiRequest<ServerConfig>('server');
|
||||
return await this.apiRequest<ServerConfig>('server');
|
||||
}
|
||||
|
||||
protected setManagementApiUrl(apiAddress: string): void {
|
||||
|
|
|
|||
|
|
@ -279,7 +279,11 @@ Polymer({
|
|||
},
|
||||
|
||||
connectToDigitalOceanTapped: function() {
|
||||
this.fire('ConnectToDigitalOcean');
|
||||
if (this.isSignedInToDigitalOcean) {
|
||||
this.fire('StartDigitalOceanCreateServerRequested');
|
||||
} else {
|
||||
this.fire('StartDigitalOceanConnectAccountRequested');
|
||||
}
|
||||
},
|
||||
|
||||
setUpGenericCloudProviderTapped: function() {
|
||||
|
|
|
|||
523
yarn.lock
523
yarn.lock
|
|
@ -1259,6 +1259,11 @@ acorn@^6.4.1:
|
|||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474"
|
||||
integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==
|
||||
|
||||
after@0.8.2:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
|
||||
integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=
|
||||
|
||||
agent-base@5:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c"
|
||||
|
|
@ -1563,6 +1568,11 @@ array-unique@^0.3.2:
|
|||
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
|
||||
integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
|
||||
|
||||
arraybuffer.slice@~0.0.7:
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675"
|
||||
integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==
|
||||
|
||||
arrify@^1.0.0, arrify@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
|
||||
|
|
@ -1696,6 +1706,11 @@ bach@^1.0.0:
|
|||
async-settle "^1.0.0"
|
||||
now-and-later "^2.0.0"
|
||||
|
||||
backo2@1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
|
||||
integrity sha1-MasayLEpNjRj41s+u2n038+6eUc=
|
||||
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
|
|
@ -1706,11 +1721,26 @@ base-64@^0.1.0:
|
|||
resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb"
|
||||
integrity sha1-eAqZyE59YAJgNhURxId2E78k9rs=
|
||||
|
||||
base64-arraybuffer@0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz#9818c79e059b1355f97e0428a017c838e90ba812"
|
||||
integrity sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=
|
||||
|
||||
base64-arraybuffer@0.1.5:
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
|
||||
integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg=
|
||||
|
||||
base64-js@^1.0.2, base64-js@^1.3.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
|
||||
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
|
||||
|
||||
base64id@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6"
|
||||
integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==
|
||||
|
||||
base@^0.11.1:
|
||||
version "0.11.2"
|
||||
resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
|
||||
|
|
@ -1736,6 +1766,13 @@ bcrypt-pbkdf@^1.0.0:
|
|||
dependencies:
|
||||
tweetnacl "^0.14.3"
|
||||
|
||||
better-assert@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522"
|
||||
integrity sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=
|
||||
dependencies:
|
||||
callsite "1.0.0"
|
||||
|
||||
big.js@^5.1.2, big.js@^5.2.2:
|
||||
version "5.2.2"
|
||||
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
|
||||
|
|
@ -1797,6 +1834,11 @@ bl@^1.0.0:
|
|||
readable-stream "^2.3.5"
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
blob@0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
|
||||
integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==
|
||||
|
||||
bluebird-lst@^1.0.9:
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/bluebird-lst/-/bluebird-lst-1.0.9.tgz#a64a0e4365658b9ab5fe875eb9dfb694189bb41c"
|
||||
|
|
@ -1824,7 +1866,7 @@ bn.js@^5.1.1:
|
|||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.2.tgz#c9686902d3c9a27729f43ab10f9d79c2004da7b0"
|
||||
integrity sha512-40rZaf3bUNKTVYu9sIeeEGOg7g14Yvnj9kH7b50EiwX0Q7A6umbvfI5tvHaOERH0XigqKkfLkFQxzb4e6CIXnA==
|
||||
|
||||
body-parser@1.19.0, body-parser@^1.18.3:
|
||||
body-parser@1.19.0, body-parser@^1.18.3, body-parser@^1.19.0:
|
||||
version "1.19.0"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
|
||||
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
|
||||
|
|
@ -1900,7 +1942,7 @@ braces@^2.3.1, braces@^2.3.2:
|
|||
split-string "^3.0.2"
|
||||
to-regex "^3.0.1"
|
||||
|
||||
braces@^3.0.1, braces@~3.0.2:
|
||||
braces@^3.0.1, braces@^3.0.2, braces@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
|
||||
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
|
||||
|
|
@ -2190,6 +2232,11 @@ caller-path@^2.0.0:
|
|||
dependencies:
|
||||
caller-callsite "^2.0.0"
|
||||
|
||||
callsite@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
|
||||
integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA=
|
||||
|
||||
callsites@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50"
|
||||
|
|
@ -2308,6 +2355,21 @@ chokidar@^3.4.0:
|
|||
optionalDependencies:
|
||||
fsevents "~2.1.2"
|
||||
|
||||
chokidar@^3.4.2:
|
||||
version "3.4.3"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b"
|
||||
integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==
|
||||
dependencies:
|
||||
anymatch "~3.1.1"
|
||||
braces "~3.0.2"
|
||||
glob-parent "~5.1.0"
|
||||
is-binary-path "~2.1.0"
|
||||
is-glob "~4.0.1"
|
||||
normalize-path "~3.0.0"
|
||||
readdirp "~3.5.0"
|
||||
optionalDependencies:
|
||||
fsevents "~2.1.2"
|
||||
|
||||
chownr@^1.1.1:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
|
||||
|
|
@ -2406,6 +2468,15 @@ clone-buffer@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
|
||||
integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg=
|
||||
|
||||
clone-deep@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
|
||||
integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==
|
||||
dependencies:
|
||||
is-plain-object "^2.0.4"
|
||||
kind-of "^6.0.2"
|
||||
shallow-clone "^3.0.0"
|
||||
|
||||
clone-response@1.0.2, clone-response@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
|
||||
|
|
@ -2483,6 +2554,11 @@ color-support@^1.1.3:
|
|||
resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
|
||||
integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
|
||||
|
||||
colors@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
|
||||
integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
|
||||
|
||||
colors@~0.6.0-1:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/colors/-/colors-0.6.2.tgz#2423fe6678ac0c5dae8852e5d0e5be08c997abcc"
|
||||
|
|
@ -2515,11 +2591,26 @@ commondir@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
|
||||
integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
|
||||
|
||||
component-emitter@^1.2.1:
|
||||
component-bind@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1"
|
||||
integrity sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=
|
||||
|
||||
component-emitter@1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
|
||||
integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=
|
||||
|
||||
component-emitter@^1.2.1, component-emitter@~1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
|
||||
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
|
||||
|
||||
component-inherit@0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
|
||||
integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=
|
||||
|
||||
compressible@^2.0.12, compressible@~2.0.16:
|
||||
version "2.0.18"
|
||||
resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
|
||||
|
|
@ -2602,6 +2693,16 @@ connect-history-api-fallback@^1.6.0:
|
|||
resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc"
|
||||
integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==
|
||||
|
||||
connect@^3.7.0:
|
||||
version "3.7.0"
|
||||
resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8"
|
||||
integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==
|
||||
dependencies:
|
||||
debug "2.6.9"
|
||||
finalhandler "1.1.2"
|
||||
parseurl "~1.3.3"
|
||||
utils-merge "1.0.1"
|
||||
|
||||
console-browserify@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336"
|
||||
|
|
@ -2636,16 +2737,16 @@ cookie-signature@1.0.6:
|
|||
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
|
||||
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
|
||||
|
||||
cookie@0.3.1, cookie@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
|
||||
integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
|
||||
|
||||
cookie@0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
|
||||
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
|
||||
|
||||
cookie@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
|
||||
integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
|
||||
|
||||
copy-concurrently@^1.0.0:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
|
||||
|
|
@ -2880,6 +2981,11 @@ currently-unhandled@^0.4.1:
|
|||
dependencies:
|
||||
array-find-index "^1.0.1"
|
||||
|
||||
custom-event@~1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
|
||||
integrity sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=
|
||||
|
||||
cyclist@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
||||
|
|
@ -2905,6 +3011,16 @@ date-and-time@^0.6.3:
|
|||
resolved "https://registry.yarnpkg.com/date-and-time/-/date-and-time-0.6.3.tgz#2daee52df67c28bd93bce862756ac86b68cf4237"
|
||||
integrity sha512-lcWy3AXDRJOD7MplwZMmNSRM//kZtJaLz4n6D1P5z9wEmZGBKhJRBIr1Xs9KNQJmdXPblvgffynYji4iylUTcA==
|
||||
|
||||
date-format@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/date-format/-/date-format-2.1.0.tgz#31d5b5ea211cf5fd764cd38baf9d033df7e125cf"
|
||||
integrity sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==
|
||||
|
||||
date-format@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/date-format/-/date-format-3.0.0.tgz#eb8780365c7d2b1511078fb491e6479780f3ad95"
|
||||
integrity sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==
|
||||
|
||||
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
|
|
@ -2912,7 +3028,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
|
|||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@4, debug@^4.1.0, debug@^4.1.1:
|
||||
debug@4, debug@^4.1.0, debug@^4.1.1, debug@~4.1.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
|
||||
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
|
||||
|
|
@ -2926,6 +3042,13 @@ debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5:
|
|||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@~3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||
|
|
@ -3126,6 +3249,11 @@ detect-node@^2.0.4:
|
|||
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
|
||||
integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==
|
||||
|
||||
di@^0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c"
|
||||
integrity sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=
|
||||
|
||||
diff@^4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||
|
|
@ -3187,6 +3315,16 @@ dom-converter@^0.2:
|
|||
dependencies:
|
||||
utila "~0.4"
|
||||
|
||||
dom-serialize@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b"
|
||||
integrity sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=
|
||||
dependencies:
|
||||
custom-event "~1.0.0"
|
||||
ent "~2.2.0"
|
||||
extend "^3.0.0"
|
||||
void-elements "^2.0.0"
|
||||
|
||||
dom-serializer@0:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
|
||||
|
|
@ -3508,6 +3646,46 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1:
|
|||
dependencies:
|
||||
once "^1.4.0"
|
||||
|
||||
engine.io-client@~3.4.0:
|
||||
version "3.4.4"
|
||||
resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.4.4.tgz#77d8003f502b0782dd792b073a4d2cf7ca5ab967"
|
||||
integrity sha512-iU4CRr38Fecj8HoZEnFtm2EiKGbYZcPn3cHxqNGl/tmdWRf60KhK+9vE0JeSjgnlS/0oynEfLgKbT9ALpim0sQ==
|
||||
dependencies:
|
||||
component-emitter "~1.3.0"
|
||||
component-inherit "0.0.3"
|
||||
debug "~3.1.0"
|
||||
engine.io-parser "~2.2.0"
|
||||
has-cors "1.1.0"
|
||||
indexof "0.0.1"
|
||||
parseqs "0.0.6"
|
||||
parseuri "0.0.6"
|
||||
ws "~6.1.0"
|
||||
xmlhttprequest-ssl "~1.5.4"
|
||||
yeast "0.1.2"
|
||||
|
||||
engine.io-parser@~2.2.0:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.2.1.tgz#57ce5611d9370ee94f99641b589f94c97e4f5da7"
|
||||
integrity sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==
|
||||
dependencies:
|
||||
after "0.8.2"
|
||||
arraybuffer.slice "~0.0.7"
|
||||
base64-arraybuffer "0.1.4"
|
||||
blob "0.0.5"
|
||||
has-binary2 "~1.0.2"
|
||||
|
||||
engine.io@~3.4.0:
|
||||
version "3.4.2"
|
||||
resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.4.2.tgz#8fc84ee00388e3e228645e0a7d3dfaeed5bd122c"
|
||||
integrity sha512-b4Q85dFkGw+TqgytGPrGgACRUhsdKc9S9ErRAXpPGy/CXKs4tYoHDkvIRdsseAF7NjfVwjRFIn6KTnbw7LwJZg==
|
||||
dependencies:
|
||||
accepts "~1.3.4"
|
||||
base64id "2.0.0"
|
||||
cookie "0.3.1"
|
||||
debug "~4.1.0"
|
||||
engine.io-parser "~2.2.0"
|
||||
ws "^7.1.2"
|
||||
|
||||
enhanced-resolve@4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f"
|
||||
|
|
@ -3526,7 +3704,7 @@ enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0:
|
|||
memory-fs "^0.5.0"
|
||||
tapable "^1.0.0"
|
||||
|
||||
ent@^2.2.0:
|
||||
ent@^2.2.0, ent@~2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d"
|
||||
integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0=
|
||||
|
|
@ -4059,7 +4237,7 @@ fill-range@^7.0.1:
|
|||
dependencies:
|
||||
to-regex-range "^5.0.1"
|
||||
|
||||
finalhandler@~1.1.2:
|
||||
finalhandler@1.1.2, finalhandler@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
|
||||
integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
|
||||
|
|
@ -4157,6 +4335,11 @@ flagged-respawn@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.1.tgz#e7de6f1279ddd9ca9aac8a5971d618606b3aab41"
|
||||
integrity sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==
|
||||
|
||||
flatted@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"
|
||||
integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==
|
||||
|
||||
flush-write-stream@^1.0.0, flush-write-stream@^1.0.2:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
|
||||
|
|
@ -4470,7 +4653,7 @@ glob@^6.0.1:
|
|||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
|
||||
glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
|
||||
version "7.1.6"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
|
||||
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
|
||||
|
|
@ -4688,7 +4871,7 @@ got@^9.6.0:
|
|||
to-readable-stream "^1.0.0"
|
||||
url-parse-lax "^3.0.0"
|
||||
|
||||
graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0:
|
||||
graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.4:
|
||||
version "4.2.4"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
|
||||
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
|
||||
|
|
@ -4789,6 +4972,18 @@ has-ansi@^2.0.0:
|
|||
dependencies:
|
||||
ansi-regex "^2.0.0"
|
||||
|
||||
has-binary2@~1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d"
|
||||
integrity sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==
|
||||
dependencies:
|
||||
isarray "2.0.1"
|
||||
|
||||
has-cors@1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39"
|
||||
integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=
|
||||
|
||||
has-flag@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||
|
|
@ -5041,7 +5236,7 @@ http-proxy-middleware@0.19.1:
|
|||
lodash "^4.17.11"
|
||||
micromatch "^3.1.10"
|
||||
|
||||
http-proxy@^1.17.0:
|
||||
http-proxy@^1.17.0, http-proxy@^1.18.1:
|
||||
version "1.18.1"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549"
|
||||
integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==
|
||||
|
|
@ -5195,6 +5390,11 @@ indexes-of@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
|
||||
integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc=
|
||||
|
||||
indexof@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
|
||||
integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=
|
||||
|
||||
infer-owner@^1.0.3:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
|
||||
|
|
@ -5708,7 +5908,12 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
||||
|
||||
isbinaryfile@^4.0.4:
|
||||
isarray@2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
|
||||
integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=
|
||||
|
||||
isbinaryfile@^4.0.4, isbinaryfile@^4.0.6:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b"
|
||||
integrity sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg==
|
||||
|
|
@ -5762,6 +5967,11 @@ jake@^10.6.1:
|
|||
filelist "^1.0.1"
|
||||
minimatch "^3.0.4"
|
||||
|
||||
jasmine-core@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.6.0.tgz#491f3bb23941799c353ceb7a45b38a950ebc5a20"
|
||||
integrity sha512-8uQYa7zJN8hq9z+g8z1bqCfdC8eoDAeVnM5sfqs7KHv9/ifoJ500m018fpFc7RDaO6SWCLCXwo/wPSNcdYTgcw==
|
||||
|
||||
jasmine-core@~3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.5.0.tgz#132c23e645af96d85c8bca13c8758b18429fc1e4"
|
||||
|
|
@ -5936,6 +6146,61 @@ jws@^3.1.5:
|
|||
jwa "^1.4.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
karma-chrome-launcher@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.1.0.tgz#805a586799a4d05f4e54f72a204979f3f3066738"
|
||||
integrity sha512-3dPs/n7vgz1rxxtynpzZTvb9y/GIaW8xjAwcIGttLbycqoFtI7yo1NGnQi6oFTherRE+GIhCAHZC4vEqWGhNvg==
|
||||
dependencies:
|
||||
which "^1.2.1"
|
||||
|
||||
karma-jasmine@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-4.0.1.tgz#b99e073b6d99a5196fc4bffc121b89313b0abd82"
|
||||
integrity sha512-h8XDAhTiZjJKzfkoO1laMH+zfNlra+dEQHUAjpn5JV1zCPtOIVWGQjLBrqhnzQa/hrU2XrZwSyBa6XjEBzfXzw==
|
||||
dependencies:
|
||||
jasmine-core "^3.6.0"
|
||||
|
||||
karma-webpack@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-4.0.2.tgz#23219bd95bdda853e3073d3874d34447c77bced0"
|
||||
integrity sha512-970/okAsdUOmiMOCY8sb17A2I8neS25Ad9uhyK3GHgmRSIFJbDcNEFE8dqqUhNe9OHiCC9k3DMrSmtd/0ymP1A==
|
||||
dependencies:
|
||||
clone-deep "^4.0.1"
|
||||
loader-utils "^1.1.0"
|
||||
neo-async "^2.6.1"
|
||||
schema-utils "^1.0.0"
|
||||
source-map "^0.7.3"
|
||||
webpack-dev-middleware "^3.7.0"
|
||||
|
||||
karma@^5.2.3:
|
||||
version "5.2.3"
|
||||
resolved "https://registry.yarnpkg.com/karma/-/karma-5.2.3.tgz#3264024219bad2728e92542e0058a2492d7a46e4"
|
||||
integrity sha512-tHdyFADhVVPBorIKCX8A37iLHxc6RBRphkSoQ+MLKdAtFn1k97tD8WUGi1KlEtDZKL3hui0qhsY9HXUfSNDYPQ==
|
||||
dependencies:
|
||||
body-parser "^1.19.0"
|
||||
braces "^3.0.2"
|
||||
chokidar "^3.4.2"
|
||||
colors "^1.4.0"
|
||||
connect "^3.7.0"
|
||||
di "^0.0.1"
|
||||
dom-serialize "^2.2.1"
|
||||
glob "^7.1.6"
|
||||
graceful-fs "^4.2.4"
|
||||
http-proxy "^1.18.1"
|
||||
isbinaryfile "^4.0.6"
|
||||
lodash "^4.17.19"
|
||||
log4js "^6.2.1"
|
||||
mime "^2.4.5"
|
||||
minimatch "^3.0.4"
|
||||
qjobs "^1.2.0"
|
||||
range-parser "^1.2.1"
|
||||
rimraf "^3.0.2"
|
||||
socket.io "^2.3.0"
|
||||
source-map "^0.6.1"
|
||||
tmp "0.2.1"
|
||||
ua-parser-js "0.7.22"
|
||||
yargs "^15.3.1"
|
||||
|
||||
kew@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/kew/-/kew-0.7.0.tgz#79d93d2d33363d6fdd2970b335d9141ad591d79b"
|
||||
|
|
@ -6104,7 +6369,7 @@ loader-utils@1.2.3:
|
|||
emojis-list "^2.0.0"
|
||||
json5 "^1.0.1"
|
||||
|
||||
loader-utils@^1.0.2, loader-utils@^1.2.3:
|
||||
loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
|
||||
integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
|
||||
|
|
@ -6152,6 +6417,22 @@ lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.
|
|||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
|
||||
integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
|
||||
|
||||
lodash@^4.17.19:
|
||||
version "4.17.20"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
||||
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
||||
|
||||
log4js@^6.2.1:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.3.0.tgz#10dfafbb434351a3e30277a00b9879446f715bcb"
|
||||
integrity sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==
|
||||
dependencies:
|
||||
date-format "^3.0.0"
|
||||
debug "^4.1.1"
|
||||
flatted "^2.0.1"
|
||||
rfdc "^1.1.4"
|
||||
streamroller "^2.2.4"
|
||||
|
||||
loglevel@^1.6.8:
|
||||
version "1.6.8"
|
||||
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171"
|
||||
|
|
@ -6414,6 +6695,11 @@ mime@^2.2.0, mime@^2.4.3, mime@^2.4.4:
|
|||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1"
|
||||
integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==
|
||||
|
||||
mime@^2.4.5:
|
||||
version "2.4.7"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.7.tgz#962aed9be0ed19c91fd7dc2ece5d7f4e89a90d74"
|
||||
integrity sha512-dhNd1uA2u397uQk3Nv5LM4lm93WYDUXFn3Fu291FJerns4jyTudqhIWe4W04YLy7Uk1tm1Ore04NpjRvQp/NPA==
|
||||
|
||||
mimic-fn@^2.0.0, mimic-fn@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||
|
|
@ -6786,6 +7072,11 @@ object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
|||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
||||
|
||||
object-component@0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291"
|
||||
integrity sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=
|
||||
|
||||
object-copy@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c"
|
||||
|
|
@ -7168,6 +7459,30 @@ parse-passwd@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
|
||||
integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=
|
||||
|
||||
parseqs@0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d"
|
||||
integrity sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=
|
||||
dependencies:
|
||||
better-assert "~1.0.0"
|
||||
|
||||
parseqs@0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.6.tgz#8e4bb5a19d1cdc844a08ac974d34e273afa670d5"
|
||||
integrity sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==
|
||||
|
||||
parseuri@0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a"
|
||||
integrity sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=
|
||||
dependencies:
|
||||
better-assert "~1.0.0"
|
||||
|
||||
parseuri@0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.6.tgz#e1496e829e3ac2ff47f39a4dd044b32823c4a25a"
|
||||
integrity sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==
|
||||
|
||||
parseurl@~1.3.2, parseurl@~1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||
|
|
@ -7689,6 +8004,11 @@ pupa@^2.0.1:
|
|||
dependencies:
|
||||
escape-goat "^2.0.0"
|
||||
|
||||
qjobs@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071"
|
||||
integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==
|
||||
|
||||
qs@6.7.0:
|
||||
version "6.7.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
||||
|
|
@ -7856,6 +8176,13 @@ readdirp@~3.4.0:
|
|||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
readdirp@~3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e"
|
||||
integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==
|
||||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
rechoir@^0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
|
||||
|
|
@ -8153,6 +8480,11 @@ retry@^0.12.0:
|
|||
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
|
||||
integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
|
||||
|
||||
rfdc@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2"
|
||||
integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==
|
||||
|
||||
rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.3:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
|
||||
|
|
@ -8160,6 +8492,13 @@ rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.3:
|
|||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
rimraf@^3.0.0, rimraf@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
|
||||
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
|
||||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
rimraf@~2.4.0:
|
||||
version "2.4.5"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.4.5.tgz#ee710ce5d93a8fdb856fb5ea8ff0e2d75934b2da"
|
||||
|
|
@ -8442,6 +8781,13 @@ sha.js@^2.4.0, sha.js@^2.4.8:
|
|||
inherits "^2.0.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
shallow-clone@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
|
||||
integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
|
||||
dependencies:
|
||||
kind-of "^6.0.2"
|
||||
|
||||
shebang-command@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
|
||||
|
|
@ -8516,6 +8862,61 @@ snapdragon@^0.8.1:
|
|||
source-map-resolve "^0.5.0"
|
||||
use "^3.1.0"
|
||||
|
||||
socket.io-adapter@~1.1.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz#ab3f0d6f66b8fc7fca3959ab5991f82221789be9"
|
||||
integrity sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==
|
||||
|
||||
socket.io-client@2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.3.0.tgz#14d5ba2e00b9bcd145ae443ab96b3f86cbcc1bb4"
|
||||
integrity sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==
|
||||
dependencies:
|
||||
backo2 "1.0.2"
|
||||
base64-arraybuffer "0.1.5"
|
||||
component-bind "1.0.0"
|
||||
component-emitter "1.2.1"
|
||||
debug "~4.1.0"
|
||||
engine.io-client "~3.4.0"
|
||||
has-binary2 "~1.0.2"
|
||||
has-cors "1.1.0"
|
||||
indexof "0.0.1"
|
||||
object-component "0.0.3"
|
||||
parseqs "0.0.5"
|
||||
parseuri "0.0.5"
|
||||
socket.io-parser "~3.3.0"
|
||||
to-array "0.1.4"
|
||||
|
||||
socket.io-parser@~3.3.0:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.1.tgz#f07d9c8cb3fb92633aa93e76d98fd3a334623199"
|
||||
integrity sha512-1QLvVAe8dTz+mKmZ07Swxt+LAo4Y1ff50rlyoEx00TQmDFVQYPfcqGvIDJLGaBdhdNCecXtyKpD+EgKGcmmbuQ==
|
||||
dependencies:
|
||||
component-emitter "~1.3.0"
|
||||
debug "~3.1.0"
|
||||
isarray "2.0.1"
|
||||
|
||||
socket.io-parser@~3.4.0:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.4.1.tgz#b06af838302975837eab2dc980037da24054d64a"
|
||||
integrity sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==
|
||||
dependencies:
|
||||
component-emitter "1.2.1"
|
||||
debug "~4.1.0"
|
||||
isarray "2.0.1"
|
||||
|
||||
socket.io@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.3.0.tgz#cd762ed6a4faeca59bc1f3e243c0969311eb73fb"
|
||||
integrity sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==
|
||||
dependencies:
|
||||
debug "~4.1.0"
|
||||
engine.io "~3.4.0"
|
||||
has-binary2 "~1.0.2"
|
||||
socket.io-adapter "~1.1.0"
|
||||
socket.io-client "2.3.0"
|
||||
socket.io-parser "~3.4.0"
|
||||
|
||||
sockjs-client@1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5"
|
||||
|
|
@ -8597,6 +8998,11 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
|
|||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
source-map@^0.7.3:
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
|
||||
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
|
||||
|
||||
sparkles@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.1.tgz#008db65edce6c50eec0c5e228e1945061dd0437c"
|
||||
|
|
@ -8788,6 +9194,15 @@ stream-transform@^2.0.1:
|
|||
dependencies:
|
||||
mixme "^0.3.1"
|
||||
|
||||
streamroller@^2.2.4:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-2.2.4.tgz#c198ced42db94086a6193608187ce80a5f2b0e53"
|
||||
integrity sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==
|
||||
dependencies:
|
||||
date-format "^2.1.0"
|
||||
debug "^4.1.1"
|
||||
fs-extra "^8.1.0"
|
||||
|
||||
strict-uri-encode@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
|
||||
|
|
@ -9170,6 +9585,13 @@ tinycolor2@^1.1.2:
|
|||
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8"
|
||||
integrity sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=
|
||||
|
||||
tmp@0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
|
||||
integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==
|
||||
dependencies:
|
||||
rimraf "^3.0.0"
|
||||
|
||||
to-absolute-glob@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b"
|
||||
|
|
@ -9178,6 +9600,11 @@ to-absolute-glob@^2.0.0:
|
|||
is-absolute "^1.0.0"
|
||||
is-negated-glob "^1.0.0"
|
||||
|
||||
to-array@0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890"
|
||||
integrity sha1-F+bBH3PdTz10zaek/zI46a2b+JA=
|
||||
|
||||
to-arraybuffer@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
|
||||
|
|
@ -9368,6 +9795,11 @@ typescript@^3.8.3:
|
|||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.3.tgz#d3ac8883a97c26139e42df5e93eeece33d610b8a"
|
||||
integrity sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==
|
||||
|
||||
ua-parser-js@0.7.22:
|
||||
version "0.7.22"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.22.tgz#960df60a5f911ea8f1c818f3747b99c6e177eae3"
|
||||
integrity sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q==
|
||||
|
||||
unbzip2-stream@^1.0.9:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7"
|
||||
|
|
@ -9714,6 +10146,11 @@ vm-browserify@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
|
||||
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
|
||||
|
||||
void-elements@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
|
||||
integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
|
||||
|
||||
watchpack-chokidar2@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0"
|
||||
|
|
@ -9761,6 +10198,17 @@ webpack-cli@^3.3.11:
|
|||
v8-compile-cache "2.0.3"
|
||||
yargs "13.2.4"
|
||||
|
||||
webpack-dev-middleware@^3.7.0:
|
||||
version "3.7.3"
|
||||
resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz#0639372b143262e2b84ab95d3b91a7597061c2c5"
|
||||
integrity sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ==
|
||||
dependencies:
|
||||
memory-fs "^0.4.1"
|
||||
mime "^2.4.4"
|
||||
mkdirp "^0.5.1"
|
||||
range-parser "^1.2.1"
|
||||
webpack-log "^2.0.0"
|
||||
|
||||
webpack-dev-middleware@^3.7.2:
|
||||
version "3.7.2"
|
||||
resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3"
|
||||
|
|
@ -9887,7 +10335,7 @@ which-module@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
|
||||
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
|
||||
|
||||
which@^1.2.10, which@^1.2.14, which@^1.2.9, which@^1.3.1:
|
||||
which@^1.2.1, which@^1.2.10, which@^1.2.14, which@^1.2.9, which@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
|
||||
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
|
||||
|
|
@ -9977,6 +10425,18 @@ ws@^6.2.1:
|
|||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
|
||||
ws@^7.1.2:
|
||||
version "7.4.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.1.tgz#a333be02696bd0e54cea0434e21dcc8a9ac294bb"
|
||||
integrity sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==
|
||||
|
||||
ws@~6.1.0:
|
||||
version "6.1.4"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9"
|
||||
integrity sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==
|
||||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
|
||||
xdg-basedir@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
|
||||
|
|
@ -10015,6 +10475,11 @@ xmlbuilder@~11.0.0:
|
|||
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
|
||||
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
|
||||
|
||||
xmlhttprequest-ssl@~1.5.4:
|
||||
version "1.5.5"
|
||||
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"
|
||||
integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=
|
||||
|
||||
xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
|
|
@ -10056,7 +10521,7 @@ yargs-parser@^13.1.0, yargs-parser@^13.1.2:
|
|||
camelcase "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
|
||||
yargs-parser@^18.1.1:
|
||||
yargs-parser@^18.1.1, yargs-parser@^18.1.2:
|
||||
version "18.1.3"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
|
||||
integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
|
||||
|
|
@ -10122,6 +10587,23 @@ yargs@^15.1.0:
|
|||
y18n "^4.0.0"
|
||||
yargs-parser "^18.1.1"
|
||||
|
||||
yargs@^15.3.1:
|
||||
version "15.4.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
|
||||
integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
|
||||
dependencies:
|
||||
cliui "^6.0.0"
|
||||
decamelize "^1.2.0"
|
||||
find-up "^4.1.0"
|
||||
get-caller-file "^2.0.1"
|
||||
require-directory "^2.1.1"
|
||||
require-main-filename "^2.0.0"
|
||||
set-blocking "^2.0.0"
|
||||
string-width "^4.2.0"
|
||||
which-module "^2.0.0"
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^18.1.2"
|
||||
|
||||
yargs@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-5.0.0.tgz#3355144977d05757dbb86d6e38ec056123b3a66e"
|
||||
|
|
@ -10168,3 +10650,8 @@ yauzl@^2.10.0, yauzl@^2.4.2:
|
|||
dependencies:
|
||||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.1.0"
|
||||
|
||||
yeast@0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"
|
||||
integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk=
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue