Skip to content
This repository has been archived by the owner on Sep 18, 2024. It is now read-only.

Commit

Permalink
[WebUI] support tensorboard (#3361)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lijiaoa authored Apr 9, 2021
1 parent 817ec68 commit aec4ce1
Show file tree
Hide file tree
Showing 10 changed files with 408 additions and 18 deletions.
12 changes: 10 additions & 2 deletions ts/webui/src/components/managementExp/ExperimentManager.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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[];
Expand Down Expand Up @@ -111,7 +115,11 @@ class Experiment extends React.Component<{}, ExpListState> {
</DefaultButton>
</div>
</Stack>
<Stack className={`${hideFilter ? 'hidden' : ''} filter-condition`} horizontal gap={25}>
<Stack
className={`${hideFilter ? 'hidden' : ''} filter-condition`}
horizontal
tokens={expTokens}
>
<FilterBtns
platform={platform}
selectedStatus={selectedStatus}
Expand Down
2 changes: 1 addition & 1 deletion ts/webui/src/components/modals/Compare.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
75 changes: 75 additions & 0 deletions ts/webui/src/components/modals/tensorboard/TensorboardDialog.tsx
Original file line number Diff line number Diff line change
@@ -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 ? (
<div>
You had started this tensorBoard with these trials:
<span className='bold'>{item.trialJobIdList.join(', ')}</span>.
<div className='line-height'>
Its tensorBoard id: <span className='bold'>{item.id}</span>
</div>
</div>
) : (
<div>
You are starting a new TensorBoard with trials:
<span className='bold'>{item.trialJobIdList.join(', ')}</span>.
<div className='line-height'>
TensorBoard id: <span className='bold'>{item.id}</span>
</div>
</div>
);

return (
<Dialog hidden={false} dialogContentProps={dialogContentProps} modalProps={{ className: 'dialog' }}>
{errorMessage.error ? (
<div>
<span>Failed to start tensorBoard! Error message: {errorMessage.message}</span>.
</div>
) : isShowTensorboardDetail ? (
<div>
This tensorBoard with trials: <span className='bold'>{item.trialJobIdList.join(', ')}</span>.
</div>
) : (
startTensorboard
)}
{errorMessage.error ? (
<DialogFooter>
<PrimaryButton onClick={onHideDialog} text='Close' />
</DialogFooter>
) : (
<DialogFooter>
<PrimaryButton
onClick={gotoTensorboard}
text={`${isShowTensorboardDetail ? 'See tensorBoard' : 'Ok'}`}
/>
</DialogFooter>
)}
</Dialog>
);
}

TensorboardDialog.propTypes = {
isReaptedStartTensorboard: PropTypes.bool,
isShowTensorboardDetail: PropTypes.bool,
onHideDialog: PropTypes.func,
item: PropTypes.object,
errorMessage: PropTypes.object
};

export default TensorboardDialog;
144 changes: 144 additions & 0 deletions ts/webui/src/components/modals/tensorboard/TensorboardUI.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
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 TensorboardDialog from './TensorboardDialog';

function TensorboardUI(props): any {
let refreshTensorboard = 0;
const { selectedRowIds } = props;
const [queryTensorboardList, setQueryTensorboardList] = useState([]);
const [isReaptedStartTensorboard, setReaptedTensorboard] = useState(false);
const [tensorboardPanelVisible, setTensorboardPanelVisible] = useState(false);
const [isShowTensorboardDetail, setIsShowTensorboardDetail] = useState(false);
const [selectedTensorboard, setSelectedTensorboard] = useState({});
const [errorMessage, setErrorMessage] = useState({});
const [timerList, setTimerList] = useState([0]);

function startTrialTensorboard(): void {
const { selectedRowIds } = props;
if (selectedRowIds.length > 0) {
setIsShowTensorboardDetail(false);
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);
closeTimer();
queryAllTensorboard();
setErrorMessage({ error: false, message: '' });
setTensorboardPanelVisible(true);
}
})
.catch(error => {
setErrorMessage({
error: true,
message: error.message || 'Tensorboard start failed'
});
setTensorboardPanelVisible(true);
});
setReaptedTensorboard(false);
}
} else {
alert('Please select trials first!');
}
}

function queryAllTensorboard(): void {
const queryTensorboard = axios.get(`${MANAGER_IP}/tensorboard-tasks`);
queryTensorboard.then(res => {
if (res.status === 200) {
setQueryTensorboardList(res.data);
if (res.data.length !== 0) {
refreshTensorboard = window.setTimeout(queryAllTensorboard, 10000);
const storeTimerList = timerList;
storeTimerList.push(refreshTensorboard);
setTimerList(storeTimerList);
}
}
});
}

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([]);
closeTimer();
}
});
}

function seeTensorboardWebportal(item: Tensorboard): void {
setSelectedTensorboard(item);
setIsShowTensorboardDetail(true);
setTensorboardPanelVisible(true);
}

const isDisableTensorboardBtn = disableTensorboard(selectedRowIds, queryTensorboardList);
const tensorboardMenu: IContextualMenuProps = getTensorboardMenu(
queryTensorboardList,
stopAllTensorboard,
seeTensorboardWebportal
);

useEffect(() => {
queryAllTensorboard();
// clear timer when component is unmounted
return function closeTimer(): void {
timerList.forEach(item => {
window.clearTimeout(item);
});
};
}, []);

return (
<React.Fragment>
<DefaultButton
text='TensorBoard'
className='elementMarginLeft'
split
splitButtonAriaLabel='See 2 options'
aria-roledescription='split button'
menuProps={tensorboardMenu}
onClick={(): void => startTrialTensorboard()}
disabled={isDisableTensorboardBtn}
/>
{queryTensorboardList.length !== 0 ? <span className='circle'>{queryTensorboardList.length}</span> : null}
{tensorboardPanelVisible && (
<TensorboardDialog
isReaptedStartTensorboard={isReaptedStartTensorboard}
isShowTensorboardDetail={isShowTensorboardDetail}
errorMessage={errorMessage}
item={selectedTensorboard}
onHideDialog={(): void => {
setTensorboardPanelVisible(false);
}}
/>
)}
</React.Fragment>
);
}

TensorboardUI.propTypes = {
selectedRowIds: PropTypes.array
};

export default TensorboardUI;
13 changes: 10 additions & 3 deletions ts/webui/src/components/overview/count/TrialCount.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import * as React from 'react';
import { Stack, TooltipHost, ProgressIndicator, DirectionalHint } from '@fluentui/react';
import { Stack, TooltipHost, ProgressIndicator, DirectionalHint, IStackTokens } from '@fluentui/react';
import { EXPERIMENT, TRIALS } from '../../../static/datamodel';
import { CONTROLTYPE, TOOLTIP_BACKGROUND_COLOR, MAX_TRIAL_NUMBERS } from '../../../static/const';
import { EditExperimentParam } from './EditExperimentParam';
import { EditExpeParamContext } from './context';
import { ExpDurationContext } from './ExpDurationContext';
import { leftProgress, rightEidtParam, progressHeight } from './commonStyle';

const line1Tokens: IStackTokens = {
childrenGap: 60
};
const line2Tokens: IStackTokens = {
childrenGap: 80
};

export const TrialCount = (): any => {
const count = TRIALS.countStatus();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
Expand Down Expand Up @@ -52,7 +59,7 @@ export const TrialCount = (): any => {
</Stack>
<Stack horizontal className='marginTop'>
<div style={leftProgress}>
<Stack horizontal className='status-count' gap={60}>
<Stack horizontal className='status-count' tokens={line1Tokens}>
<div>
<span>Running</span>
<p>{count.get('RUNNING')}</p>
Expand All @@ -66,7 +73,7 @@ export const TrialCount = (): any => {
<p>{stoppedCount}</p>
</div>
</Stack>
<Stack horizontal className='status-count marginTop' gap={80}>
<Stack horizontal className='status-count marginTop' tokens={line2Tokens}>
<div>
<span>Failed</span>
<p>{count.get('FAILED')}</p>
Expand Down
21 changes: 12 additions & 9 deletions ts/webui/src/components/trial-detail/TableList.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from 'react';
import {
DefaultButton,
Dropdown,
Expand All @@ -12,28 +13,29 @@ import {
TooltipHost,
DirectionalHint
} from '@fluentui/react';
import React from 'react';
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/openRow.scss';
import '../../static/style/pagination.scss';
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 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 '../../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/tensorboard.scss';
import '../../static/style/overview/overviewTitle.scss';

require('echarts/lib/chart/line');
require('echarts/lib/component/tooltip');
Expand Down Expand Up @@ -491,6 +493,7 @@ class TableList extends React.Component<TableListProps, TableListState> {
}}
disabled={selectedRowIds.length === 0}
/>
<TensorboardUI selectedRowIds={selectedRowIds} />
</StackItem>
<StackItem grow={50}>
<Stack horizontal horizontalAlign='end' className='allList'>
Expand Down
Loading

0 comments on commit aec4ce1

Please sign in to comment.