From d82606f426dd9cd957d4564c9f4d20db5575112c Mon Sep 17 00:00:00 2001 From: Lijiao Date: Fri, 29 Jan 2021 08:41:59 +0000 Subject: [PATCH 01/17] set dev evn --- ts/webui/.eslintrc | 3 ++- ts/webui/scripts/start.js | 2 +- ts/webui/src/static/const.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ts/webui/.eslintrc b/ts/webui/.eslintrc index e220b1f541..a26dd88967 100644 --- a/ts/webui/.eslintrc +++ b/ts/webui/.eslintrc @@ -21,7 +21,7 @@ "prettier" ], "rules": { - "prettier/prettier": 2, + "prettier/prettier": 0, "@typescript-eslint/no-explicit-any": 0, "@typescript-eslint/no-namespace": 0, "@typescript-eslint/consistent-type-assertions": 0, @@ -32,6 +32,7 @@ "arrow-parens": [2, "as-needed"], "no-inner-declarations": 0, "no-empty": 2, + "no-console": 0, "no-multiple-empty-lines": [2, { "max": 1 }], "react/display-name": 0 }, diff --git a/ts/webui/scripts/start.js b/ts/webui/scripts/start.js index e6236bd6a2..891ce0d39b 100644 --- a/ts/webui/scripts/start.js +++ b/ts/webui/scripts/start.js @@ -41,7 +41,7 @@ if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { } // Tools like Cloud9 rely on this. -const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; +const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 9090; const HOST = process.env.HOST || '0.0.0.0'; if (process.env.HOST) { diff --git a/ts/webui/src/static/const.ts b/ts/webui/src/static/const.ts index 553e151703..45919e8dc3 100644 --- a/ts/webui/src/static/const.ts +++ b/ts/webui/src/static/const.ts @@ -2,7 +2,7 @@ const METRIC_GROUP_UPDATE_THRESHOLD = 100; const METRIC_GROUP_UPDATE_SIZE = 20; -const MANAGER_IP = `/api/v1/nni`; +const MANAGER_IP = `http://13.77.78.63:8080/api/v1/nni`; const DOWNLOAD_IP = `/logs`; const WEBUIDOC = 'https://nni.readthedocs.io/en/latest/Tutorial/WebUI.html'; const trialJobStatus = [ From beab60ea2dd16f82fb455796a7ef356b95cc9408 Mon Sep 17 00:00:00 2001 From: Lijiao Date: Tue, 2 Feb 2021 01:26:22 +0000 Subject: [PATCH 02/17] update --- ts/webui/.eslintrc | 2 +- ts/webui/src/components/buttons/Icon.tsx | 6 + ts/webui/src/components/modals/Compare.tsx | 2 +- .../modals/tensorboard/DialogDetail.tsx | 36 ++++ .../modals/tensorboard/Tensorboard.tsx | 167 ++++++++++++++++++ .../src/components/trial-detail/TableList.tsx | 43 +++-- ts/webui/src/static/style/common.scss | 17 ++ ts/webui/src/static/style/tensorboard.scss | 14 ++ 8 files changed, 272 insertions(+), 15 deletions(-) create mode 100644 ts/webui/src/components/modals/tensorboard/DialogDetail.tsx create mode 100644 ts/webui/src/components/modals/tensorboard/Tensorboard.tsx create mode 100644 ts/webui/src/static/style/tensorboard.scss diff --git a/ts/webui/.eslintrc b/ts/webui/.eslintrc index a26dd88967..39d1918635 100644 --- a/ts/webui/.eslintrc +++ b/ts/webui/.eslintrc @@ -26,7 +26,7 @@ "@typescript-eslint/no-namespace": 0, "@typescript-eslint/consistent-type-assertions": 0, "@typescript-eslint/no-inferrable-types": 0, - "@typescript-eslint/no-use-before-define": [2, "nofunc"], + "@typescript-eslint/no-use-before-define": 0, "@typescript-eslint/no-var-requires": 0, "@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }], "arrow-parens": [2, "as-needed"], diff --git a/ts/webui/src/components/buttons/Icon.tsx b/ts/webui/src/components/buttons/Icon.tsx index 3c221180da..da7a78d028 100644 --- a/ts/webui/src/components/buttons/Icon.tsx +++ b/ts/webui/src/components/buttons/Icon.tsx @@ -4,6 +4,10 @@ initializeIcons(); const infoIcon = ; const warining = ; +// const deleteIcon = ; +// const openInNewWindow = ; +// const deleteIcon = { iconName: 'Delete' }; +// const openInNewWindow = { iconName: 'OpenInNewWindow' }; const errorBadge = ; const completed = ; const blocked = ; @@ -26,6 +30,8 @@ const ChevronRightMed = ; export { infoIcon, warining, + // deleteIcon, + // openInNewWindow, errorBadge, completed, blocked, diff --git a/ts/webui/src/components/modals/Compare.tsx b/ts/webui/src/components/modals/Compare.tsx index fc20bd95da..f3aacad0a4 100644 --- a/ts/webui/src/components/modals/Compare.tsx +++ b/ts/webui/src/components/modals/Compare.tsx @@ -4,9 +4,9 @@ import { Stack, Modal, IconButton, IDragOptions, ContextualMenu } from '@fluentu import ReactEcharts from 'echarts-for-react'; import { TooltipForIntermediate, TableObj, SingleAxis } from '../../static/interface'; import { contentStyles, iconButtonStyles } from '../buttons/ModalTheme'; -import '../../static/style/compare.scss'; import { convertDuration, parseMetrics } from '../../static/function'; import { EXPERIMENT, TRIALS } from '../../static/datamodel'; +import '../../static/style/compare.scss'; function _getWebUIWidth(): number { return window.innerWidth; diff --git a/ts/webui/src/components/modals/tensorboard/DialogDetail.tsx b/ts/webui/src/components/modals/tensorboard/DialogDetail.tsx new file mode 100644 index 0000000000..843247bfdd --- /dev/null +++ b/ts/webui/src/components/modals/tensorboard/DialogDetail.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { PrimaryButton, Dialog, DialogType, DialogFooter } from '@fluentui/react'; + +function DialogDetail(props): any { + + const {status, visible, func} = props; + const dialogContentProps = { + type: DialogType.normal, + title: 'Tensorboard progress', + closeButtonAriaLabel: 'Close', + subText: `Please waiting some time to see tensorboard because this trial's tensorboard status is ${status}`, + }; + console.info('dialog'); + + return ( + + ); +} + +DialogDetail.propTypes = { + status: PropTypes.string, + visible: PropTypes.bool, + func: PropTypes.func, +}; + +export default DialogDetail; diff --git a/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx b/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx new file mode 100644 index 0000000000..7af8d6471b --- /dev/null +++ b/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx @@ -0,0 +1,167 @@ +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { Stack, Panel, StackItem, PrimaryButton, DetailsList, IColumn, IconButton } from '@fluentui/react'; +import DialogDetail from './DialogDetail'; +import { caclMonacoEditorHeight, requestAxios } from '../../../static/function'; +import { MANAGER_IP } from '../../../static/const'; +import '../../../static/style/tensorboard.scss'; + +function Tensorboard(props): any { + + const { onHideDialog, trialIDs } = props; + const [deleteIDs, setDeleteIDs] = useState([] as string[]); + const [trialCount, setTrialCount] = useState(trialIDs.length - deleteIDs.length); + const [source, setSource] = useState([]); + const [status, setStatus] = useState(''); // trial tensorboard status + const [visibleDialog, setVisibleDialog] = useState(false); + + const columns: IColumn[] = [ + { + name: 'ID', + key: 'id', + fieldName: 'id', + minWidth: 60, + maxWidth: 120, + isResizable: true, + className: 'tableHead leftTitle', + data: 'string', + onRender: (item: any): React.ReactNode =>
{item.id}
+ }, + { + name: 'Operation', + key: '_operation', + fieldName: 'operation', + minWidth: 90, + maxWidth: 90, + isResizable: true, + className: 'detail-table', + onRender: _renderOperationColumn + } + ]; + + useEffect(() => { + const realIDs = trialIDs.filter(item => !(deleteIDs.includes(item))); + setSource(realIDs.map(id => ({ id }))); + }, [trialCount]); // trialCount发生改变时触发页面更新 + + // const { experiment, expDrawerHeight } = state; + const tableHeight = caclMonacoEditorHeight(window.innerHeight); + + function _renderOperationColumn(record: any): React.ReactNode { + return ( + + openTrialTensorboard(record.id)} + /> + deleteOneTrialTensorboard(record.id)} + /> + + ); + } + + async function openTrialTensorboard(id: string): Promise { + /** Get tensorboard status + Request: Get /api/v1/tensorboard/:id + Response if success: + Status:200 + { + "status": "downloading data | running | stopping | stopped" + "url": "tensorboard url" + } + */ + // 效果演示代码 + // await setStatus('downloag'); + // await setVisibleDialog(true); + console.info(id); + await requestAxios(`${MANAGER_IP}/tensorboard/:${id}`) + .then(data => { + if (data.status !== 'downloading data') { + // trial 启动成功 + window.open(data.url); + } else { + // 提示trial正在起tensorboard, 展示当前状态, + setStatus(data.status); + setVisibleDialog(true); + } + }) + .catch(_error => { + // 页面展示error message + alert('youwenti'); + // TODO: 提示有问题,请重新点击 + }); + } + + function deleteOneTrialTensorboard(id: string): void { + /** + * 4. Stop tensorboard + Request: DELETE /api/v1/tensorboard/:id + Response if success + { + status: "stopping" + } + */ + const a = deleteIDs; + a.push(id); + setDeleteIDs(a); + setTrialCount(trialIDs.length - a.length); + console.info('dele op'); + + } + + /** + * 1. Start new tensorboard + Request: POST /api/v1/tensorboard + Parameters: + { + "trials": "trialId1, trialId2" + } + Response if success: + Status:201 + { + tensorboardId: "id" + } + Response if failed: + Status 400 + { + Message:"error message" + } + + */ + return ( + +
+
+ Tensorboard + {trialCount} +
+ + + + + + +
+ {visibleDialog && } +
+ ); +} + +Tensorboard.propTypes = { + trialIDs: PropTypes.array, + onHideDialog: PropTypes.func +}; + +export default Tensorboard; diff --git a/ts/webui/src/components/trial-detail/TableList.tsx b/ts/webui/src/components/trial-detail/TableList.tsx index 6c612124ef..a8140dba42 100644 --- a/ts/webui/src/components/trial-detail/TableList.tsx +++ b/ts/webui/src/components/trial-detail/TableList.tsx @@ -17,26 +17,23 @@ import { EXPERIMENT, TRIALS } from '../../static/datamodel'; import { TOOLTIP_BACKGROUND_COLOR } from '../../static/const'; import { convertDuration, formatTimestamp, copyAndSort } from '../../static/function'; import { TableObj, SortInfo } from '../../static/interface'; -import '../../static/style/search.scss'; -import '../../static/style/tableStatus.css'; -import '../../static/style/logPath.scss'; -import '../../static/style/table.scss'; -import '../../static/style/button.scss'; -import '../../static/style/logPath.scss'; -import '../../static/style/openRow.scss'; -import '../../static/style/pagination.scss'; -import '../../static/style/search.scss'; -import '../../static/style/table.scss'; -import '../../static/style/tableStatus.css'; -import '../../static/style/overview/overviewTitle.scss'; import { blocked, copy, LineChart, tableListIcon } from '../buttons/Icon'; import ChangeColumnComponent from '../modals/ChangeColumnComponent'; import Compare from '../modals/Compare'; import Customize from '../modals/CustomizedTrial'; +import Tensorboard from '../modals/tensorboard/Tensorboard'; import KillJob from '../modals/Killjob'; import ExpandableDetails from '../public-child/ExpandableDetails'; import PaginationTable from '../public-child/PaginationTable'; import { Trial } from '../../static/model/trial'; +import '../../static/style/button.scss'; +import '../../static/style/logPath.scss'; +import '../../static/style/openRow.scss'; +import '../../static/style/pagination.scss'; +import '../../static/style/search.scss'; +import '../../static/style/table.scss'; +import '../../static/style/tableStatus.css'; +import '../../static/style/overview/overviewTitle.scss'; require('echarts/lib/chart/line'); require('echarts/lib/component/tooltip'); @@ -88,6 +85,7 @@ interface TableListState { selectedRowIds: string[]; customizeColumnsDialogVisible: boolean; compareDialogVisible: boolean; + tensorboardPanelVisible: boolean; intermediateDialogTrial: TableObj | undefined; copiedTrialId: string | undefined; sortInfo: SortInfo; @@ -108,6 +106,7 @@ class TableList extends React.Component { searchText: '', customizeColumnsDialogVisible: false, compareDialogVisible: false, + tensorboardPanelVisible: false, selectedRowIds: [], intermediateDialogTrial: undefined, copiedTrialId: undefined, @@ -435,12 +434,14 @@ class TableList extends React.Component { searchType, customizeColumnsDialogVisible, compareDialogVisible, + tensorboardPanelVisible, displayedColumns, selectedRowIds, intermediateDialogTrial, copiedTrialId } = this.state; - + console.info('detail'); + console.info(selectedRowIds); return (
@@ -457,6 +458,14 @@ class TableList extends React.Component { }} disabled={selectedRowIds.length === 0} /> + { + this.setState({ tensorboardPanelVisible: true }); + }} + disabled={selectedRowIds.length === 0} + /> @@ -517,6 +526,14 @@ class TableList extends React.Component { }} /> )} + {tensorboardPanelVisible && ( + { + this.setState({ tensorboardPanelVisible: false }); + }} + /> + )} {intermediateDialogTrial !== undefined && ( Date: Tue, 2 Feb 2021 03:04:58 +0000 Subject: [PATCH 03/17] update --- .../modals/tensorboard/DialogDetail.tsx | 11 ++-- .../modals/tensorboard/Tensorboard.tsx | 53 ++++++++++++------- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/ts/webui/src/components/modals/tensorboard/DialogDetail.tsx b/ts/webui/src/components/modals/tensorboard/DialogDetail.tsx index 843247bfdd..f901ffe2ef 100644 --- a/ts/webui/src/components/modals/tensorboard/DialogDetail.tsx +++ b/ts/webui/src/components/modals/tensorboard/DialogDetail.tsx @@ -4,14 +4,13 @@ import { PrimaryButton, Dialog, DialogType, DialogFooter } from '@fluentui/react function DialogDetail(props): any { - const {status, visible, func} = props; + const { visible, message, func } = props; const dialogContentProps = { type: DialogType.normal, - title: 'Tensorboard progress', + title: 'Tensorboard', closeButtonAriaLabel: 'Close', - subText: `Please waiting some time to see tensorboard because this trial's tensorboard status is ${status}`, + subText: message }; - console.info('dialog'); return ( - {func(false)}} text="Close" /> + { func(false) }} text="Close" /> ); } DialogDetail.propTypes = { - status: PropTypes.string, visible: PropTypes.bool, + message: PropTypes.string, func: PropTypes.func, }; diff --git a/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx b/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx index 7af8d6471b..5ec0573fcd 100644 --- a/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx +++ b/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import { Stack, Panel, StackItem, PrimaryButton, DetailsList, IColumn, IconButton } from '@fluentui/react'; import DialogDetail from './DialogDetail'; +import axios from 'axios'; import { caclMonacoEditorHeight, requestAxios } from '../../../static/function'; import { MANAGER_IP } from '../../../static/const'; import '../../../static/style/tensorboard.scss'; @@ -12,7 +13,7 @@ function Tensorboard(props): any { const [deleteIDs, setDeleteIDs] = useState([] as string[]); const [trialCount, setTrialCount] = useState(trialIDs.length - deleteIDs.length); const [source, setSource] = useState([]); - const [status, setStatus] = useState(''); // trial tensorboard status + const [dialogContent, setDialogContent] = useState(''); // trial tensorboard api error const [visibleDialog, setVisibleDialog] = useState(false); const columns: IColumn[] = [ @@ -53,12 +54,12 @@ function Tensorboard(props): any { openTrialTensorboard(record.id)} + onClick={(): Promise => openTrialTensorboard(record.id)} /> deleteOneTrialTensorboard(record.id)} + onClick={(): Promise => deleteOneTrialTensorboard(record.id)} /> ); @@ -74,10 +75,10 @@ function Tensorboard(props): any { "url": "tensorboard url" } */ - // 效果演示代码 - // await setStatus('downloag'); - // await setVisibleDialog(true); - console.info(id); + // 效果演示代码 + // await setStatus('downloag'); + // await setVisibleDialog(true); + console.info(id); await requestAxios(`${MANAGER_IP}/tensorboard/:${id}`) .then(data => { if (data.status !== 'downloading data') { @@ -85,18 +86,18 @@ function Tensorboard(props): any { window.open(data.url); } else { // 提示trial正在起tensorboard, 展示当前状态, - setStatus(data.status); + setDialogContent(`Please waiting some time to see tensorboard because this trial's tensorboard status is ${status}`); setVisibleDialog(true); } }) - .catch(_error => { - // 页面展示error message - alert('youwenti'); - // TODO: 提示有问题,请重新点击 + .catch(error => { + // 提示有问题,请重新点击 + setDialogContent(error.message); + setVisibleDialog(true); }); } - function deleteOneTrialTensorboard(id: string): void { + async function deleteOneTrialTensorboard(id: string): Promise { /** * 4. Stop tensorboard Request: DELETE /api/v1/tensorboard/:id @@ -104,12 +105,19 @@ function Tensorboard(props): any { { status: "stopping" } - */ - const a = deleteIDs; - a.push(id); - setDeleteIDs(a); - setTrialCount(trialIDs.length - a.length); - console.info('dele op'); + */ + + const response = await axios.delete(`${MANAGER_IP}/tensorboard/:${id}`); + if (response.status === 200) { + const a = deleteIDs; + a.push(id); + setDeleteIDs(a); + setTrialCount(trialIDs.length - a.length); + setDialogContent(`Had stopped trial ${id}'s tensorboard`); + } else { + setDialogContent(`Failed to stopped trial ${id}'s tensorboard`); + } + setVisibleDialog(true); } @@ -154,7 +162,12 @@ function Tensorboard(props): any {
- {visibleDialog && } + {visibleDialog && + } ); } From cd19ecbf0aa6e353c4d80ace2e90b1135723b66d Mon Sep 17 00:00:00 2001 From: Lijiao Date: Tue, 2 Feb 2021 08:50:18 +0000 Subject: [PATCH 04/17] update --- .../modals/tensorboard/DialogDetail.tsx | 7 +- .../modals/tensorboard/Tensorboard.tsx | 25 +----- .../src/components/trial-detail/TableList.tsx | 78 ++++++++++++++++--- 3 files changed, 71 insertions(+), 39 deletions(-) diff --git a/ts/webui/src/components/modals/tensorboard/DialogDetail.tsx b/ts/webui/src/components/modals/tensorboard/DialogDetail.tsx index f901ffe2ef..bc5e9d1481 100644 --- a/ts/webui/src/components/modals/tensorboard/DialogDetail.tsx +++ b/ts/webui/src/components/modals/tensorboard/DialogDetail.tsx @@ -4,7 +4,7 @@ import { PrimaryButton, Dialog, DialogType, DialogFooter } from '@fluentui/react function DialogDetail(props): any { - const { visible, message, func } = props; + const { message, func } = props; const dialogContentProps = { type: DialogType.normal, title: 'Tensorboard', @@ -14,20 +14,19 @@ function DialogDetail(props): any { return ( ); } DialogDetail.propTypes = { - visible: PropTypes.bool, message: PropTypes.string, func: PropTypes.func, }; diff --git a/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx b/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx index 5ec0573fcd..fce92d719a 100644 --- a/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx +++ b/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx @@ -1,10 +1,10 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; +import axios from 'axios'; import { Stack, Panel, StackItem, PrimaryButton, DetailsList, IColumn, IconButton } from '@fluentui/react'; import DialogDetail from './DialogDetail'; -import axios from 'axios'; -import { caclMonacoEditorHeight, requestAxios } from '../../../static/function'; import { MANAGER_IP } from '../../../static/const'; +import { caclMonacoEditorHeight, requestAxios } from '../../../static/function'; import '../../../static/style/tensorboard.scss'; function Tensorboard(props): any { @@ -118,28 +118,8 @@ function Tensorboard(props): any { setDialogContent(`Failed to stopped trial ${id}'s tensorboard`); } setVisibleDialog(true); - } - /** - * 1. Start new tensorboard - Request: POST /api/v1/tensorboard - Parameters: - { - "trials": "trialId1, trialId2" - } - Response if success: - Status:201 - { - tensorboardId: "id" - } - Response if failed: - Status 400 - { - Message:"error message" - } - - */ return (
@@ -164,7 +144,6 @@ function Tensorboard(props): any {
{visibleDialog && } diff --git a/ts/webui/src/components/trial-detail/TableList.tsx b/ts/webui/src/components/trial-detail/TableList.tsx index a8140dba42..99e52a058d 100644 --- a/ts/webui/src/components/trial-detail/TableList.tsx +++ b/ts/webui/src/components/trial-detail/TableList.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { DefaultButton, Dropdown, @@ -12,9 +13,9 @@ import { TooltipHost, DirectionalHint } from '@fluentui/react'; -import React from 'react'; +import axios from 'axios'; import { EXPERIMENT, TRIALS } from '../../static/datamodel'; -import { TOOLTIP_BACKGROUND_COLOR } from '../../static/const'; +import { TOOLTIP_BACKGROUND_COLOR, MANAGER_IP } from '../../static/const'; import { convertDuration, formatTimestamp, copyAndSort } from '../../static/function'; import { TableObj, SortInfo } from '../../static/interface'; import { blocked, copy, LineChart, tableListIcon } from '../buttons/Icon'; @@ -26,6 +27,7 @@ import KillJob from '../modals/Killjob'; import ExpandableDetails from '../public-child/ExpandableDetails'; import PaginationTable from '../public-child/PaginationTable'; import { Trial } from '../../static/model/trial'; +import DialogDetail from '../modals/tensorboard/DialogDetail'; import '../../static/style/button.scss'; import '../../static/style/logPath.scss'; import '../../static/style/openRow.scss'; @@ -89,6 +91,8 @@ interface TableListState { intermediateDialogTrial: TableObj | undefined; copiedTrialId: string | undefined; sortInfo: SortInfo; + visibleDialog: boolean; + dialogContent: string; } class TableList extends React.Component { @@ -110,7 +114,9 @@ class TableList extends React.Component { selectedRowIds: [], intermediateDialogTrial: undefined, copiedTrialId: undefined, - sortInfo: { field: '', isDescend: true } + sortInfo: { field: '', isDescend: true }, + visibleDialog: false, + dialogContent: '' }; this._selection = new Selection({ @@ -401,8 +407,8 @@ class TableList extends React.Component { {blocked} ) : ( - - )} + + )} { ); } + private seeTrialTensorboard = (): void => { + /** + * 1. Start new tensorboard + Request: POST /api/v1/tensorboard + Parameters: + { + "trials": "trialId1, trialId2" + } + Response if success: + Status:201 + { + tensorboardId: "id" + } + Response if failed: + Status 400 + { + Message:"error message" + } + + */ + const { selectedRowIds } = this.state; + this.setState({ tensorboardPanelVisible: true }); + axios(`${MANAGER_IP}/tensorboard`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + data: { + trials: selectedRowIds.toString() + } + }) + .then(res => { + if (res.status === 201) { + this.setState({ tensorboardPanelVisible: true }); + } + }) + .catch(error => { + this.setState({ visibleDialog: true, dialogContent: error.message || 'Tensorboard start failed' }); + }); + } + + private closeDialog = (): void => { + this.setState({ visibleDialog: false}); + } + componentDidUpdate(prevProps: TableListProps): void { if (this.props.tableSource !== prevProps.tableSource) { this._updateTableSource(); @@ -438,7 +487,9 @@ class TableList extends React.Component { displayedColumns, selectedRowIds, intermediateDialogTrial, - copiedTrialId + copiedTrialId, + visibleDialog, + dialogContent } = this.state; console.info('detail'); console.info(selectedRowIds); @@ -461,9 +512,7 @@ class TableList extends React.Component { { - this.setState({ tensorboardPanelVisible: true }); - }} + onClick={(): void => this.seeTrialTensorboard()} disabled={selectedRowIds.length === 0} /> @@ -488,11 +537,10 @@ class TableList extends React.Component { @@ -565,6 +613,12 @@ class TableList extends React.Component { this.setState({ copiedTrialId: undefined }); }} /> + {/* for all trials tensorboard failed modal */} + {visibleDialog && + } ); } From 593b49381e24faa039563b6754206650fc917766 Mon Sep 17 00:00:00 2001 From: Lijiao Date: Wed, 17 Mar 2021 08:05:17 +0000 Subject: [PATCH 05/17] for demo --- .../modals/tensorboard/Tensorboard.tsx | 16 +++++++++++--- .../src/components/trial-detail/TableList.tsx | 21 ++++++++++++------- ts/webui/src/static/const.ts | 3 ++- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx b/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx index fce92d719a..8313a10de5 100644 --- a/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx +++ b/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx @@ -5,6 +5,7 @@ import { Stack, Panel, StackItem, PrimaryButton, DetailsList, IColumn, IconButto import DialogDetail from './DialogDetail'; import { MANAGER_IP } from '../../../static/const'; import { caclMonacoEditorHeight, requestAxios } from '../../../static/function'; +// import { caclMonacoEditorHeight } from '../../../static/function'; import '../../../static/style/tensorboard.scss'; function Tensorboard(props): any { @@ -41,9 +42,14 @@ function Tensorboard(props): any { ]; useEffect(() => { + console.info(trialIDs); // eslint-diable-line const realIDs = trialIDs.filter(item => !(deleteIDs.includes(item))); setSource(realIDs.map(id => ({ id }))); - }, [trialCount]); // trialCount发生改变时触发页面更新 + // console.info(realIDs.map(id => ({id}))); // eslint-disable-line + // setSource(trialIDs.toString()); + + // }, [trialCount]); // trialCount发生改变时触发页面更新 + }, []); // trialCount发生改变时触发页面更新 // const { experiment, expDrawerHeight } = state; const tableHeight = caclMonacoEditorHeight(window.innerHeight); @@ -78,8 +84,8 @@ function Tensorboard(props): any { // 效果演示代码 // await setStatus('downloag'); // await setVisibleDialog(true); - console.info(id); - await requestAxios(`${MANAGER_IP}/tensorboard/:${id}`) + // console.info(id); + await requestAxios(`${MANAGER_IP}/tensorboard/${id}`) .then(data => { if (data.status !== 'downloading data') { // trial 启动成功 @@ -95,6 +101,8 @@ function Tensorboard(props): any { setDialogContent(error.message); setVisibleDialog(true); }); + + // window.open('https://developer.microsoft.com/en-us/fluentui#/styles/web/icons'); } async function deleteOneTrialTensorboard(id: string): Promise { @@ -120,6 +128,8 @@ function Tensorboard(props): any { setVisibleDialog(true); } + console.info(source); // eslint-disable-line + // const a = [{id: 'some-split-string-id-part'}]; return (
diff --git a/ts/webui/src/components/trial-detail/TableList.tsx b/ts/webui/src/components/trial-detail/TableList.tsx index 99e52a058d..aa26e3adf7 100644 --- a/ts/webui/src/components/trial-detail/TableList.tsx +++ b/ts/webui/src/components/trial-detail/TableList.tsx @@ -93,6 +93,7 @@ interface TableListState { sortInfo: SortInfo; visibleDialog: boolean; dialogContent: string; + startTensorId: string; } class TableList extends React.Component { @@ -116,7 +117,8 @@ class TableList extends React.Component { copiedTrialId: undefined, sortInfo: { field: '', isDescend: true }, visibleDialog: false, - dialogContent: '' + dialogContent: '', + startTensorId: '' }; this._selection = new Selection({ @@ -449,12 +451,14 @@ class TableList extends React.Component { method: 'POST', headers: { 'Content-Type': 'application/json' }, data: { - trials: selectedRowIds.toString() + trials: selectedRowIds.join(',') } }) .then(res => { - if (res.status === 201) { - this.setState({ tensorboardPanelVisible: true }); + if (res.status === 200) { + console.info(res); // eslint-diable-line + setTimeout(() => {this.setState({ startTensorId: res.data.id, tensorboardPanelVisible: true }), 2000}); + } }) .catch(error => { @@ -489,10 +493,10 @@ class TableList extends React.Component { intermediateDialogTrial, copiedTrialId, visibleDialog, - dialogContent + dialogContent, + startTensorId } = this.state; - console.info('detail'); - console.info(selectedRowIds); + console.info(startTensorId); // eslint-diable-line return (
@@ -576,7 +580,8 @@ class TableList extends React.Component { )} {tensorboardPanelVisible && ( { this.setState({ tensorboardPanelVisible: false }); }} diff --git a/ts/webui/src/static/const.ts b/ts/webui/src/static/const.ts index 45919e8dc3..2917bb1774 100644 --- a/ts/webui/src/static/const.ts +++ b/ts/webui/src/static/const.ts @@ -2,7 +2,8 @@ const METRIC_GROUP_UPDATE_THRESHOLD = 100; const METRIC_GROUP_UPDATE_SIZE = 20; -const MANAGER_IP = `http://13.77.78.63:8080/api/v1/nni`; +const MANAGER_IP = `http://40.121.81.141:8080/api/v1/nni`; +// const MANAGER_IP = `http://13.77.78.63:8484/api/v1/nni`; const DOWNLOAD_IP = `/logs`; const WEBUIDOC = 'https://nni.readthedocs.io/en/latest/Tutorial/WebUI.html'; const trialJobStatus = [ From 86fca468f317ae4504b02b1ec46f983d3c4f68ca Mon Sep 17 00:00:00 2001 From: Lijiao Date: Wed, 31 Mar 2021 09:30:26 +0000 Subject: [PATCH 06/17] run tensorboard exp --- ts/webui/src/static/const.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ts/webui/src/static/const.ts b/ts/webui/src/static/const.ts index 22db683bef..dee69fd477 100644 --- a/ts/webui/src/static/const.ts +++ b/ts/webui/src/static/const.ts @@ -2,8 +2,7 @@ const METRIC_GROUP_UPDATE_THRESHOLD = 100; const METRIC_GROUP_UPDATE_SIZE = 20; -const MANAGER_IP = `http://40.121.81.141:8080/api/v1/nni`; -// const MANAGER_IP = `http://13.77.78.63:8484/api/v1/nni`; +const MANAGER_IP = `http://13.77.78.63:9999/api/v1/nni`; const DOWNLOAD_IP = `/logs`; const WEBUIDOC = 'https://nni.readthedocs.io/en/latest/Tutorial/WebUI.html'; const trialJobStatus = [ From 23b4b821d052af660ec59e3f65d3138422d2966b Mon Sep 17 00:00:00 2001 From: Lijiao Date: Fri, 2 Apr 2021 11:05:45 +0000 Subject: [PATCH 07/17] update --- ts/webui/scripts/start.js | 2 +- .../modals/tensorboard/DialogDetail.tsx | 34 --- .../tensorboard/ShowTensorBoardDetail.tsx | 46 ++++ .../modals/tensorboard/Tensorboard.tsx | 195 ++++------------ .../src/components/trial-detail/TableList.tsx | 221 +++++++++++++----- ts/webui/src/static/const.ts | 2 + ts/webui/src/static/style/common.scss | 8 + ts/webui/src/static/style/tensorboard.scss | 56 +++++ 8 files changed, 322 insertions(+), 242 deletions(-) delete mode 100644 ts/webui/src/components/modals/tensorboard/DialogDetail.tsx create mode 100644 ts/webui/src/components/modals/tensorboard/ShowTensorBoardDetail.tsx diff --git a/ts/webui/scripts/start.js b/ts/webui/scripts/start.js index 891ce0d39b..5daa8c24e7 100644 --- a/ts/webui/scripts/start.js +++ b/ts/webui/scripts/start.js @@ -41,7 +41,7 @@ if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { } // Tools like Cloud9 rely on this. -const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 9090; +const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 8000; const HOST = process.env.HOST || '0.0.0.0'; if (process.env.HOST) { diff --git a/ts/webui/src/components/modals/tensorboard/DialogDetail.tsx b/ts/webui/src/components/modals/tensorboard/DialogDetail.tsx deleted file mode 100644 index bc5e9d1481..0000000000 --- a/ts/webui/src/components/modals/tensorboard/DialogDetail.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { PrimaryButton, Dialog, DialogType, DialogFooter } from '@fluentui/react'; - -function DialogDetail(props): any { - - const { message, func } = props; - const dialogContentProps = { - type: DialogType.normal, - title: 'Tensorboard', - closeButtonAriaLabel: 'Close', - subText: message - }; - - return ( - - ); -} - -DialogDetail.propTypes = { - message: PropTypes.string, - func: PropTypes.func, -}; - -export default DialogDetail; diff --git a/ts/webui/src/components/modals/tensorboard/ShowTensorBoardDetail.tsx b/ts/webui/src/components/modals/tensorboard/ShowTensorBoardDetail.tsx new file mode 100644 index 0000000000..938b3cb4f1 --- /dev/null +++ b/ts/webui/src/components/modals/tensorboard/ShowTensorBoardDetail.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +// import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +// import { MANAGER_IP } from '../../../static/const'; +import { PrimaryButton, Dialog, DialogType, DialogFooter } from '@fluentui/react'; +// import { caclMonacoEditorHeight, requestAxios } from '../../../static/function'; + +function ShowTensorBoardDetail(props): any { + + const { onHideDialog, item } = props; + + const dialogContentProps = { + type: DialogType.normal, + title: item.id, + closeButtonAriaLabel: 'Close', + }; + + function gotoTensorboard(): void { + const hostname = window.location.hostname; + const protocol = window.location.protocol; + window.open(`${protocol}//${hostname}:${item.port}`); + onHideDialog(); + } + + return ( + + ); +} + +ShowTensorBoardDetail.propTypes = { + item: PropTypes.object, + onHideDialog: PropTypes.func +}; + +export default ShowTensorBoardDetail; diff --git a/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx b/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx index 8313a10de5..eeb77c533d 100644 --- a/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx +++ b/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx @@ -1,169 +1,54 @@ -import React, { useState, useEffect } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; -import axios from 'axios'; -import { Stack, Panel, StackItem, PrimaryButton, DetailsList, IColumn, IconButton } from '@fluentui/react'; -import DialogDetail from './DialogDetail'; -import { MANAGER_IP } from '../../../static/const'; -import { caclMonacoEditorHeight, requestAxios } from '../../../static/function'; -// import { caclMonacoEditorHeight } from '../../../static/function'; -import '../../../static/style/tensorboard.scss'; +import { PrimaryButton, Dialog, DialogType, DialogFooter } from '@fluentui/react'; -function Tensorboard(props): any { +function StartTensorboardDialog(props): any { - const { onHideDialog, trialIDs } = props; - const [deleteIDs, setDeleteIDs] = useState([] as string[]); - const [trialCount, setTrialCount] = useState(trialIDs.length - deleteIDs.length); - const [source, setSource] = useState([]); - const [dialogContent, setDialogContent] = useState(''); // trial tensorboard api error - const [visibleDialog, setVisibleDialog] = useState(false); + const { isReaptedTensorboard, onHideDialog, item } = props; - const columns: IColumn[] = [ - { - name: 'ID', - key: 'id', - fieldName: 'id', - minWidth: 60, - maxWidth: 120, - isResizable: true, - className: 'tableHead leftTitle', - data: 'string', - onRender: (item: any): React.ReactNode =>
{item.id}
- }, - { - name: 'Operation', - key: '_operation', - fieldName: 'operation', - minWidth: 90, - maxWidth: 90, - isResizable: true, - className: 'detail-table', - onRender: _renderOperationColumn - } - ]; + const dialogContentProps = { + type: DialogType.normal, + title: 'Tensorboard', + closeButtonAriaLabel: 'OK', + }; - useEffect(() => { - console.info(trialIDs); // eslint-diable-line - const realIDs = trialIDs.filter(item => !(deleteIDs.includes(item))); - setSource(realIDs.map(id => ({ id }))); - // console.info(realIDs.map(id => ({id}))); // eslint-disable-line - // setSource(trialIDs.toString()); - - // }, [trialCount]); // trialCount发生改变时触发页面更新 - }, []); // trialCount发生改变时触发页面更新 - - // const { experiment, expDrawerHeight } = state; - const tableHeight = caclMonacoEditorHeight(window.innerHeight); - - function _renderOperationColumn(record: any): React.ReactNode { - return ( - - => openTrialTensorboard(record.id)} - /> - => deleteOneTrialTensorboard(record.id)} - /> - - ); - } - - async function openTrialTensorboard(id: string): Promise { - /** Get tensorboard status - Request: Get /api/v1/tensorboard/:id - Response if success: - Status:200 - { - "status": "downloading data | running | stopping | stopped" - "url": "tensorboard url" - } - */ - // 效果演示代码 - // await setStatus('downloag'); - // await setVisibleDialog(true); - // console.info(id); - await requestAxios(`${MANAGER_IP}/tensorboard/${id}`) - .then(data => { - if (data.status !== 'downloading data') { - // trial 启动成功 - window.open(data.url); - } else { - // 提示trial正在起tensorboard, 展示当前状态, - setDialogContent(`Please waiting some time to see tensorboard because this trial's tensorboard status is ${status}`); - setVisibleDialog(true); - } - }) - .catch(error => { - // 提示有问题,请重新点击 - setDialogContent(error.message); - setVisibleDialog(true); - }); - - // window.open('https://developer.microsoft.com/en-us/fluentui#/styles/web/icons'); - } - - async function deleteOneTrialTensorboard(id: string): Promise { - /** - * 4. Stop tensorboard - Request: DELETE /api/v1/tensorboard/:id - Response if success - { - status: "stopping" - } - */ - - const response = await axios.delete(`${MANAGER_IP}/tensorboard/:${id}`); - if (response.status === 200) { - const a = deleteIDs; - a.push(id); - setDeleteIDs(a); - setTrialCount(trialIDs.length - a.length); - setDialogContent(`Had stopped trial ${id}'s tensorboard`); - } else { - setDialogContent(`Failed to stopped trial ${id}'s tensorboard`); - } - setVisibleDialog(true); + function gotoTensorboard(): void { + const hostname = window.location.hostname; + const protocol = window.location.protocol; + window.open(`${protocol}//${hostname}:${item.port}`); + onHideDialog(); } - console.info(source); // eslint-disable-line - // const a = [{id: 'some-split-string-id-part'}]; return ( - -
-
- Tensorboard - {trialCount} +
- {visibleDialog && - } - + } + + + + ); } -Tensorboard.propTypes = { - trialIDs: PropTypes.array, - onHideDialog: PropTypes.func +StartTensorboardDialog.propTypes = { + isReaptedTensorboard: PropTypes.bool, + onHideDialog: PropTypes.func, + item: PropTypes.object }; -export default Tensorboard; +export default StartTensorboardDialog; diff --git a/ts/webui/src/components/trial-detail/TableList.tsx b/ts/webui/src/components/trial-detail/TableList.tsx index 2b7b3d1bb5..91b1331989 100644 --- a/ts/webui/src/components/trial-detail/TableList.tsx +++ b/ts/webui/src/components/trial-detail/TableList.tsx @@ -11,7 +11,8 @@ import { Stack, StackItem, TooltipHost, - DirectionalHint + DirectionalHint, + IContextualMenuProps } from '@fluentui/react'; import axios from 'axios'; import { EXPERIMENT, TRIALS } from '../../static/datamodel'; @@ -23,12 +24,13 @@ import ChangeColumnComponent from '../modals/ChangeColumnComponent'; import Compare from '../modals/Compare'; import Customize from '../modals/CustomizedTrial'; import Tensorboard from '../modals/tensorboard/Tensorboard'; +import ShowTensorBoardDetail from '../modals/tensorboard/ShowTensorBoardDetail'; import KillJob from '../modals/Killjob'; import ExpandableDetails from '../public-child/ExpandableDetails'; import PaginationTable from '../public-child/PaginationTable'; import CopyButton from '../public-child/CopyButton'; import { Trial } from '../../static/model/trial'; -import DialogDetail from '../modals/tensorboard/DialogDetail'; +// import DialogDetail from '../modals/tensorboard/RepeatTensorDialog'; import '../../static/style/button.scss'; import '../../static/style/logPath.scss'; import '../../static/style/openRow.scss'; @@ -36,12 +38,22 @@ import '../../static/style/pagination.scss'; import '../../static/style/search.scss'; import '../../static/style/table.scss'; import '../../static/style/tableStatus.css'; +import '../../static/style/tensorboard.scss'; import '../../static/style/overview/overviewTitle.scss'; require('echarts/lib/chart/line'); require('echarts/lib/component/tooltip'); require('echarts/lib/component/title'); +interface Tensorboard { + id: string; + status: string; + trialJobIdList: string[]; + trialLogDirectoryList: string[]; + pid: number; + port: string; +}; + type SearchOptionType = 'id' | 'trialnum' | 'status' | 'parameters'; const searchOptionLiterals = { id: 'ID', @@ -91,17 +103,22 @@ interface TableListState { customizeColumnsDialogVisible: boolean; compareDialogVisible: boolean; tensorboardPanelVisible: boolean; + detailTensorboardPanelVisible: boolean; intermediateDialogTrial: TableObj | undefined; copiedTrialId: string | undefined; sortInfo: SortInfo; visibleDialog: boolean; dialogContent: string; - startTensorId: string; + isReaptedTensorboard: boolean; + queryTensorboardList: Tensorboard[]; + selectedTensorboard?: Tensorboard; } class TableList extends React.Component { private _selection: Selection; private _expandedTrialIds: Set; + private refreshTensorboard!: number | undefined; + private tableListComponent: boolean = false; constructor(props: TableListProps) { super(props); @@ -111,7 +128,7 @@ class TableList extends React.Component { displayedColumns: localStorage.getItem('columns') !== null ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - JSON.parse(localStorage.getItem('columns')!) + JSON.parse(localStorage.getItem('columns')!) : defaultDisplayedColumns, columns: [], searchType: 'id', @@ -119,13 +136,15 @@ class TableList extends React.Component { customizeColumnsDialogVisible: false, compareDialogVisible: false, tensorboardPanelVisible: false, + detailTensorboardPanelVisible: false, selectedRowIds: [], intermediateDialogTrial: undefined, copiedTrialId: undefined, sortInfo: { field: '', isDescend: true }, visibleDialog: false, dialogContent: '', - startTensorId: '' + isReaptedTensorboard: false, + queryTensorboardList: [] }; this._selection = new Selection({ @@ -447,8 +466,8 @@ class TableList extends React.Component { {blocked} ) : ( - - )} + + )} { ); } - private seeTrialTensorboard = (): void => { - /** - * 1. Start new tensorboard - Request: POST /api/v1/tensorboard - Parameters: - { - "trials": "trialId1, trialId2" - } - Response if success: - Status:201 - { - tensorboardId: "id" - } - Response if failed: - Status 400 - { - Message:"error message" + private clearAllTensorboard = (): void => { + // clear all 所有状态 + axios(`${MANAGER_IP}/tensorboard-tasks`, { + method: 'DELETE', + headers: { 'Content-Type': 'application/json' }, + }) + .then(res => { + if (res.status === 200) { + // 成功停掉了所有在运行的tensorboard,状态一律称为 stopped + // 提示所有的tensorboard都已经停掉了,清掉所有的query, 关掉定时器 + this.setState(() => ({ queryTensorboardList: [] })); + window.clearTimeout(this.refreshTensorboard); } + }) + } - */ + private queryAllTensorboard = (): void => { + // 查询所有trial tensorboard 状态 + // TODO: 加error场景 + if(this.tableListComponent){ + axios(`${MANAGER_IP}/tensorboard-tasks`, { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }) + .then(res => { + if (res.status === 200) { + this.setState(() => ({ queryTensorboardList: res.data.filter(item => item.status !== 'STOPPED') })); + } + }) + .catch(error => { + console.info(error); + }); + this.refreshTensorboard = window.setTimeout(this.queryAllTensorboard, 10000); + } + } + + private startTrialTensorboard = (): void => { const { selectedRowIds } = this.state; - this.setState({ tensorboardPanelVisible: true }); - axios(`${MANAGER_IP}/tensorboard`, { - method: 'POST', + // 查询所有 trial tensorboard 状态 + axios(`${MANAGER_IP}/tensorboard-tasks`, { + method: 'GET', headers: { 'Content-Type': 'application/json' }, - data: { - trials: selectedRowIds.join(',') - } }) .then(res => { if (res.status === 200) { - console.info(res); // eslint-diable-line - setTimeout(() => {this.setState({ startTensorId: res.data.id, tensorboardPanelVisible: true }), 2000}); - + const data = res.data; + // ??? 程序一开始空的时候 + const result = data.filter(item => item.status !== 'STOPPED' && item.trialJobIdList.join(',') === selectedRowIds.join(',')); + if (result.length > 0) { + this.setState({ isReaptedTensorboard: true, selectedTensorboard: result[0], tensorboardPanelVisible: true }); + } else { + axios(`${MANAGER_IP}/tensorboard`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + data: { + trials: selectedRowIds.join(',') + } + }) + .then(res => { + if (res.status === 200) { + + // setTimeout((): void => { + this.setState({ isReaptedTensorboard: false, selectedTensorboard: res.data, tensorboardPanelVisible: true }); + // }, 500); + // 每10s刷一次表单数据 + // 如果表单没显示出来也会默默刷新 + this.queryAllTensorboard(); + } + }) + .catch(error => { + this.setState({ isReaptedTensorboard: false, visibleDialog: true, dialogContent: error.message || 'Tensorboard start failed' }); + }); + } } }) - .catch(error => { - this.setState({ visibleDialog: true, dialogContent: error.message || 'Tensorboard start failed' }); - }); } private closeDialog = (): void => { - this.setState({ visibleDialog: false}); + this.setState({ visibleDialog: false }); + } + + private seeTensorboardWebportal = (item: Tensorboard): void => { + // 加弹窗再提醒一下消息 + this.setState({ detailTensorboardPanelVisible: true, selectedTensorboard: item}); } componentDidUpdate(prevProps: TableListProps): void { @@ -515,7 +575,14 @@ class TableList extends React.Component { } componentDidMount(): void { + this.tableListComponent = true; this._updateTableSource(); + this.queryAllTensorboard(); + } + + componentWillUnmount(): void { + this.tableListComponent = false; + window.clearTimeout(this.refreshTensorboard); } render(): React.ReactNode { @@ -526,15 +593,47 @@ class TableList extends React.Component { customizeColumnsDialogVisible, compareDialogVisible, tensorboardPanelVisible, + detailTensorboardPanelVisible, displayedColumns, selectedRowIds, intermediateDialogTrial, copiedTrialId, - visibleDialog, - dialogContent, - startTensorId + // visibleDialog, + // dialogContent, + isReaptedTensorboard, + queryTensorboardList, + selectedTensorboard } = this.state; - console.info(startTensorId); // eslint-diable-line + const some: Array = []; + if (queryTensorboardList.length !== 0) { + some.push({ + key: 'delete', + text: 'Stop all tensorBoard', + className: 'clearAll', + onClick: this.clearAllTensorboard + }); + queryTensorboardList.forEach(item => { + some.push({ + key: item.id, + text: `${item.id} ${item.port}`, + className: `CommandBarButton-${item.status}`, + onClick: this.seeTensorboardWebportal.bind(this, item) + }); + }) + + } + const tensorboardMenu: IContextualMenuProps = { + items: some.reverse() as any + }; + // disable tensorboard btn logic + let flag = true; + if (selectedRowIds.length !== 0) { + flag = false; + } + + if (selectedRowIds.length === 0 && queryTensorboardList.length !== 0) { + flag = false; + } return (
@@ -552,11 +651,21 @@ class TableList extends React.Component { disabled={selectedRowIds.length === 0} /> this.seeTrialTensorboard()} - disabled={selectedRowIds.length === 0} + text='TensorBoard' + className='elementMarginLeft' + split + splitButtonAriaLabel="See 2 options" + aria-roledescription="split button" + menuProps={tensorboardMenu} + onClick={(): void => this.startTrialTensorboard()} + disabled={flag} /> + { + queryTensorboardList.length !== 0 ? + {queryTensorboardList.length} + : null + } + @@ -580,8 +689,8 @@ class TableList extends React.Component { type='text' className='allList-search-input' placeholder={`Search by ${['id', 'trialnum'].includes(searchType) - ? searchOptionLiterals[searchType] - : searchType + ? searchOptionLiterals[searchType] + : searchType }`} onChange={this._updateSearchText.bind(this)} style={{ width: 230 }} @@ -618,13 +727,21 @@ class TableList extends React.Component { )} {tensorboardPanelVisible && ( { this.setState({ tensorboardPanelVisible: false }); }} /> )} + {detailTensorboardPanelVisible && ( + { + this.setState({ detailTensorboardPanelVisible: false }); + }} + /> + )} {intermediateDialogTrial !== undefined && ( { }} /> {/* for all trials tensorboard failed modal */} - {visibleDialog && + {/* {visibleDialog && } + />} */}
); } diff --git a/ts/webui/src/static/const.ts b/ts/webui/src/static/const.ts index dee69fd477..dcafa2a0d0 100644 --- a/ts/webui/src/static/const.ts +++ b/ts/webui/src/static/const.ts @@ -3,6 +3,8 @@ const METRIC_GROUP_UPDATE_THRESHOLD = 100; const METRIC_GROUP_UPDATE_SIZE = 20; const MANAGER_IP = `http://13.77.78.63:9999/api/v1/nni`; +// const MANAGER_IP = `http://127.0.0.1:9999/api/v1/nni`; +// const MANAGER_IP = `http://192.168.199.1:9999/api/v1/nni`; const DOWNLOAD_IP = `/logs`; const WEBUIDOC = 'https://nni.readthedocs.io/en/latest/Tutorial/WebUI.html'; const trialJobStatus = [ diff --git a/ts/webui/src/static/style/common.scss b/ts/webui/src/static/style/common.scss index 28aa640641..66efa9ecb4 100644 --- a/ts/webui/src/static/style/common.scss +++ b/ts/webui/src/static/style/common.scss @@ -65,3 +65,11 @@ $themeBlue: #0071bc; .ms-List-cell { border-bottom: 1px solid #EEECEA; } + +.line-height { + line-height: 30px; +} + +.bold{ + font-weight: bold; +} \ No newline at end of file diff --git a/ts/webui/src/static/style/tensorboard.scss b/ts/webui/src/static/style/tensorboard.scss index dc92a15555..b34b80e6bd 100644 --- a/ts/webui/src/static/style/tensorboard.scss +++ b/ts/webui/src/static/style/tensorboard.scss @@ -11,4 +11,60 @@ $size: 24px; text-align: center; background: $cicleBg; margin-left: 4px; + position: relative; + top: -16px; + left: -20px; } + +.CommandBarButton{ + &-RUNNING{ + .ms-ContextualMenu-itemText:after{ + content: '(RUNNING)'; + color: #0071bc; + margin-left: 15px; + } + } + &-STOPPED{ + .ms-ContextualMenu-itemText:after{ + content: '(STOPPED)'; + color: green; + margin-left: 15px; + } + } + &-STOPPING{ + .ms-ContextualMenu-itemText:after{ + content: '(STOPPING)'; + color: green; + margin-left: 15px; + } + } + &-DOWNLOADING_DATA{ + .ms-ContextualMenu-itemText:after{ + content: '(DOWNLOADING_DATA)'; + color: green; + margin-left: 15px; + } + } + &-ERROR{ + .ms-ContextualMenu-itemText:after{ + content: '(ERROR)'; + color: red; + margin-left: 15px; + } + } + &-FAIL_DOWNLOAD_DATA{ + .ms-ContextualMenu-itemText:after{ + content: '(FAIL_DOWNLOAD_DATA)'; + color: red; + margin-left: 15px; + } + } +} + +.clearAll{ + border-top: 1px solid #ccc; + + .ms-ContextualMenu-itemText{ + text-align: center; + } +} \ No newline at end of file From dc8a1daac88ad3a4bdcca02054f411edb1c66901 Mon Sep 17 00:00:00 2001 From: Lijiao Date: Tue, 6 Apr 2021 14:27:40 +0000 Subject: [PATCH 08/17] refactor --- .../tensorboard/ShowTensorBoardDetail.tsx | 3 - .../modals/tensorboard/TensorboardUI.tsx | 140 ++++++++++++ .../src/components/trial-detail/TableList.tsx | 209 +----------------- ts/webui/src/static/const.ts | 2 +- ts/webui/src/static/function.ts | 46 +++- ts/webui/src/static/interface.ts | 13 +- 6 files changed, 204 insertions(+), 209 deletions(-) create mode 100644 ts/webui/src/components/modals/tensorboard/TensorboardUI.tsx diff --git a/ts/webui/src/components/modals/tensorboard/ShowTensorBoardDetail.tsx b/ts/webui/src/components/modals/tensorboard/ShowTensorBoardDetail.tsx index 938b3cb4f1..b5406f34c8 100644 --- a/ts/webui/src/components/modals/tensorboard/ShowTensorBoardDetail.tsx +++ b/ts/webui/src/components/modals/tensorboard/ShowTensorBoardDetail.tsx @@ -1,9 +1,6 @@ import React from 'react'; -// import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; -// import { MANAGER_IP } from '../../../static/const'; import { PrimaryButton, Dialog, DialogType, DialogFooter } from '@fluentui/react'; -// import { caclMonacoEditorHeight, requestAxios } from '../../../static/function'; function ShowTensorBoardDetail(props): any { diff --git a/ts/webui/src/components/modals/tensorboard/TensorboardUI.tsx b/ts/webui/src/components/modals/tensorboard/TensorboardUI.tsx new file mode 100644 index 0000000000..ee6808428a --- /dev/null +++ b/ts/webui/src/components/modals/tensorboard/TensorboardUI.tsx @@ -0,0 +1,140 @@ +import React, {useState} from 'react'; +import PropTypes from 'prop-types'; +import axios from 'axios'; +import { DefaultButton, IContextualMenuProps } from '@fluentui/react'; +import { MANAGER_IP } from '../../../static/const'; +import { disableTensorboard, getTensorboardMenu } from '../../../static/function'; +import { Tensorboard } from '../../../static/interface'; +import StartTensorboardDialog from '../tensorboard/Tensorboard'; +import ShowTensorBoardDetail from '../tensorboard/ShowTensorBoardDetail'; + +function TensorboardUI(props): any { + + let refreshTensorboard = 0; + const { selectedRowIds } = props; + const [queryTensorboardList, setQueryTensorboardList] = useState([]); + const [isReaptedTensorboard, setReaptedTensorboard] = useState(false); + const [tensorboardPanelVisible, setTensorboardPanelVisible] = useState(false); + const [visibleDialog, setVisibleDialog] = useState(false); + const [detailTensorboardPanelVisible, setDetailTensorboardPanelVisible] = useState(false); + const [selectedTensorboard, setSelectedTensorboard] = useState({}); + const [dialogContent, setDialogContent] = useState(''); + const [timerList, setTimerList] = useState([0]); + + function startTrialTensorboard(): void { + const { selectedRowIds } = props; + + const result = queryTensorboardList.filter((item: Tensorboard) => item.trialJobIdList.join(',') === selectedRowIds.join(',')); + if (result.length > 0) { + setReaptedTensorboard(true); + setSelectedTensorboard(result[0]); + setTensorboardPanelVisible(true); + } else { + const startTensorboard = axios.post(`${MANAGER_IP}/tensorboard`, { trials: selectedRowIds.join(',') }); + startTensorboard.then(res => { + if (res.status === 200) { + // setReaptedTensorboard(false); + setSelectedTensorboard(res.data); + setTensorboardPanelVisible(true); + queryAllTensorboard(); + } + }).catch(error => { + setVisibleDialog(true); + setDialogContent(error.message || 'Tensorboard start failed'); + }); + setReaptedTensorboard(false); + } + } + + function queryAllTensorboard (): void { + // if(this.tableListComponent){ + const queryTensorboard = axios.get(`${MANAGER_IP}/tensorboard-tasks`); + queryTensorboard.then(res => { + if (res.status === 200) { + console.info('****************'); + setQueryTensorboardList(res.data); + refreshTensorboard = window.setTimeout(queryAllTensorboard, 10000); + setTimerList(timerList.push(refreshTensorboard) as any); + console.info('list', timerList); + } + }).catch(_error => { + alert('Failed to start tensorboard'); + }); + // } + } + + function stopAllTensorboard (): void { + const delTensorboard = axios.delete(`${MANAGER_IP}/tensorboard-tasks`); + delTensorboard.then(res => { + if (res.status === 200) { + setQueryTensorboardList([]); + console.info('stop list', timerList); + timerList.forEach(item => { + window.clearTimeout(item); + }); + console.info('--------------'); + } + }); + } + + function seeTensorboardWebportal (item: Tensorboard): void { + setSelectedTensorboard(item); + setDetailTensorboardPanelVisible(true); + } + + const isDisableTensorboardBtn = disableTensorboard(selectedRowIds, queryTensorboardList); + const tensorboardMenu: IContextualMenuProps = getTensorboardMenu(queryTensorboardList, stopAllTensorboard, seeTensorboardWebportal); + + // useEffect(() => { + // timerList.forEach(item => { + // console.info('来请定时器'); + // console.info(item); + // window.clearTimeout(item); + // }); + // }, [closeTimer]); + + console.info(visibleDialog); + console.info(dialogContent); + return ( + + startTrialTensorboard()} + disabled={isDisableTensorboardBtn} + /> + { + queryTensorboardList.length !== 0 ? + {queryTensorboardList.length} + : null + } + {tensorboardPanelVisible && ( + { + setTensorboardPanelVisible(false); + }} + /> + )} + {detailTensorboardPanelVisible && ( + { + setDetailTensorboardPanelVisible(false); + }} + /> + )} + + ); +} + +TensorboardUI.propTypes = { + selectedRowIds: PropTypes.array +}; + +export default TensorboardUI; diff --git a/ts/webui/src/components/trial-detail/TableList.tsx b/ts/webui/src/components/trial-detail/TableList.tsx index 91b1331989..155502ef4b 100644 --- a/ts/webui/src/components/trial-detail/TableList.tsx +++ b/ts/webui/src/components/trial-detail/TableList.tsx @@ -11,26 +11,22 @@ import { Stack, StackItem, TooltipHost, - DirectionalHint, - IContextualMenuProps + DirectionalHint } from '@fluentui/react'; -import axios from 'axios'; import { EXPERIMENT, TRIALS } from '../../static/datamodel'; -import { TOOLTIP_BACKGROUND_COLOR, MANAGER_IP } from '../../static/const'; +import { TOOLTIP_BACKGROUND_COLOR } from '../../static/const'; import { convertDuration, formatTimestamp, copyAndSort } from '../../static/function'; import { TableObj, SortInfo } from '../../static/interface'; import { blocked, copy, LineChart, tableListIcon } from '../buttons/Icon'; import ChangeColumnComponent from '../modals/ChangeColumnComponent'; import Compare from '../modals/Compare'; import Customize from '../modals/CustomizedTrial'; -import Tensorboard from '../modals/tensorboard/Tensorboard'; -import ShowTensorBoardDetail from '../modals/tensorboard/ShowTensorBoardDetail'; +import TensorboardUI from '../modals/tensorboard/TensorboardUI'; import KillJob from '../modals/Killjob'; import ExpandableDetails from '../public-child/ExpandableDetails'; import PaginationTable from '../public-child/PaginationTable'; import CopyButton from '../public-child/CopyButton'; import { Trial } from '../../static/model/trial'; -// import DialogDetail from '../modals/tensorboard/RepeatTensorDialog'; import '../../static/style/button.scss'; import '../../static/style/logPath.scss'; import '../../static/style/openRow.scss'; @@ -45,15 +41,6 @@ require('echarts/lib/chart/line'); require('echarts/lib/component/tooltip'); require('echarts/lib/component/title'); -interface Tensorboard { - id: string; - status: string; - trialJobIdList: string[]; - trialLogDirectoryList: string[]; - pid: number; - port: string; -}; - type SearchOptionType = 'id' | 'trialnum' | 'status' | 'parameters'; const searchOptionLiterals = { id: 'ID', @@ -102,23 +89,14 @@ interface TableListState { selectedRowIds: string[]; customizeColumnsDialogVisible: boolean; compareDialogVisible: boolean; - tensorboardPanelVisible: boolean; - detailTensorboardPanelVisible: boolean; intermediateDialogTrial: TableObj | undefined; copiedTrialId: string | undefined; sortInfo: SortInfo; - visibleDialog: boolean; - dialogContent: string; - isReaptedTensorboard: boolean; - queryTensorboardList: Tensorboard[]; - selectedTensorboard?: Tensorboard; } class TableList extends React.Component { private _selection: Selection; private _expandedTrialIds: Set; - private refreshTensorboard!: number | undefined; - private tableListComponent: boolean = false; constructor(props: TableListProps) { super(props); @@ -135,16 +113,10 @@ class TableList extends React.Component { searchText: '', customizeColumnsDialogVisible: false, compareDialogVisible: false, - tensorboardPanelVisible: false, - detailTensorboardPanelVisible: false, selectedRowIds: [], intermediateDialogTrial: undefined, copiedTrialId: undefined, - sortInfo: { field: '', isDescend: true }, - visibleDialog: false, - dialogContent: '', - isReaptedTensorboard: false, - queryTensorboardList: [] + sortInfo: { field: '', isDescend: true } }; this._selection = new Selection({ @@ -482,92 +454,6 @@ class TableList extends React.Component { ); } - private clearAllTensorboard = (): void => { - // clear all 所有状态 - axios(`${MANAGER_IP}/tensorboard-tasks`, { - method: 'DELETE', - headers: { 'Content-Type': 'application/json' }, - }) - .then(res => { - if (res.status === 200) { - // 成功停掉了所有在运行的tensorboard,状态一律称为 stopped - // 提示所有的tensorboard都已经停掉了,清掉所有的query, 关掉定时器 - this.setState(() => ({ queryTensorboardList: [] })); - window.clearTimeout(this.refreshTensorboard); - } - }) - } - - private queryAllTensorboard = (): void => { - // 查询所有trial tensorboard 状态 - // TODO: 加error场景 - if(this.tableListComponent){ - axios(`${MANAGER_IP}/tensorboard-tasks`, { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }) - .then(res => { - if (res.status === 200) { - this.setState(() => ({ queryTensorboardList: res.data.filter(item => item.status !== 'STOPPED') })); - } - }) - .catch(error => { - console.info(error); - }); - this.refreshTensorboard = window.setTimeout(this.queryAllTensorboard, 10000); - } - } - - private startTrialTensorboard = (): void => { - const { selectedRowIds } = this.state; - // 查询所有 trial tensorboard 状态 - axios(`${MANAGER_IP}/tensorboard-tasks`, { - method: 'GET', - headers: { 'Content-Type': 'application/json' }, - }) - .then(res => { - if (res.status === 200) { - const data = res.data; - // ??? 程序一开始空的时候 - const result = data.filter(item => item.status !== 'STOPPED' && item.trialJobIdList.join(',') === selectedRowIds.join(',')); - if (result.length > 0) { - this.setState({ isReaptedTensorboard: true, selectedTensorboard: result[0], tensorboardPanelVisible: true }); - } else { - axios(`${MANAGER_IP}/tensorboard`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - data: { - trials: selectedRowIds.join(',') - } - }) - .then(res => { - if (res.status === 200) { - - // setTimeout((): void => { - this.setState({ isReaptedTensorboard: false, selectedTensorboard: res.data, tensorboardPanelVisible: true }); - // }, 500); - // 每10s刷一次表单数据 - // 如果表单没显示出来也会默默刷新 - this.queryAllTensorboard(); - } - }) - .catch(error => { - this.setState({ isReaptedTensorboard: false, visibleDialog: true, dialogContent: error.message || 'Tensorboard start failed' }); - }); - } - } - }) - } - - private closeDialog = (): void => { - this.setState({ visibleDialog: false }); - } - - private seeTensorboardWebportal = (item: Tensorboard): void => { - // 加弹窗再提醒一下消息 - this.setState({ detailTensorboardPanelVisible: true, selectedTensorboard: item}); - } - componentDidUpdate(prevProps: TableListProps): void { if (this.props.tableSource !== prevProps.tableSource) { this._updateTableSource(); @@ -575,14 +461,7 @@ class TableList extends React.Component { } componentDidMount(): void { - this.tableListComponent = true; this._updateTableSource(); - this.queryAllTensorboard(); - } - - componentWillUnmount(): void { - this.tableListComponent = false; - window.clearTimeout(this.refreshTensorboard); } render(): React.ReactNode { @@ -592,48 +471,12 @@ class TableList extends React.Component { searchType, customizeColumnsDialogVisible, compareDialogVisible, - tensorboardPanelVisible, - detailTensorboardPanelVisible, displayedColumns, selectedRowIds, intermediateDialogTrial, - copiedTrialId, - // visibleDialog, - // dialogContent, - isReaptedTensorboard, - queryTensorboardList, - selectedTensorboard + copiedTrialId } = this.state; - const some: Array = []; - if (queryTensorboardList.length !== 0) { - some.push({ - key: 'delete', - text: 'Stop all tensorBoard', - className: 'clearAll', - onClick: this.clearAllTensorboard - }); - queryTensorboardList.forEach(item => { - some.push({ - key: item.id, - text: `${item.id} ${item.port}`, - className: `CommandBarButton-${item.status}`, - onClick: this.seeTensorboardWebportal.bind(this, item) - }); - }) - - } - const tensorboardMenu: IContextualMenuProps = { - items: some.reverse() as any - }; - // disable tensorboard btn logic - let flag = true; - if (selectedRowIds.length !== 0) { - flag = false; - } - - if (selectedRowIds.length === 0 && queryTensorboardList.length !== 0) { - flag = false; - } + return (
@@ -650,22 +493,7 @@ class TableList extends React.Component { }} disabled={selectedRowIds.length === 0} /> - this.startTrialTensorboard()} - disabled={flag} - /> - { - queryTensorboardList.length !== 0 ? - {queryTensorboardList.length} - : null - } - + @@ -725,23 +553,6 @@ class TableList extends React.Component { }} /> )} - {tensorboardPanelVisible && ( - { - this.setState({ tensorboardPanelVisible: false }); - }} - /> - )} - {detailTensorboardPanelVisible && ( - { - this.setState({ detailTensorboardPanelVisible: false }); - }} - /> - )} {intermediateDialogTrial !== undefined && ( { this.setState({ copiedTrialId: undefined }); }} /> - {/* for all trials tensorboard failed modal */} - {/* {visibleDialog && - } */}
); } diff --git a/ts/webui/src/static/const.ts b/ts/webui/src/static/const.ts index dcafa2a0d0..ed9086142e 100644 --- a/ts/webui/src/static/const.ts +++ b/ts/webui/src/static/const.ts @@ -2,7 +2,7 @@ const METRIC_GROUP_UPDATE_THRESHOLD = 100; const METRIC_GROUP_UPDATE_SIZE = 20; -const MANAGER_IP = `http://13.77.78.63:9999/api/v1/nni`; +const MANAGER_IP = `http://13.77.78.63:8888/api/v1/nni`; // const MANAGER_IP = `http://127.0.0.1:9999/api/v1/nni`; // const MANAGER_IP = `http://192.168.199.1:9999/api/v1/nni`; const DOWNLOAD_IP = `/logs`; diff --git a/ts/webui/src/static/function.ts b/ts/webui/src/static/function.ts index 2091c75e8f..3c96e67afc 100644 --- a/ts/webui/src/static/function.ts +++ b/ts/webui/src/static/function.ts @@ -1,7 +1,8 @@ import * as JSON5 from 'json5'; import axios from 'axios'; +import { IContextualMenuProps } from '@fluentui/react'; import { MANAGER_IP } from './const'; -import { MetricDataRecord, FinalType, TableObj } from './interface'; +import { MetricDataRecord, FinalType, TableObj, Tensorboard } from './interface'; async function requestAxios(url: string): Promise { const response = await axios.get(url); @@ -305,6 +306,45 @@ function copyAndSort(items: T[], columnKey: string, isSortedDescending?: bool return (isSortedDescending ? a[key] < b[key] : a[key] > b[key]) ? 1 : -1; }); } + +function disableTensorboard(selectedRowIds: string[], queryTensorboardList: Tensorboard[]): boolean{ + let flag = true; + + if (selectedRowIds.length !== 0) { + flag = false; + } + + if (selectedRowIds.length === 0 && queryTensorboardList.length !== 0) { + flag = false; + } + + return flag; +} + +function getTensorboardMenu(queryTensorboardList: Tensorboard[], stopFunc, seeDetailFunc): IContextualMenuProps{ + const result: Array = []; + if (queryTensorboardList.length !== 0) { + result.push({ + key: 'delete', + text: 'Stop all tensorBoard', + className: 'clearAll', + onClick: stopFunc + }); + queryTensorboardList.forEach(item => { + result.push({ + key: item.id, + text: `${item.id}`, + className: `CommandBarButton-${item.status}`, + onClick: (): void => seeDetailFunc(item) + }); + }) + } + const tensorboardMenu: IContextualMenuProps = { + items: result.reverse() as any + }; + + return tensorboardMenu; +} export { convertTime, convertDuration, @@ -327,5 +367,7 @@ export { formatComplexTypeValue, isManagerExperimentPage, caclMonacoEditorHeight, - copyAndSort + copyAndSort, + disableTensorboard, + getTensorboardMenu }; diff --git a/ts/webui/src/static/interface.ts b/ts/webui/src/static/interface.ts index ad32122461..8673d6efe5 100644 --- a/ts/webui/src/static/interface.ts +++ b/ts/webui/src/static/interface.ts @@ -234,6 +234,16 @@ interface AllExperimentList { webuiUrl: string[]; logDir: string[]; } + +interface Tensorboard { + id: string; + status: string; + trialJobIdList: string[]; + trialLogDirectoryList: string[]; + pid: number; + port: string; +}; + export { TableObj, TableRecord, @@ -257,5 +267,6 @@ export { SingleAxis, MultipleAxes, SortInfo, - AllExperimentList + AllExperimentList, + Tensorboard }; From c88fd13dbf5b303946a3b27d82ea57e808024175 Mon Sep 17 00:00:00 2001 From: Lijiao Date: Wed, 7 Apr 2021 08:37:07 +0000 Subject: [PATCH 09/17] fix todo list item --- ts/webui/.eslintrc | 3 +- .../modals/tensorboard/Tensorboard.tsx | 50 ++++++++--- .../modals/tensorboard/TensorboardUI.tsx | 90 ++++++++++--------- 3 files changed, 86 insertions(+), 57 deletions(-) diff --git a/ts/webui/.eslintrc b/ts/webui/.eslintrc index 39d1918635..78e79460a8 100644 --- a/ts/webui/.eslintrc +++ b/ts/webui/.eslintrc @@ -21,7 +21,7 @@ "prettier" ], "rules": { - "prettier/prettier": 0, + "prettier/prettier": 2, "@typescript-eslint/no-explicit-any": 0, "@typescript-eslint/no-namespace": 0, "@typescript-eslint/consistent-type-assertions": 0, @@ -32,7 +32,6 @@ "arrow-parens": [2, "as-needed"], "no-inner-declarations": 0, "no-empty": 2, - "no-console": 0, "no-multiple-empty-lines": [2, { "max": 1 }], "react/display-name": 0 }, diff --git a/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx b/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx index eeb77c533d..04c9e40a7a 100644 --- a/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx +++ b/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx @@ -4,11 +4,11 @@ import { PrimaryButton, Dialog, DialogType, DialogFooter } from '@fluentui/react function StartTensorboardDialog(props): any { - const { isReaptedTensorboard, onHideDialog, item } = props; + const { isReaptedTensorboard, onHideDialog, item, isShowTensorboardDetail, errorMessage } = props; const dialogContentProps = { type: DialogType.normal, - title: 'Tensorboard', + title: `${isShowTensorboardDetail ? item.id : 'Tensorboard'}`, closeButtonAriaLabel: 'OK', }; @@ -19,6 +19,20 @@ function StartTensorboardDialog(props): any { onHideDialog(); } + const startTensorboard = ( + isReaptedTensorboard + ? +
+ You had started this tensorboard with these trials: {item.trialJobIdList.join(', ')}. +
Its tensorboard id: {item.id}
+
+ : +
+ You are starting a new Tensorboard with trials: {item.trialJobIdList.join(', ')}. +
Tensorboard id: {item.id}
+
+ ); + return ( ); } StartTensorboardDialog.propTypes = { isReaptedTensorboard: PropTypes.bool, + isShowTensorboardDetail: PropTypes.bool, onHideDialog: PropTypes.func, - item: PropTypes.object + item: PropTypes.object, + errorMessage: PropTypes.object }; export default StartTensorboardDialog; diff --git a/ts/webui/src/components/modals/tensorboard/TensorboardUI.tsx b/ts/webui/src/components/modals/tensorboard/TensorboardUI.tsx index ee6808428a..ec719b2a70 100644 --- a/ts/webui/src/components/modals/tensorboard/TensorboardUI.tsx +++ b/ts/webui/src/components/modals/tensorboard/TensorboardUI.tsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react'; +import React, {useState, useEffect} from 'react'; import PropTypes from 'prop-types'; import axios from 'axios'; import { DefaultButton, IContextualMenuProps } from '@fluentui/react'; @@ -6,7 +6,7 @@ import { MANAGER_IP } from '../../../static/const'; import { disableTensorboard, getTensorboardMenu } from '../../../static/function'; import { Tensorboard } from '../../../static/interface'; import StartTensorboardDialog from '../tensorboard/Tensorboard'; -import ShowTensorBoardDetail from '../tensorboard/ShowTensorBoardDetail'; +// import ShowTensorBoardDetail from '../tensorboard/ShowTensorBoardDetail'; function TensorboardUI(props): any { @@ -15,15 +15,14 @@ function TensorboardUI(props): any { const [queryTensorboardList, setQueryTensorboardList] = useState([]); const [isReaptedTensorboard, setReaptedTensorboard] = useState(false); const [tensorboardPanelVisible, setTensorboardPanelVisible] = useState(false); - const [visibleDialog, setVisibleDialog] = useState(false); - const [detailTensorboardPanelVisible, setDetailTensorboardPanelVisible] = useState(false); + const [isShowTensorboardDetail, setIsShowTensorboardDetail] = useState(false); const [selectedTensorboard, setSelectedTensorboard] = useState({}); - const [dialogContent, setDialogContent] = useState(''); + const [errorMessage, setErrorMessage] = useState({}); const [timerList, setTimerList] = useState([0]); function startTrialTensorboard(): void { const { selectedRowIds } = props; - + setIsShowTensorboardDetail(false); const result = queryTensorboardList.filter((item: Tensorboard) => item.trialJobIdList.join(',') === selectedRowIds.join(',')); if (result.length > 0) { setReaptedTensorboard(true); @@ -33,19 +32,31 @@ function TensorboardUI(props): any { const startTensorboard = axios.post(`${MANAGER_IP}/tensorboard`, { trials: selectedRowIds.join(',') }); startTensorboard.then(res => { if (res.status === 200) { - // setReaptedTensorboard(false); setSelectedTensorboard(res.data); setTensorboardPanelVisible(true); queryAllTensorboard(); } }).catch(error => { - setVisibleDialog(true); - setDialogContent(error.message || 'Tensorboard start failed'); + setTensorboardPanelVisible(true); + setErrorMessage({ + error: true, + message: error.message || 'Tensorboard start failed' + }); }); setReaptedTensorboard(false); } } + function initQueryTensorboard(): void { + const queryTensorboard = axios.get(`${MANAGER_IP}/tensorboard-tasks`); + queryTensorboard.then(res => { + if (res.status === 200) { + console.info('***init***'); + setQueryTensorboardList(res.data); + } + }); + } + function queryAllTensorboard (): void { // if(this.tableListComponent){ const queryTensorboard = axios.get(`${MANAGER_IP}/tensorboard-tasks`); @@ -53,9 +64,11 @@ function TensorboardUI(props): any { if (res.status === 200) { console.info('****************'); setQueryTensorboardList(res.data); + closeTimer(); refreshTensorboard = window.setTimeout(queryAllTensorboard, 10000); - setTimerList(timerList.push(refreshTensorboard) as any); - console.info('list', timerList); + const temp = timerList; + temp.push(refreshTensorboard); + setTimerList(temp); } }).catch(_error => { alert('Failed to start tensorboard'); @@ -63,38 +76,35 @@ function TensorboardUI(props): any { // } } + function closeTimer(): void { + timerList.forEach(item => { + window.clearTimeout(item); + }); + } + function stopAllTensorboard (): void { const delTensorboard = axios.delete(`${MANAGER_IP}/tensorboard-tasks`); delTensorboard.then(res => { if (res.status === 200) { setQueryTensorboardList([]); - console.info('stop list', timerList); - timerList.forEach(item => { - window.clearTimeout(item); - }); - console.info('--------------'); + closeTimer(); } }); } function seeTensorboardWebportal (item: Tensorboard): void { setSelectedTensorboard(item); - setDetailTensorboardPanelVisible(true); + setIsShowTensorboardDetail(true); + setTensorboardPanelVisible(true); } const isDisableTensorboardBtn = disableTensorboard(selectedRowIds, queryTensorboardList); const tensorboardMenu: IContextualMenuProps = getTensorboardMenu(queryTensorboardList, stopAllTensorboard, seeTensorboardWebportal); - - // useEffect(() => { - // timerList.forEach(item => { - // console.info('来请定时器'); - // console.info(item); - // window.clearTimeout(item); - // }); - // }, [closeTimer]); - console.info(visibleDialog); - console.info(dialogContent); + useEffect(() => { + initQueryTensorboard(); + }, []); + return ( { - setTensorboardPanelVisible(false); - }} - /> - )} - {detailTensorboardPanelVisible && ( - { - setDetailTensorboardPanelVisible(false); - }} - /> - )} + { + setTensorboardPanelVisible(false); + }} + /> + )} ); } From 2012d85766b3cb2cac7a8df319d0c78286d1c7fa Mon Sep 17 00:00:00 2001 From: Lijiao Date: Wed, 7 Apr 2021 08:43:56 +0000 Subject: [PATCH 10/17] fix lint --- .../tensorboard/ShowTensorBoardDetail.tsx | 43 ---------- .../modals/tensorboard/Tensorboard.tsx | 80 ------------------- .../modals/tensorboard/TensorboardDialog.tsx | 75 +++++++++++++++++ .../modals/tensorboard/TensorboardUI.tsx | 72 +++++++++-------- .../src/components/trial-detail/TableList.tsx | 13 +-- ts/webui/src/static/function.ts | 8 +- ts/webui/src/static/interface.ts | 2 +- ts/webui/src/static/style/common.scss | 14 ++-- ts/webui/src/static/style/tensorboard.scss | 41 +++++----- 9 files changed, 154 insertions(+), 194 deletions(-) delete mode 100644 ts/webui/src/components/modals/tensorboard/ShowTensorBoardDetail.tsx delete mode 100644 ts/webui/src/components/modals/tensorboard/Tensorboard.tsx create mode 100644 ts/webui/src/components/modals/tensorboard/TensorboardDialog.tsx diff --git a/ts/webui/src/components/modals/tensorboard/ShowTensorBoardDetail.tsx b/ts/webui/src/components/modals/tensorboard/ShowTensorBoardDetail.tsx deleted file mode 100644 index b5406f34c8..0000000000 --- a/ts/webui/src/components/modals/tensorboard/ShowTensorBoardDetail.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { PrimaryButton, Dialog, DialogType, DialogFooter } from '@fluentui/react'; - -function ShowTensorBoardDetail(props): any { - - const { onHideDialog, item } = props; - - const dialogContentProps = { - type: DialogType.normal, - title: item.id, - closeButtonAriaLabel: 'Close', - }; - - function gotoTensorboard(): void { - const hostname = window.location.hostname; - const protocol = window.location.protocol; - window.open(`${protocol}//${hostname}:${item.port}`); - onHideDialog(); - } - - return ( - - ); -} - -ShowTensorBoardDetail.propTypes = { - item: PropTypes.object, - onHideDialog: PropTypes.func -}; - -export default ShowTensorBoardDetail; diff --git a/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx b/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx deleted file mode 100644 index 04c9e40a7a..0000000000 --- a/ts/webui/src/components/modals/tensorboard/Tensorboard.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { PrimaryButton, Dialog, DialogType, DialogFooter } from '@fluentui/react'; - -function StartTensorboardDialog(props): any { - - const { isReaptedTensorboard, onHideDialog, item, isShowTensorboardDetail, errorMessage } = props; - - const dialogContentProps = { - type: DialogType.normal, - title: `${isShowTensorboardDetail ? item.id : 'Tensorboard'}`, - closeButtonAriaLabel: 'OK', - }; - - function gotoTensorboard(): void { - const hostname = window.location.hostname; - const protocol = window.location.protocol; - window.open(`${protocol}//${hostname}:${item.port}`); - onHideDialog(); - } - - const startTensorboard = ( - isReaptedTensorboard - ? -
- You had started this tensorboard with these trials: {item.trialJobIdList.join(', ')}. -
Its tensorboard id: {item.id}
-
- : -
- You are starting a new Tensorboard with trials: {item.trialJobIdList.join(', ')}. -
Tensorboard id: {item.id}
-
- ); - - return ( - - ); -} - -StartTensorboardDialog.propTypes = { - isReaptedTensorboard: PropTypes.bool, - isShowTensorboardDetail: PropTypes.bool, - onHideDialog: PropTypes.func, - item: PropTypes.object, - errorMessage: PropTypes.object -}; - -export default StartTensorboardDialog; diff --git a/ts/webui/src/components/modals/tensorboard/TensorboardDialog.tsx b/ts/webui/src/components/modals/tensorboard/TensorboardDialog.tsx new file mode 100644 index 0000000000..513b2f5c35 --- /dev/null +++ b/ts/webui/src/components/modals/tensorboard/TensorboardDialog.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { PrimaryButton, Dialog, DialogType, DialogFooter } from '@fluentui/react'; + +function TensorboardDialog(props): any { + const { isReaptedStartTensorboard, onHideDialog, item, isShowTensorboardDetail, errorMessage } = props; + + const dialogContentProps = { + type: DialogType.normal, + title: `${isShowTensorboardDetail ? item.id : 'Tensorboard'}` + }; + + function gotoTensorboard(): void { + const hostname = window.location.hostname; + const protocol = window.location.protocol; + window.open(`${protocol}//${hostname}:${item.port}`); + onHideDialog(); + } + + const startTensorboard = isReaptedStartTensorboard ? ( +
+ You had started this tensorboard with these trials:{' '} + {item.trialJobIdList.join(', ')}. +
+ Its tensorboard id: {item.id} +
+
+ ) : ( +
+ You are starting a new Tensorboard with trials:{' '} + {item.trialJobIdList.join(', ')}. +
+ Tensorboard id: {item.id} +
+
+ ); + + return ( + + ); +} + +TensorboardDialog.propTypes = { + isReaptedStartTensorboard: PropTypes.bool, + isShowTensorboardDetail: PropTypes.bool, + onHideDialog: PropTypes.func, + item: PropTypes.object, + errorMessage: PropTypes.object +}; + +export default TensorboardDialog; diff --git a/ts/webui/src/components/modals/tensorboard/TensorboardUI.tsx b/ts/webui/src/components/modals/tensorboard/TensorboardUI.tsx index ec719b2a70..d38326a2bf 100644 --- a/ts/webui/src/components/modals/tensorboard/TensorboardUI.tsx +++ b/ts/webui/src/components/modals/tensorboard/TensorboardUI.tsx @@ -1,19 +1,17 @@ -import React, {useState, useEffect} from 'react'; +import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import axios from 'axios'; import { DefaultButton, IContextualMenuProps } from '@fluentui/react'; import { MANAGER_IP } from '../../../static/const'; import { disableTensorboard, getTensorboardMenu } from '../../../static/function'; import { Tensorboard } from '../../../static/interface'; -import StartTensorboardDialog from '../tensorboard/Tensorboard'; -// import ShowTensorBoardDetail from '../tensorboard/ShowTensorBoardDetail'; +import TensorboardDialog from './TensorboardDialog'; function TensorboardUI(props): any { - let refreshTensorboard = 0; const { selectedRowIds } = props; const [queryTensorboardList, setQueryTensorboardList] = useState([]); - const [isReaptedTensorboard, setReaptedTensorboard] = useState(false); + const [isReaptedStartTensorboard, setReaptedTensorboard] = useState(false); const [tensorboardPanelVisible, setTensorboardPanelVisible] = useState(false); const [isShowTensorboardDetail, setIsShowTensorboardDetail] = useState(false); const [selectedTensorboard, setSelectedTensorboard] = useState({}); @@ -23,26 +21,30 @@ function TensorboardUI(props): any { function startTrialTensorboard(): void { const { selectedRowIds } = props; setIsShowTensorboardDetail(false); - const result = queryTensorboardList.filter((item: Tensorboard) => item.trialJobIdList.join(',') === selectedRowIds.join(',')); + const result = queryTensorboardList.filter( + (item: Tensorboard) => item.trialJobIdList.join(',') === selectedRowIds.join(',') + ); if (result.length > 0) { setReaptedTensorboard(true); setSelectedTensorboard(result[0]); setTensorboardPanelVisible(true); } else { const startTensorboard = axios.post(`${MANAGER_IP}/tensorboard`, { trials: selectedRowIds.join(',') }); - startTensorboard.then(res => { - if (res.status === 200) { - setSelectedTensorboard(res.data); + startTensorboard + .then(res => { + if (res.status === 200) { + setSelectedTensorboard(res.data); + setTensorboardPanelVisible(true); + queryAllTensorboard(); + } + }) + .catch(error => { setTensorboardPanelVisible(true); - queryAllTensorboard(); - } - }).catch(error => { - setTensorboardPanelVisible(true); - setErrorMessage({ - error: true, - message: error.message || 'Tensorboard start failed' + setErrorMessage({ + error: true, + message: error.message || 'Tensorboard start failed' + }); }); - }); setReaptedTensorboard(false); } } @@ -51,18 +53,17 @@ function TensorboardUI(props): any { const queryTensorboard = axios.get(`${MANAGER_IP}/tensorboard-tasks`); queryTensorboard.then(res => { if (res.status === 200) { - console.info('***init***'); setQueryTensorboardList(res.data); } }); } - function queryAllTensorboard (): void { + function queryAllTensorboard(): void { // if(this.tableListComponent){ - const queryTensorboard = axios.get(`${MANAGER_IP}/tensorboard-tasks`); - queryTensorboard.then(res => { + const queryTensorboard = axios.get(`${MANAGER_IP}/tensorboard-tasks`); + queryTensorboard + .then(res => { if (res.status === 200) { - console.info('****************'); setQueryTensorboardList(res.data); closeTimer(); refreshTensorboard = window.setTimeout(queryAllTensorboard, 10000); @@ -70,7 +71,8 @@ function TensorboardUI(props): any { temp.push(refreshTensorboard); setTimerList(temp); } - }).catch(_error => { + }) + .catch(_error => { alert('Failed to start tensorboard'); }); // } @@ -82,7 +84,7 @@ function TensorboardUI(props): any { }); } - function stopAllTensorboard (): void { + function stopAllTensorboard(): void { const delTensorboard = axios.delete(`${MANAGER_IP}/tensorboard-tasks`); delTensorboard.then(res => { if (res.status === 200) { @@ -92,14 +94,18 @@ function TensorboardUI(props): any { }); } - function seeTensorboardWebportal (item: Tensorboard): void { + function seeTensorboardWebportal(item: Tensorboard): void { setSelectedTensorboard(item); setIsShowTensorboardDetail(true); setTensorboardPanelVisible(true); } const isDisableTensorboardBtn = disableTensorboard(selectedRowIds, queryTensorboardList); - const tensorboardMenu: IContextualMenuProps = getTensorboardMenu(queryTensorboardList, stopAllTensorboard, seeTensorboardWebportal); + const tensorboardMenu: IContextualMenuProps = getTensorboardMenu( + queryTensorboardList, + stopAllTensorboard, + seeTensorboardWebportal + ); useEffect(() => { initQueryTensorboard(); @@ -111,20 +117,16 @@ function TensorboardUI(props): any { text='TensorBoard' className='elementMarginLeft' split - splitButtonAriaLabel="See 2 options" - aria-roledescription="split button" + splitButtonAriaLabel='See 2 options' + aria-roledescription='split button' menuProps={tensorboardMenu} onClick={(): void => startTrialTensorboard()} disabled={isDisableTensorboardBtn} /> - { - queryTensorboardList.length !== 0 ? - {queryTensorboardList.length} - : null - } + {queryTensorboardList.length !== 0 ? {queryTensorboardList.length} : null} {tensorboardPanelVisible && ( - { displayedColumns: localStorage.getItem('columns') !== null ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - JSON.parse(localStorage.getItem('columns')!) + JSON.parse(localStorage.getItem('columns')!) : defaultDisplayedColumns, columns: [], searchType: 'id', @@ -493,7 +493,7 @@ class TableList extends React.Component { }} disabled={selectedRowIds.length === 0} /> - + @@ -516,10 +516,11 @@ class TableList extends React.Component { diff --git a/ts/webui/src/static/function.ts b/ts/webui/src/static/function.ts index 3c96e67afc..bf10d8ef6e 100644 --- a/ts/webui/src/static/function.ts +++ b/ts/webui/src/static/function.ts @@ -307,13 +307,13 @@ function copyAndSort(items: T[], columnKey: string, isSortedDescending?: bool }); } -function disableTensorboard(selectedRowIds: string[], queryTensorboardList: Tensorboard[]): boolean{ +function disableTensorboard(selectedRowIds: string[], queryTensorboardList: Tensorboard[]): boolean { let flag = true; if (selectedRowIds.length !== 0) { flag = false; } - + if (selectedRowIds.length === 0 && queryTensorboardList.length !== 0) { flag = false; } @@ -321,7 +321,7 @@ function disableTensorboard(selectedRowIds: string[], queryTensorboardList: Tens return flag; } -function getTensorboardMenu(queryTensorboardList: Tensorboard[], stopFunc, seeDetailFunc): IContextualMenuProps{ +function getTensorboardMenu(queryTensorboardList: Tensorboard[], stopFunc, seeDetailFunc): IContextualMenuProps { const result: Array = []; if (queryTensorboardList.length !== 0) { result.push({ @@ -337,7 +337,7 @@ function getTensorboardMenu(queryTensorboardList: Tensorboard[], stopFunc, seeDe className: `CommandBarButton-${item.status}`, onClick: (): void => seeDetailFunc(item) }); - }) + }); } const tensorboardMenu: IContextualMenuProps = { items: result.reverse() as any diff --git a/ts/webui/src/static/interface.ts b/ts/webui/src/static/interface.ts index 8673d6efe5..593caf23c0 100644 --- a/ts/webui/src/static/interface.ts +++ b/ts/webui/src/static/interface.ts @@ -242,7 +242,7 @@ interface Tensorboard { trialLogDirectoryList: string[]; pid: number; port: string; -}; +} export { TableObj, diff --git a/ts/webui/src/static/style/common.scss b/ts/webui/src/static/style/common.scss index 66efa9ecb4..95af28b5d6 100644 --- a/ts/webui/src/static/style/common.scss +++ b/ts/webui/src/static/style/common.scss @@ -52,24 +52,24 @@ $themeBlue: #0071bc; } .elementMarginLeft { - margin-left: 10px; + margin-left: 10px; } .operationBtn { - i { - color: $themeBlue; - } + i { + color: $themeBlue; + } } /* for table row border */ .ms-List-cell { - border-bottom: 1px solid #EEECEA; + border-bottom: 1px solid #eeecea; } .line-height { line-height: 30px; } -.bold{ +.bold { font-weight: bold; -} \ No newline at end of file +} diff --git a/ts/webui/src/static/style/tensorboard.scss b/ts/webui/src/static/style/tensorboard.scss index b34b80e6bd..4eb56cc392 100644 --- a/ts/webui/src/static/style/tensorboard.scss +++ b/ts/webui/src/static/style/tensorboard.scss @@ -1,8 +1,8 @@ -$cicleBg: #009EE7; +$cicleBg: #009ee7; $size: 24px; .circle { - display:inline-block; + display: inline-block; width: $size; height: $size; border-radius: 50%; @@ -16,44 +16,49 @@ $size: 24px; left: -20px; } -.CommandBarButton{ - &-RUNNING{ - .ms-ContextualMenu-itemText:after{ +.CommandBarButton { + &-RUNNING { + .ms-ContextualMenu-itemText::after { content: '(RUNNING)'; color: #0071bc; margin-left: 15px; } } - &-STOPPED{ - .ms-ContextualMenu-itemText:after{ + + &-STOPPED { + .ms-ContextualMenu-itemText::after { content: '(STOPPED)'; color: green; margin-left: 15px; } } - &-STOPPING{ - .ms-ContextualMenu-itemText:after{ + + &-STOPPING { + .ms-ContextualMenu-itemText::after { content: '(STOPPING)'; color: green; margin-left: 15px; } } - &-DOWNLOADING_DATA{ - .ms-ContextualMenu-itemText:after{ + + &-DOWNLOADING_DATA { + .ms-ContextualMenu-itemText::after { content: '(DOWNLOADING_DATA)'; color: green; margin-left: 15px; } } - &-ERROR{ - .ms-ContextualMenu-itemText:after{ + + &-ERROR { + .ms-ContextualMenu-itemText::after { content: '(ERROR)'; color: red; margin-left: 15px; } } - &-FAIL_DOWNLOAD_DATA{ - .ms-ContextualMenu-itemText:after{ + + &-FAIL_DOWNLOAD_DATA { + .ms-ContextualMenu-itemText::after { content: '(FAIL_DOWNLOAD_DATA)'; color: red; margin-left: 15px; @@ -61,10 +66,10 @@ $size: 24px; } } -.clearAll{ +.clearAll { border-top: 1px solid #ccc; - .ms-ContextualMenu-itemText{ + .ms-ContextualMenu-itemText { text-align: center; } -} \ No newline at end of file +} From 35a7af5d4207387dc2d30c881e7a14aaa68a6e92 Mon Sep 17 00:00:00 2001 From: Lijiao Date: Wed, 7 Apr 2021 09:57:18 +0000 Subject: [PATCH 11/17] fix clear timer issue --- .../managementExp/ExperimentManager.tsx | 12 ++- .../modals/tensorboard/TensorboardDialog.tsx | 10 +-- .../modals/tensorboard/TensorboardUI.tsx | 85 +++++++++---------- .../components/overview/count/TrialCount.tsx | 13 ++- 4 files changed, 64 insertions(+), 56 deletions(-) diff --git a/ts/webui/src/components/managementExp/ExperimentManager.tsx b/ts/webui/src/components/managementExp/ExperimentManager.tsx index a0e62b1bbb..c6efc56bbc 100644 --- a/ts/webui/src/components/managementExp/ExperimentManager.tsx +++ b/ts/webui/src/components/managementExp/ExperimentManager.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Stack, DetailsList, DefaultButton, Icon, SearchBox, IColumn } from '@fluentui/react'; +import { Stack, DetailsList, DefaultButton, Icon, SearchBox, IColumn, IStackTokens } from '@fluentui/react'; import { ExperimentsManager } from '../../static/model/experimentsManager'; import { expformatTimestamp, copyAndSort } from '../../static/function'; import { AllExperimentList, SortInfo } from '../../static/interface'; @@ -18,6 +18,10 @@ import '../../static/style/experiment/experiment.scss'; import '../../static/style/overview/probar.scss'; import '../../static/style/tableStatus.css'; +const expTokens: IStackTokens = { + childrenGap: 25 +}; + interface ExpListState { columns: IColumn[]; platform: string[]; @@ -111,7 +115,11 @@ class Experiment extends React.Component<{}, ExpListState> { - + - You had started this tensorboard with these trials:{' '} - {item.trialJobIdList.join(', ')}. + You had started this tensorboard with these trials: + {item.trialJobIdList.join(' ,')}.
Its tensorboard id: {item.id}
) : (
- You are starting a new Tensorboard with trials:{' '} - {item.trialJobIdList.join(', ')}. + You are starting a new Tensorboard with trials: + {item.trialJobIdList.join(' ,')}.
Tensorboard id: {item.id}
@@ -36,7 +36,7 @@ function TensorboardDialog(props): any { ); return ( -