Commit 77e3a6c7 authored by Lijiao's avatar Lijiao Committed by fishyds
Browse files

[WebUI] Close timer in 'DONE | STOPPED | ERROR' status and add edit concurrency btn (#466)

* [WebUI]Close timer

* Add edit concurrency btn

* fix bug
parent d8e55165
...@@ -170,10 +170,10 @@ class Overview extends React.Component<{}, OverviewState> { ...@@ -170,10 +170,10 @@ class Overview extends React.Component<{}, OverviewState> {
} }
} }
}); });
} }
showTrials = () => { showTrials = () => {
this.isOffInterval();
axios(`${MANAGER_IP}/trial-jobs`, { axios(`${MANAGER_IP}/trial-jobs`, {
method: 'GET' method: 'GET'
}) })
...@@ -219,7 +219,12 @@ class Overview extends React.Component<{}, OverviewState> { ...@@ -219,7 +219,12 @@ class Overview extends React.Component<{}, OverviewState> {
const acc = getFinalResult(tableData[item].finalMetricData); const acc = getFinalResult(tableData[item].finalMetricData);
// if hyperparameters is undefine, show error message, else, show parameters value // if hyperparameters is undefine, show error message, else, show parameters value
if (tableData[item].hyperParameters) { if (tableData[item].hyperParameters) {
desJobDetail.parameters = JSON.parse(tableData[item].hyperParameters).parameters; const parameters = JSON.parse(tableData[item].hyperParameters).parameters;
if (typeof parameters === 'string') {
desJobDetail.parameters = JSON.parse(parameters);
} else {
desJobDetail.parameters = parameters;
}
} else { } else {
desJobDetail.parameters = { error: 'This trial\'s parameters are not available.' }; desJobDetail.parameters = { error: 'This trial\'s parameters are not available.' };
} }
...@@ -253,6 +258,7 @@ class Overview extends React.Component<{}, OverviewState> { ...@@ -253,6 +258,7 @@ class Overview extends React.Component<{}, OverviewState> {
trialNumber: profile trialNumber: profile
}); });
} }
this.checkStatus();
// draw accuracy // draw accuracy
this.drawPointGraph(); this.drawPointGraph();
} }
...@@ -371,6 +377,19 @@ class Overview extends React.Component<{}, OverviewState> { ...@@ -371,6 +377,19 @@ class Overview extends React.Component<{}, OverviewState> {
}); });
} }
isOffInterval = () => {
const { status } = this.state;
switch (status) {
case 'DONE':
case 'ERROR':
case 'STOPPED':
window.clearInterval(this.intervalID);
window.clearInterval(this.intervalProfile);
break;
default:
}
}
componentDidMount() { componentDidMount() {
this._isMounted = true; this._isMounted = true;
this.showSessionPro(); this.showSessionPro();
...@@ -430,6 +449,7 @@ class Overview extends React.Component<{}, OverviewState> { ...@@ -430,6 +449,7 @@ class Overview extends React.Component<{}, OverviewState> {
bestAccuracy={bestAccuracy} bestAccuracy={bestAccuracy}
status={status} status={status}
errors={errorStr} errors={errorStr}
updateFile={this.showSessionPro}
/> />
</Col> </Col>
{/* experiment parameters search space tuner assessor... */} {/* experiment parameters search space tuner assessor... */}
......
...@@ -18,6 +18,7 @@ interface TrialDetailState { ...@@ -18,6 +18,7 @@ interface TrialDetailState {
accNodata: string; accNodata: string;
tableListSource: Array<TableObj>; tableListSource: Array<TableObj>;
tableBaseSource: Array<TableObj>; tableBaseSource: Array<TableObj>;
experimentStatus: string;
} }
class TrialsDetail extends React.Component<{}, TrialDetailState> { class TrialsDetail extends React.Component<{}, TrialDetailState> {
...@@ -33,7 +34,8 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { ...@@ -33,7 +34,8 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
accSource: {}, accSource: {},
accNodata: '', accNodata: '',
tableListSource: [], tableListSource: [],
tableBaseSource: [] tableBaseSource: [],
experimentStatus: ''
}; };
} }
// trial accuracy graph // trial accuracy graph
...@@ -120,8 +122,8 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { ...@@ -120,8 +122,8 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
} }
drawTableList = () => { drawTableList = () => {
this.isOffIntervals();
axios.get(`${MANAGER_IP}/trial-jobs`) axios.get(`${MANAGER_IP}/trial-jobs`)
.then(res => { .then(res => {
if (res.status === 200) { if (res.status === 200) {
const trialJobs = res.data; const trialJobs = res.data;
...@@ -148,7 +150,12 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { ...@@ -148,7 +150,12 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
} }
} }
if (trialJobs[item].hyperParameters !== undefined) { if (trialJobs[item].hyperParameters !== undefined) {
desc.parameters = JSON.parse(trialJobs[item].hyperParameters).parameters; const getPara = JSON.parse(trialJobs[item].hyperParameters).parameters;
if (typeof getPara === 'string') {
desc.parameters = JSON.parse(getPara);
} else {
desc.parameters = getPara;
}
} else { } else {
desc.parameters = { error: 'This trial\'s parameters are not available.' }; desc.parameters = { error: 'This trial\'s parameters are not available.' };
} }
...@@ -187,11 +194,13 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { ...@@ -187,11 +194,13 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
break; break;
case '2': case '2':
this.isOffIntervals();
window.clearInterval(this.interAccuracy); window.clearInterval(this.interAccuracy);
window.clearInterval(Duration.intervalDuration); window.clearInterval(Duration.intervalDuration);
break; break;
case '3': case '3':
this.isOffIntervals();
window.clearInterval(this.interAccuracy); window.clearInterval(this.interAccuracy);
window.clearInterval(Para.intervalIDPara); window.clearInterval(Para.intervalIDPara);
break; break;
...@@ -226,6 +235,28 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { ...@@ -226,6 +235,28 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
this.drawTableList(); this.drawTableList();
this.interTableList = window.setInterval(this.drawTableList, 10000); this.interTableList = window.setInterval(this.drawTableList, 10000);
} }
isOffIntervals = () => {
axios(`${MANAGER_IP}/check-status`, {
method: 'GET'
})
.then(res => {
if (res.status === 200 && this._isMounted) {
switch (res.data.status) {
case 'DONE':
case 'ERROR':
case 'STOPPED':
window.clearInterval(this.interAccuracy);
window.clearInterval(this.interTableList);
window.clearInterval(Duration.intervalDuration);
window.clearInterval(Para.intervalIDPara);
break;
default:
}
}
});
}
componentDidMount() { componentDidMount() {
this._isMounted = true; this._isMounted = true;
......
...@@ -31,24 +31,20 @@ class BasicInfo extends React.Component<BasicInfoProps, {}> { ...@@ -31,24 +31,20 @@ class BasicInfo extends React.Component<BasicInfoProps, {}> {
<div>{trialProfile.id}</div> <div>{trialProfile.id}</div>
</Col> </Col>
<Col span={8} className="padItem basic"> <Col span={8} className="padItem basic">
<Row> <p>Start Time</p>
<Col span={18}> <div className="nowrap">
<p>Start Time</p> {new Date(trialProfile.startTime).toLocaleString('en-US')}
<div className="nowrap"> </div>
{new Date(trialProfile.startTime).toLocaleString('en-US')} <p>End Time</p>
</div> <div className="nowrap">
<p>End Time</p> {
<div className="nowrap"> trialProfile.endTime
{ ?
trialProfile.endTime new Date(trialProfile.endTime).toLocaleString('en-US')
? :
new Date(trialProfile.endTime).toLocaleString('en-US') 'none'
: }
'none' </div>
}
</div>
</Col>
</Row>
</Col> </Col>
<Col span={8} className="padItem basic"> <Col span={8} className="padItem basic">
<p>LogPath</p> <p>LogPath</p>
......
...@@ -2,8 +2,12 @@ import * as React from 'react'; ...@@ -2,8 +2,12 @@ import * as React from 'react';
import { import {
Row, Row,
Col, Col,
Popover Popover,
Button,
message
} from 'antd'; } from 'antd';
import axios from 'axios';
import { MANAGER_IP, CONTROLTYPE } from '../../static/const';
import { Experiment, TrialNumber } from '../../static/interface'; import { Experiment, TrialNumber } from '../../static/interface';
import { convertTime } from '../../static/function'; import { convertTime } from '../../static/function';
import ProgressBar from './ProgressItem'; import ProgressBar from './ProgressItem';
...@@ -16,19 +20,130 @@ interface ProgressProps { ...@@ -16,19 +20,130 @@ interface ProgressProps {
bestAccuracy: string; bestAccuracy: string;
status: string; status: string;
errors: string; errors: string;
updateFile: Function;
} }
class Progressed extends React.Component<ProgressProps, {}> { interface ProgressState {
btnName: string;
isEnable: boolean;
userInputVal?: string; // get user input
cancelSty: string;
}
class Progressed extends React.Component<ProgressProps, ProgressState> {
public conInput: HTMLInputElement | null;
constructor(props: ProgressProps) { constructor(props: ProgressProps) {
super(props); super(props);
this.state = {
btnName: 'Edit',
isEnable: true,
cancelSty: 'none'
};
}
editTrialConcurrency = () => {
const { btnName } = this.state;
if (btnName === 'Edit') {
this.setState(() => ({
isEnable: false,
btnName: 'Save',
cancelSty: 'inline-block'
}));
} else {
axios(`${MANAGER_IP}/experiment`, {
method: 'GET'
})
.then(rese => {
if (rese.status === 200) {
const { userInputVal } = this.state;
const experimentFile = rese.data;
const trialConcurrency = experimentFile.params.trialConcurrency;
if (userInputVal !== undefined) {
if (userInputVal === trialConcurrency.toString() || userInputVal === '') {
message.info(
`trialConcurrency's value is ${trialConcurrency}, you did not modify it`, 2);
} else {
experimentFile.params.trialConcurrency = parseInt(userInputVal, 10);
// rest api, modify trial concurrency value
axios(`${MANAGER_IP}/experiment`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
data: experimentFile,
params: {
update_type: CONTROLTYPE[1]
}
}).then(res => {
if (res.status === 200) {
message.success(`Update ${CONTROLTYPE[1].toLocaleLowerCase()} successfully`);
// rerender trial profile message
const { updateFile } = this.props;
updateFile();
}
})
.catch(error => {
if (error.response.status === 500) {
if (error.response.data.error) {
message.error(error.response.data.error);
} else {
message.error(`Update ${CONTROLTYPE[1].toLocaleLowerCase()} failed`);
}
}
});
// btn -> edit
this.setState(() => ({
btnName: 'Edit',
isEnable: true,
cancelSty: 'none'
}));
}
}
}
});
}
}
cancelFunction = () => {
const { trialProfile } = this.props;
this.setState(
() => ({
btnName: 'Edit',
isEnable: true,
cancelSty: 'none',
}));
if (this.conInput !== null) {
this.conInput.value = trialProfile.runConcurren.toString();
}
}
getUserTrialConcurrency = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
if (value.match(/^[1-9]\d*$/) || value === '') {
this.setState(() => ({
userInputVal: value
}));
} else {
message.error('Please enter a positive integer!', 2);
if (this.conInput !== null) {
const { trialProfile } = this.props;
this.conInput.value = trialProfile.runConcurren.toString();
}
}
}
componentWillReceiveProps() {
const { trialProfile } = this.props;
if (this.conInput !== null) {
this.conInput.value = trialProfile.runConcurren.toString();
}
} }
render() { render() {
const { trialProfile, const { trialProfile, trialNumber, bestAccuracy, status, errors } = this.props;
trialNumber, bestAccuracy, const { isEnable, btnName, cancelSty } = this.state;
status, errors
} = this.props;
const bar2 = trialNumber.totalCurrentTrial - trialNumber.waitTrial - trialNumber.unknowTrial; const bar2 = trialNumber.totalCurrentTrial - trialNumber.waitTrial - trialNumber.unknowTrial;
const bar2Percent = (bar2 / trialProfile.MaxTrialNum) * 100; const bar2Percent = (bar2 / trialProfile.MaxTrialNum) * 100;
const percent = (trialProfile.execDuration / trialProfile.maxDuration) * 100; const percent = (trialProfile.execDuration / trialProfile.maxDuration) * 100;
...@@ -70,6 +185,31 @@ class Progressed extends React.Component<ProgressProps, {}> { ...@@ -70,6 +185,31 @@ class Progressed extends React.Component<ProgressProps, {}> {
</div> </div>
</Row> </Row>
{/* modify concurrency */}
<Row className="inputBox">
<span className="title">Concurrency:</span>
<input
type="number"
disabled={isEnable}
onChange={this.getUserTrialConcurrency}
className="concurrencyInput"
ref={(input) => this.conInput = input}
/>
<Button
type="primary"
className="tableButton editStyle"
onClick={this.editTrialConcurrency}
>{btnName}
</Button>
<Button
type="primary"
onClick={this.cancelFunction}
style={{ display: cancelSty, marginLeft: 1 }}
className="tableButton editStyle"
>
Cancel
</Button>
</Row>
<ProgressBar <ProgressBar
who="Duration" who="Duration"
percent={percent} percent={percent}
......
...@@ -19,7 +19,7 @@ class SearchSpace extends React.Component<SearchspaceProps, {}> { ...@@ -19,7 +19,7 @@ class SearchSpace extends React.Component<SearchspaceProps, {}> {
<div className="searchSpace"> <div className="searchSpace">
<MonacoEditor <MonacoEditor
width="100%" width="100%"
height="380" height="398"
language="json" language="json"
theme="vs-light" theme="vs-light"
value={JSON.stringify(searchSpace, null, 2)} value={JSON.stringify(searchSpace, null, 2)}
......
...@@ -29,7 +29,7 @@ class TrialInfo extends React.Component<TrialInfoProps, {}> { ...@@ -29,7 +29,7 @@ class TrialInfo extends React.Component<TrialInfoProps, {}> {
<div className="profile"> <div className="profile">
<MonacoEditor <MonacoEditor
width="100%" width="100%"
height="386" height="396"
language="json" language="json"
theme="vs-light" theme="vs-light"
value={JSON.stringify(showProInfo[0], null, 2)} value={JSON.stringify(showProInfo[0], null, 2)}
......
import { FinalResult } from './interface'; import { FinalResult } from './interface';
export const convertTime = (num: number) => { const convertTime = (num: number) => {
if (num % 3600 === 0) { if (num % 3600 === 0) {
return num / 3600 + 'h'; return num / 3600 + 'h';
} else { } else {
...@@ -11,7 +11,7 @@ export const convertTime = (num: number) => { ...@@ -11,7 +11,7 @@ export const convertTime = (num: number) => {
}; };
// trial's duration, accurate to seconds for example 10min 30s // trial's duration, accurate to seconds for example 10min 30s
export const convertDuration = (num: number) => { const convertDuration = (num: number) => {
const hour = Math.floor(num / 3600); const hour = Math.floor(num / 3600);
const min = Math.floor(num / 60 % 60); const min = Math.floor(num / 60 % 60);
const second = Math.floor(num % 60); const second = Math.floor(num % 60);
...@@ -28,7 +28,7 @@ export const convertDuration = (num: number) => { ...@@ -28,7 +28,7 @@ export const convertDuration = (num: number) => {
}; };
// get final result value // get final result value
export const getFinalResult = (final: FinalResult) => { const getFinalResult = (final: FinalResult) => {
let acc; let acc;
let showDefault = 0; let showDefault = 0;
if (final) { if (final) {
...@@ -45,3 +45,7 @@ export const getFinalResult = (final: FinalResult) => { ...@@ -45,3 +45,7 @@ export const getFinalResult = (final: FinalResult) => {
return 0; return 0;
} }
}; };
export {
convertTime, convertDuration, getFinalResult
};
$btnBgcolor: #3c8dbc; $btnBgcolor: #0071bc;
Button.tableButton{ Button.tableButton{
background: $btnBgcolor; background: $btnBgcolor;
......
/* new style */ /* new style */
.overMessage{ .overMessage{
height: 424px; height: 456px;
} }
.overGraph{ .overGraph{
height: 362px; height: 362px;
......
...@@ -5,8 +5,6 @@ ...@@ -5,8 +5,6 @@
color: #0573bc; color: #0573bc;
font-size: 20px; font-size: 20px;
font-weight: bold; font-weight: bold;
border-bottom: 1px solid #ccc;
padding-bottom: 12px;
} }
.probar{ .probar{
...@@ -54,7 +52,7 @@ ...@@ -54,7 +52,7 @@
} }
div{ div{
font-size: 16px; font-size: 16px;
color: #8c8c8c; color: #333;
} }
} }
...@@ -72,4 +70,25 @@ ...@@ -72,4 +70,25 @@
min-width: 300px; min-width: 300px;
} }
*/ */
.inputBox{
height: 30px;
margin-top: 8px;
border-bottom: 1px solid #ccc;
padding-bottom: 44px;
.title{
font-size: 14px;
margin-right: 10px;
}
.concurrencyInput{
width: 25%;
height: 32px;
outline: none;
border: 1px solid #ccc;
}
.editStyle{
height: 32px;
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment