"magic_pdf/vscode:/vscode.git/clone" did not exist on "1c74ed8ea8991e27935544a8adc21740a354cd39"
Unverified Commit 9392125e authored by liuzhe-lz's avatar liuzhe-lz Committed by GitHub
Browse files

Refactor REST server class (#4486)

parent 5a017181
......@@ -3,6 +3,11 @@ from typing import Any, Optional
import requests
# API URL must be synchronized with:
# - ts/nni_manager/rest_server/index.ts
# - ts/webui/src/static/constant.ts
# Remember to update them if the values are changed, or if this file is moved.
_logger = logging.getLogger(__name__)
timeout = 20
......
......@@ -83,12 +83,14 @@ export class Logger {
const message = args.map(arg => (typeof arg === 'string' ? arg : util.inspect(arg))).join(' ');
const record = `[${datetime}] ${levelName} (${this.name}) ${message}\n`;
const record = `[${datetime}] ${levelName} (${this.name}) ${message}`;
if (logFile === undefined) {
if (!isUnitTest()) { // be quite for unit test
console.log(record);
}
} else {
logFile.write(record);
logFile.write(record + '\n');
}
}
}
......@@ -130,3 +132,10 @@ export function stopLogging(): void {
(global as any).logFile = undefined;
}
}
/* utilities */
function isUnitTest(): boolean {
const event = process.env['npm_lifecycle_event'] ?? '';
return event.startsWith('test') || event === 'mocha' || event === 'nyc';
}
......@@ -14,7 +14,7 @@ import { getBasePort } from './experimentStartupInfo';
* The module who wants to use a RestServer could <b>extends</b> this abstract class
* And implement its own registerRestHandler() function to register routers
*/
export abstract class RestServer {
export abstract class LegacyRestServer {
private startTask!: Deferred<void>;
private stopTask!: Deferred<void>;
private server!: http.Server;
......
......@@ -25,7 +25,7 @@ import {
REPORT_METRIC_DATA, REQUEST_TRIAL_JOBS, SEND_TRIAL_JOB_PARAMETER, TERMINATE, TRIAL_END, UPDATE_SEARCH_SPACE, IMPORT_DATA
} from './commands';
import { createDispatcherInterface, createDispatcherPipeInterface, IpcInterface } from './ipcInterface';
import { NNIRestServer } from '../rest_server/nniRestServer';
import { RestServer } from '../rest_server';
/**
* NNIManager which implements Manager interface
......@@ -355,7 +355,7 @@ class NNIManager implements Manager {
await this.experimentManager.stop();
await component.get<TensorboardManager>(TensorboardManager).stop();
await this.dataStore.close();
await component.get<NNIRestServer>(NNIRestServer).stop();
await component.get<RestServer>(RestServer).shutdown();
} catch (err) {
hasError = true;
this.log.error(`${err.stack}`);
......
......@@ -19,8 +19,7 @@ import { NNIManager } from './core/nnimanager';
import { SqlDB } from './core/sqlDatabase';
import { NNIExperimentsManager } from './core/nniExperimentsManager';
import { NNITensorboardManager } from './core/nniTensorboardManager';
import { NNIRestServer } from './rest_server/nniRestServer';
import { RestServer } from './rest_server';
function initStartupInfo(
startExpMode: string, experimentId: string, basePort: number, platform: string,
......@@ -124,9 +123,8 @@ mkDirP(getLogDir())
.then(async () => {
try {
await initContainer(foreground, mode);
const restServer: NNIRestServer = component.get(NNIRestServer);
const restServer: RestServer = component.get(RestServer);
await restServer.start();
getLogger('main').info(`Rest server listening on: ${restServer.endPoint}`);
} catch (err) {
getLogger('main').error(`${err.stack}`);
throw err;
......
......@@ -14,7 +14,7 @@
"app-module-path": "^2.2.0",
"azure-storage": "^2.10.6",
"child-process-promise": "^2.2.1",
"express": "^4.17.1",
"express": "^4.17.2",
"express-joi-validator": "^2.0.1",
"http-proxy": "^1.18.1",
"ignore": "^5.1.8",
......@@ -46,6 +46,7 @@
"@types/lockfile": "^1.0.0",
"@types/mocha": "^8.2.2",
"@types/node": "^15.12.1",
"@types/node-fetch": "<3.0.0",
"@types/request": "^2.48.5",
"@types/rx": "^4.1.2",
"@types/sqlite3": "^3.1.7",
......@@ -61,6 +62,7 @@
"eslint": "^7.28.0",
"glob": "^7.1.7",
"mocha": "^9.0.2",
"node-fetch": "<3.0.0",
"nyc": "^15.1.0",
"request": "^2.88.2",
"rmdir": "^1.2.0",
......
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
/**
* Currently the REST server that dispatches web UI and `Experiment` requests.
* In future it should handle WebSocket connections as well.
*
* To add new APIs to REST server, modify `rootRouter()` function.
*
* This file contains API URL constants. They must be synchronized with:
* - nni/experiment/rest.py
* - ts/webui/src/static/const.ts
* - ts/webui/src/components/public-child/OpenRow.tsx
* Remember to update them if the values are changed, or if this file is moved.
*
* TODO:
* 1. Add a global function to handle critical error.
* 2. Refactor ClusterJobRestServer to an express-ws application so it doesn't require extra port.
* 3. Provide public API to register express app, so this can be decoupled with other modules' implementation.
* 4. Refactor NNIRestHandler. It's a mess.
* 5. Get rid of IOC.
* 6. Deal with log path mismatch between REST API and file system.
* 7. Strip slashes of URL prefix inside ExperimentStartupInfo.
**/
import type { Server } from 'http';
import type { AddressInfo } from 'net';
import path from 'path';
import express, { Request, Response, Router } from 'express';
import httpProxy from 'http-proxy';
import { Deferred } from 'ts-deferred';
import { Singleton } from 'common/component';
import { getBasePort, getPrefixUrl } from 'common/experimentStartupInfo';
import { Logger, getLogger } from 'common/log';
import { getLogDir } from 'common/utils';
import { createRestHandler } from './restHandler';
/**
* The singleton REST server that dispatches web UI and `Experiment` requests.
*
* RestServer must be initialized with start() after NNI manager constructing, but not necessarily after initializing.
* This is because RestServer needs NNI manager instance to register API handlers.
**/
@Singleton
export class RestServer {
private port: number;
private urlPrefix: string;
private server: Server | null = null;
private logger: Logger = getLogger('RestServer');
// I would prefer to get port and urlPrefix by constructor parameters,
// but this is impossible due to limitation of IOC.
constructor() {
this.port = getBasePort();
// Stripping slashes should be done inside ExperimentInfo, but I don't want to touch it for now.
this.urlPrefix = '/' + stripSlashes(getPrefixUrl());
}
// The promise is resolved when it's ready to serve requests.
// This worth nothing for now,
// but for example if we connect to tuner using WebSocket then it must be launched after promise resolved.
public start(): Promise<void> {
this.logger.info(`Starting REST server at port ${this.port}, URL prefix: "${this.urlPrefix}"`);
const app = express();
// FIXME: We should have a global handler for critical errors.
// `shutdown()` is not a callback and should not be passed to NNIRestHandler.
app.use(this.urlPrefix, rootRouter(this.shutdown.bind(this)));
app.all('*', (_req: Request, res: Response) => { res.status(404).send(`Outside prefix "${this.urlPrefix}"`); });
this.server = app.listen(this.port);
const deferred = new Deferred<void>();
this.server.on('listening', () => {
if (this.port === 0) { // Currently for unit test, can be public feature in future.
this.port = (<AddressInfo>this.server!.address()).port;
}
this.logger.info('REST server started.');
deferred.resolve();
});
// FIXME: Use global handler. The event can be emitted after listening.
this.server.on('error', (error: Error) => {
this.logger.error('REST server error:', error);
deferred.reject(error);
});
return deferred.promise;
}
public shutdown(): Promise<void> {
this.logger.info('Stopping REST server.');
if (this.server === null) {
this.logger.warning('REST server is not running.');
return Promise.resolve();
}
const deferred = new Deferred<void>();
this.server.close(() => {
this.logger.info('REST server stopped.');
deferred.resolve();
});
// FIXME: Use global handler. It should be aware of shutting down event and swallow errors in this stage.
this.server.on('error', (error: Error) => {
this.logger.error('REST server error:', error);
deferred.resolve();
});
return deferred.promise;
}
}
/**
* You will need to modify this function if you want to add a new module, for example, project management.
*
* Each module should have a unique URL prefix and a "Router". Check express' reference about Application and Router.
* Note that the order of `use()` calls does matter so you must not put a router after web UI.
*
* In fact experiments management should have a separate prefix and module.
**/
function rootRouter(stopCallback: () => Promise<void>): Router {
const router = Router();
router.use(express.json({ limit: '50mb' }));
/* NNI manager APIs */
router.use('/api/v1/nni', createRestHandler(stopCallback));
/* Download log files */
// The REST API path "/logs" does not match file system path "/log".
// Here we use an additional router to workaround this problem.
const logRouter = Router();
logRouter.get('*', express.static(getLogDir()));
router.use('/logs', logRouter);
/* NAS model visualization */
router.use('/netron', netronProxy());
/* Web UI */
router.get('*', express.static(webuiPath));
// React Router handles routing inside the browser. We must send index.html to all routes.
// path.resolve() is required by Response.sendFile() API.
router.get('*', (_req: Request, res: Response) => { res.sendFile(path.join(webuiPath, 'index.html')); });
/* 404 as catch-all */
router.all('*', (_req: Request, res: Response) => { res.status(404).send('Not Found'); });
return router;
}
function netronProxy(): Router {
const router = Router();
const proxy = httpProxy.createProxyServer();
router.all('*', (req: Request, res: Response): void => {
delete req.headers.host;
proxy.web(req, res, { changeOrigin: true, target: netronUrl });
});
return router;
}
function stripSlashes(str: string): string {
return str.replace(/^\/+/, '').replace(/\/+$/, '');
}
let webuiPath: string = path.resolve('static');
let netronUrl: string = 'https://netron.app';
export namespace UnitTestHelpers {
export function getPort(server: RestServer): number {
return (<any>server).port;
}
export function setWebuiPath(mockPath: string): void {
webuiPath = path.resolve(mockPath);
}
export function setNetronUrl(mockUrl: string): void {
netronUrl = mockUrl;
}
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import bodyParser from 'body-parser';
import express from 'express';
import httpProxy from 'http-proxy';
import path from 'path';
import * as component from '../common/component';
import { RestServer } from '../common/restServer'
import { getLogDir } from '../common/utils';
import { createRestHandler } from './restHandler';
import { getAPIRootUrl, getPrefixUrl } from '../common/experimentStartupInfo';
/**
* NNI Main rest server, provides rest API to support
* # nnictl CLI tool
* # NNI WebUI
*
*/
@component.Singleton
export class NNIRestServer extends RestServer {
private readonly LOGS_ROOT_URL: string = '/logs';
protected netronProxy: any = null;
protected API_ROOT_URL: string = '/api/v1/nni';
/**
* constructor to provide NNIRestServer's own rest property, e.g. port
*/
constructor() {
super();
this.API_ROOT_URL = getAPIRootUrl();
this.netronProxy = httpProxy.createProxyServer();
}
/**
* NNIRestServer's own router registration
*/
protected registerRestHandler(): void {
this.app.use(getPrefixUrl(), express.static('static'));
this.app.use(bodyParser.json({limit: '50mb'}));
this.app.use(this.API_ROOT_URL, createRestHandler(this));
this.app.use(this.LOGS_ROOT_URL, express.static(getLogDir()));
this.app.all('/netron/*', (req: express.Request, res: express.Response) => {
delete req.headers.host;
req.url = req.url.replace('/netron', '/');
this.netronProxy.web(req, res, {
changeOrigin: true,
target: 'https://netron.app'
});
});
this.app.get(`${getPrefixUrl()}/*`, (_req: express.Request, res: express.Response) => {
res.sendFile(path.resolve('static/index.html'));
});
}
}
......@@ -13,7 +13,6 @@ import { ExperimentProfile, Manager, TrialJobStatistics } from '../common/manage
import { ExperimentManager } from '../common/experimentManager';
import { TensorboardManager, TensorboardTaskInfo } from '../common/tensorboardManager';
import { ValidationSchemas } from './restValidationSchemas';
import { NNIRestServer } from './nniRestServer';
import { getVersion } from '../common/utils';
import { MetricType } from '../common/datastore';
import { ProfileUpdateType } from '../common/manager';
......@@ -23,17 +22,17 @@ import { TrialJobStatus } from '../common/trainingService';
//const expressJoi = require('express-joi-validator');
class NNIRestHandler {
private restServer: NNIRestServer;
private stopCallback: () => Promise<void>;
private nniManager: Manager;
private experimentsManager: ExperimentManager;
private tensorboardManager: TensorboardManager;
private log: Logger;
constructor(rs: NNIRestServer) {
constructor(stopCallback: () => Promise<void>) {
this.nniManager = component.get(Manager);
this.experimentsManager = component.get(ExperimentManager);
this.tensorboardManager = component.get(TensorboardManager);
this.restServer = rs;
this.stopCallback = stopCallback;
this.log = getLogger('NNIRestHandler');
}
......@@ -125,7 +124,7 @@ class NNIRestHandler {
this.handleError(err, res);
this.log.error(err.message);
this.log.error(`Datastore initialize failed, stopping rest server...`);
await this.restServer.stop();
await this.stopCallback();
});
});
}
......@@ -434,8 +433,8 @@ class NNIRestHandler {
}
}
export function createRestHandler(rs: NNIRestServer): Router {
const handler: NNIRestHandler = new NNIRestHandler(rs);
export function createRestHandler(stopCallback: () => Promise<void>): Router {
const handler: NNIRestHandler = new NNIRestHandler(stopCallback);
return handler.createRestHandler();
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
/**
* A helper HTTP server that sends request informantion back as response.
* Used to check that RestServer will send correct requests to netron.app.
*
* It will automatically dispose itself so no need for stop().
**/
import type { AddressInfo } from 'net';
import express, { Request, Response } from 'express';
import { Deferred } from 'ts-deferred';
export function start(): Promise<number> {
const app = express();
app.use(express.text());
app.get('*', (req: Request, res: Response) => {
res.send({
method: 'GET',
url: req.url,
headers: req.headers,
});
});
app.post('*', (req: Request, res: Response) => {
res.send({
method: 'POST',
url: req.url,
headers: req.headers,
body: req.body,
});
});
const server = app.listen();
server.unref();
const deferred = new Deferred<number>();
server.on('listening', () => {
deferred.resolve((<AddressInfo>server!.address()).port);
});
return deferred.promise;
}
......@@ -15,7 +15,7 @@ import { TrainingService } from '../../common/trainingService';
import { cleanupUnitTest, prepareUnitTest } from '../../common/utils';
import { MockedDataStore } from '../mock/datastore';
import { MockedTrainingService } from '../mock/trainingService';
import { NNIRestServer } from '../../rest_server/nniRestServer';
import { RestServer } from '../../rest_server';
import { testManagerProvider } from '../mock/nniManager';
import { testExperimentManagerProvider } from '../mock/experimentManager';
import { TensorboardManager } from '../../common/tensorboardManager';
......@@ -32,9 +32,9 @@ describe('Unit test for rest server', () => {
Container.bind(TrainingService).to(MockedTrainingService);
Container.bind(ExperimentManager).provider(testExperimentManagerProvider);
Container.bind(TensorboardManager).to(NNITensorboardManager);
const restServer: NNIRestServer = component.get(NNIRestServer);
const restServer: RestServer = component.get(RestServer);
restServer.start().then(() => {
ROOT_URL = `${restServer.endPoint}/api/v1/nni`;
ROOT_URL = `http://localhost:8080/api/v1/nni`;
done();
}).catch((e: Error) => {
assert.fail(`Failed to start rest server: ${e.message}`);
......@@ -42,7 +42,7 @@ describe('Unit test for rest server', () => {
});
after(() => {
component.get<NNIRestServer>(NNIRestServer).stop();
component.get<RestServer>(RestServer).shutdown();
cleanupUnitTest();
});
......
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import assert from 'assert';
import fs from 'fs';
import path from 'path';
import fetch from 'node-fetch';
import { setExperimentStartupInfo } from 'common/experimentStartupInfo';
import { RestServer, UnitTestHelpers } from 'rest_server';
import * as mock_netron_server from './mock_netron_server';
let restServer: RestServer;
let endPoint: string;
let endPointWithoutPrefix: string;
let netronHost: string;
const logFileContent = fs.readFileSync(path.join(__dirname, 'log/mock.log'));
const webuiIndexContent = fs.readFileSync(path.join(__dirname, 'static/index.html'));
const webuiScriptContent = fs.readFileSync(path.join(__dirname, 'static/script.js'));
/* Test cases */
// Test cases will be run twice, the first time without URL prefix, and the second time with a URL prefix.
// NNI manager APIs are covered by old tests.
// In future RestServer should not be responsible for API implementation.
// Download a log file.
async function testLogs(): Promise<void> {
const res = await fetch(urlJoin(endPoint, '/logs/mock.log'));
const contentType = res.headers.get('Content-Type')!;
assert.ok(res.ok);
assert.ok(contentType.startsWith('text/plain')); // content type can influence browser behavior
assert.equal(await res.text(), logFileContent);
}
// Proxy a GET request to Netron.
async function testNetronGet(): Promise<void> {
const res = await fetch(urlJoin(endPoint, '/netron/mock/get-path'));
const req = await res.json(); // the mock server send request info back as response
assert.ok(res.ok);
assert.equal(req.headers.host, netronHost);
assert.equal(req.url, '/mock/get-path');
}
// Proxy a POST request to Netron.
async function testNetronPost(): Promise<void> {
const postData = 'hello netron';
const res = await fetch(urlJoin(endPoint, '/netron/post-path'), { method: 'POST', body: postData });
const req = await res.json();
assert.ok(res.ok);
assert.equal(req.url, '/post-path');
assert.equal(req.body, postData);
}
// Access web UI index page.
async function testWebuiIndex(): Promise<void> {
const res = await fetch(endPoint);
assert.ok(res.ok);
assert.equal(await res.text(), webuiIndexContent);
}
// Access web UI resource file (js, css, image, etc).
async function testWebuiResource(): Promise<void> {
const res = await fetch(urlJoin(endPoint, '/script.js'));
assert.ok(res.ok);
assert.equal(await res.text(), webuiScriptContent);
}
// Access web UI routing path ("/oview", "/detail", etc).
// This should also send index page.
async function testWebuiRouting(): Promise<void> {
const res = await fetch(urlJoin(endPoint, '/not-exist'));
assert.ok(res.ok);
assert.equal(await res.text(), webuiIndexContent);
}
// When URL prefix is set, send a request without that prefix.
async function testOutsidePrefix(): Promise<void> {
const res = await fetch(endPointWithoutPrefix);
assert.equal(res.status, 404);
const res2 = await fetch(urlJoin(endPointWithoutPrefix, '/not-exist'));
assert.equal(res2.status, 404);
}
/* Register test cases */
describe('## rest_server ##', () => {
it('logs', () => testLogs());
it('netron get', () => testNetronGet());
it('netron post', () => testNetronPost());
it('webui index', () => testWebuiIndex());
it('webui resource', () => testWebuiResource());
it('webui routing', () => testWebuiRouting());
// I don't know how to add "between test cases hook".
// This is a workaround to reset REST server with URL prefix.
it('// re-configure rest server', () => configRestServer('url/prefix'));
it('prefix logs', () => testLogs());
it('prefix netron get', () => testNetronGet());
it('prefix netron post', () => testNetronPost());
it('prefix webui index', () => testWebuiIndex());
it('prefix webui resource', () => testWebuiResource());
it('prefix webui routing', () => testWebuiRouting());
it('outside prefix', () => testOutsidePrefix());
});
/* Configure test environment */
before(async () => {
await configRestServer();
const netronPort = await mock_netron_server.start();
netronHost = `localhost:${netronPort}`;
UnitTestHelpers.setNetronUrl('http://' + netronHost);
});
after(async () => {
await restServer.shutdown();
});
async function configRestServer(urlPrefix?: string) {
if (restServer !== undefined) {
await restServer.shutdown();
}
// Set port, URL prefix, and log path.
// There should be a better way to do this.
// Maybe rewire? I can't get it work with TypeScript.
setExperimentStartupInfo(
true,
path.basename(__dirname), // hacking getLogDir()
0, // ask for a random idle port
'local',
path.dirname(__dirname),
undefined,
undefined,
undefined,
urlPrefix
);
UnitTestHelpers.setWebuiPath(path.join(__dirname, 'static'));
restServer = new RestServer();
await restServer.start();
const port = UnitTestHelpers.getPort(restServer);
endPointWithoutPrefix = `http://localhost:${port}`;
endPoint = urlJoin(endPointWithoutPrefix, urlPrefix ?? '');
}
function urlJoin(part1: string, part2: string): string {
if (part1.endsWith('/')) {
part1 = part1.slice(0, -1);
}
if (part2.startsWith('/')) {
part2 = part2.slice(1);
}
if (part2 === '') {
return part1;
}
return part1 + '/' + part2;
}
<!DOCTYPE html>
<html>
mock index
</html>
......@@ -10,15 +10,16 @@ import { Writable } from 'stream';
import { String } from 'typescript-string-operations';
import * as component from 'common/component';
import { getBasePort, getExperimentId } from 'common/experimentStartupInfo';
import { RestServer } from 'common/restServer';
import { LegacyRestServer } from 'common/restServer';
import { getExperimentRootDir, mkDirPSync } from 'common/utils';
/**
* Cluster Job Training service Rest server, provides rest API to support Cluster job metrics update
*
* FIXME: This should be a router, not a separate REST server.
*/
@component.Singleton
export abstract class ClusterJobRestServer extends RestServer {
export abstract class ClusterJobRestServer extends LegacyRestServer {
private readonly API_ROOT_URL: string = '/api/v1/nni-pai';
private readonly NNI_METRICS_PATTERN: string = `NNISDK_MEb'(?<metrics>.*?)'`;
......
......@@ -619,6 +619,14 @@
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.2.tgz#91daa226eb8c2ff261e6a8cbf8c7304641e095e0"
integrity sha512-Lwh0lzzqT5Pqh6z61P3c3P5nm6fzQK/MMHl9UKeneAeInVflBSz1O2EkX6gM6xfJd7FBXBY5purtLx7fUiZ7Hw==
"@types/node-fetch@<3.0.0":
version "2.5.12"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.12.tgz#8a6f779b1d4e60b7a57fb6fd48d84fb545b9cc66"
integrity sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==
dependencies:
"@types/node" "*"
form-data "^3.0.0"
"@types/node@*":
version "15.12.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.1.tgz#9b60797dee1895383a725f828a869c86c6caa5c2"
......@@ -1202,21 +1210,21 @@ binary-extensions@^2.0.0, binary-extensions@^2.2.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
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==
body-parser@1.19.1:
version "1.19.1"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.1.tgz#1499abbaa9274af3ecc9f6f10396c995943e31d4"
integrity sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==
dependencies:
bytes "3.1.0"
bytes "3.1.1"
content-type "~1.0.4"
debug "2.6.9"
depd "~1.1.2"
http-errors "1.7.2"
http-errors "1.8.1"
iconv-lite "0.4.24"
on-finished "~2.3.0"
qs "6.7.0"
raw-body "2.4.0"
type-is "~1.6.17"
qs "6.9.6"
raw-body "2.4.2"
type-is "~1.6.18"
boom@2.6.x:
version "2.6.1"
......@@ -1286,10 +1294,10 @@ builtins@^1.0.3:
resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88"
integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og=
bytes@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
bytes@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a"
integrity sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==
cacache@^15.0.3, cacache@^15.0.5, cacache@^15.2.0:
version "15.2.0"
......@@ -1617,7 +1625,7 @@ columnify@~1.5.4:
strip-ansi "^3.0.0"
wcwidth "^1.0.0"
combined-stream@^1.0.6, combined-stream@~1.0.6:
combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
......@@ -1644,12 +1652,12 @@ console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
content-disposition@0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
content-disposition@0.5.4:
version "0.5.4"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
dependencies:
safe-buffer "5.1.2"
safe-buffer "5.2.1"
content-type@~1.0.4:
version "1.0.4"
......@@ -1668,10 +1676,10 @@ cookie-signature@1.0.6:
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
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.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2"
......@@ -2074,17 +2082,17 @@ express-joi-validator@^2.0.1:
extend "2.0.x"
joi "6.x.x"
express@^4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
express@^4.17.2:
version "4.17.2"
resolved "https://registry.yarnpkg.com/express/-/express-4.17.2.tgz#c18369f265297319beed4e5558753cc8c1364cb3"
integrity sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==
dependencies:
accepts "~1.3.7"
array-flatten "1.1.1"
body-parser "1.19.0"
content-disposition "0.5.3"
body-parser "1.19.1"
content-disposition "0.5.4"
content-type "~1.0.4"
cookie "0.4.0"
cookie "0.4.1"
cookie-signature "1.0.6"
debug "2.6.9"
depd "~1.1.2"
......@@ -2098,13 +2106,13 @@ express@^4.17.1:
on-finished "~2.3.0"
parseurl "~1.3.3"
path-to-regexp "0.1.7"
proxy-addr "~2.0.5"
qs "6.7.0"
proxy-addr "~2.0.7"
qs "6.9.6"
range-parser "~1.2.1"
safe-buffer "5.1.2"
send "0.17.1"
serve-static "1.14.1"
setprototypeof "1.1.1"
safe-buffer "5.2.1"
send "0.17.2"
serve-static "1.14.2"
setprototypeof "1.2.0"
statuses "~1.5.0"
type-is "~1.6.18"
utils-merge "1.0.1"
......@@ -2283,6 +2291,15 @@ form-data@^2.5.0:
combined-stream "^1.0.6"
mime-types "^2.1.12"
form-data@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
form-data@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
......@@ -2624,27 +2641,16 @@ http-cache-semantics@^4.1.0:
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
http-errors@1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
dependencies:
depd "~1.1.2"
inherits "2.0.3"
setprototypeof "1.1.1"
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
http-errors@~1.7.2:
version "1.7.3"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
http-errors@1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c"
integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==
dependencies:
depd "~1.1.2"
inherits "2.0.4"
setprototypeof "1.1.1"
setprototypeof "1.2.0"
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
toidentifier "1.0.1"
http-proxy-agent@^4.0.1:
version "4.0.1"
......@@ -2772,11 +2778,6 @@ inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, i
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
inherits@2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
ini@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5"
......@@ -3702,11 +3703,6 @@ ms@2.0.0:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
ms@2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
......@@ -3761,6 +3757,13 @@ node-addon-api@^3.0.0:
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==
node-fetch@<3.0.0:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
dependencies:
whatwg-url "^5.0.0"
node-forge@>=0.10.0, node-forge@^0.8.5:
version "0.10.0"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
......@@ -4542,7 +4545,7 @@ promzard@^0.3.0:
dependencies:
read "1"
proxy-addr@~2.0.5:
proxy-addr@~2.0.7:
version "2.0.7"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
......@@ -4575,10 +4578,10 @@ qrcode-terminal@^0.12.0:
resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819"
integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==
qs@6.7.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
qs@6.9.6:
version "6.9.6"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee"
integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==
qs@^6.7.0:
version "6.10.1"
......@@ -4618,13 +4621,13 @@ range-parser@~1.2.1:
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
raw-body@2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
raw-body@2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.2.tgz#baf3e9c21eebced59dd6533ac872b71f7b61cb32"
integrity sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==
dependencies:
bytes "3.1.0"
http-errors "1.7.2"
bytes "3.1.1"
http-errors "1.8.1"
iconv-lite "0.4.24"
unpipe "1.0.0"
......@@ -4872,16 +4875,16 @@ rx@^4.1.0:
resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
integrity sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0:
safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
......@@ -4914,10 +4917,10 @@ semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semve
dependencies:
lru-cache "^6.0.0"
send@0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
send@0.17.2:
version "0.17.2"
resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820"
integrity sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==
dependencies:
debug "2.6.9"
depd "~1.1.2"
......@@ -4926,9 +4929,9 @@ send@0.17.1:
escape-html "~1.0.3"
etag "~1.8.1"
fresh "0.5.2"
http-errors "~1.7.2"
http-errors "1.8.1"
mime "1.6.0"
ms "2.1.1"
ms "2.1.3"
on-finished "~2.3.0"
range-parser "~1.2.1"
statuses "~1.5.0"
......@@ -4940,25 +4943,25 @@ serialize-javascript@6.0.0:
dependencies:
randombytes "^2.1.0"
serve-static@1.14.1:
version "1.14.1"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
serve-static@1.14.2:
version "1.14.2"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.2.tgz#722d6294b1d62626d41b43a013ece4598d292bfa"
integrity sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==
dependencies:
encodeurl "~1.0.2"
escape-html "~1.0.3"
parseurl "~1.3.3"
send "0.17.1"
send "0.17.2"
set-blocking@^2.0.0, set-blocking@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
setprototypeof@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
setprototypeof@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
shallow-clone@^0.1.2:
version "0.1.2"
......@@ -5374,10 +5377,10 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
toidentifier@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
toidentifier@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
tough-cookie@~2.5.0:
version "2.5.0"
......@@ -5387,6 +5390,11 @@ tough-cookie@~2.5.0:
psl "^1.1.28"
punycode "^2.1.1"
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
tree-kill@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
......@@ -5464,7 +5472,7 @@ type-fest@^0.8.0:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
type-is@~1.6.17, type-is@~1.6.18:
type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
......@@ -5611,6 +5619,19 @@ wcwidth@^1.0.0:
dependencies:
defaults "^1.0.3"
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0=
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
......
......@@ -14,6 +14,11 @@ import '../../static/style/overview/overview.scss';
import '../../static/style/copyParameter.scss';
import '../../static/style/openRow.scss';
/**
* netron URL must be synchronized with ts/nni_manager/rest_server/index.ts`.
* Remember to update it if the value is changed or this file is moved.
**/
interface OpenRowProps {
trialId: string;
}
......
......@@ -4,6 +4,13 @@ import { getPrefix } from './function';
const METRIC_GROUP_UPDATE_THRESHOLD = 100;
const METRIC_GROUP_UPDATE_SIZE = 20;
/**
* RESTAPI and DOWNLOAD_IP must be synchronized with:
* - nni/experiment/rest.py
* - ts/nni_manager/rest_server/index.ts
* Remember to update them if the values are changed or if this file is moved.
**/
const prefix = getPrefix();
const RESTAPI = '/api/v1/nni';
const MANAGER_IP = prefix === undefined ? RESTAPI : `${prefix}${RESTAPI}`;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment