From e12df2b1649f99fa5be097b0a33a42957add4a78 Mon Sep 17 00:00:00 2001 From: SparkSnail Date: Fri, 21 Jun 2019 10:09:48 +0700 Subject: [PATCH 1/2] Validate file name in codeDir for PAI platform (#1168) --- src/nni_manager/common/utils.ts | 36 ++++++++++++++++++- .../training_service/common/util.ts | 15 ++++++-- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/nni_manager/common/utils.ts b/src/nni_manager/common/utils.ts index cb5770026f..3c11c0c835 100644 --- a/src/nni_manager/common/utils.ts +++ b/src/nni_manager/common/utils.ts @@ -374,6 +374,40 @@ function countFilesRecursively(directory: string, timeoutMilliSeconds?: number): }); } +function validateFileName(fileName: string): boolean { + let pattern: string = '^[a-z0-9A-Z\.-_]+$'; + const validateResult = fileName.match(pattern); + if(validateResult) { + return true; + } + return false; +} + +async function validateFileNameRecursively(directory: string): Promise { + if(!fs.existsSync(directory)) { + throw Error(`Direcotory ${directory} doesn't exist`); + } + + const fileNameArray: string[] = fs.readdirSync(directory); + let result = true; + for(var name of fileNameArray){ + const fullFilePath: string = path.join(directory, name); + try { + // validate file names and directory names + result = validateFileName(name); + if (fs.lstatSync(fullFilePath).isDirectory()) { + result = result && await validateFileNameRecursively(fullFilePath); + } + if(!result) { + return Promise.reject(new Error(`file name in ${fullFilePath} is not valid!`)); + } + } catch(error) { + return Promise.reject(error); + } + } + return Promise.resolve(result); +} + /** * get the version of current package */ @@ -474,6 +508,6 @@ function unixPathJoin(...paths: any[]): string { return dir; } -export {countFilesRecursively, getRemoteTmpDir, generateParamFileName, getMsgDispatcherCommand, getCheckpointDir, +export {countFilesRecursively, validateFileNameRecursively, getRemoteTmpDir, generateParamFileName, getMsgDispatcherCommand, getCheckpointDir, getLogDir, getExperimentRootDir, getJobCancelStatus, getDefaultDatabaseDir, getIPV4Address, unixPathJoin, mkDirP, delay, prepareUnitTest, parseArg, cleanupUnitTest, uniqueString, randomSelect, getLogLevel, getVersion, getCmdPy, getTunerProc, isAlive, killPid, getNewLine }; diff --git a/src/nni_manager/training_service/common/util.ts b/src/nni_manager/training_service/common/util.ts index 71c255e848..aab276bd30 100644 --- a/src/nni_manager/training_service/common/util.ts +++ b/src/nni_manager/training_service/common/util.ts @@ -25,7 +25,7 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import { String } from 'typescript-string-operations'; -import { countFilesRecursively, getNewLine } from '../../common/utils'; +import { countFilesRecursively, getNewLine, validateFileNameRecursively } from '../../common/utils'; import { file } from '../../node_modules/@types/tmp'; import { GPU_INFO_COLLECTOR_FORMAT_LINUX, GPU_INFO_COLLECTOR_FORMAT_WINDOWS } from './gpuData'; @@ -38,22 +38,33 @@ import { GPU_INFO_COLLECTOR_FORMAT_LINUX, GPU_INFO_COLLECTOR_FORMAT_WINDOWS } fr // tslint:disable: no-redundant-jsdoc export async function validateCodeDir(codeDir: string) : Promise { let fileCount: number | undefined; - + let fileNameValid: boolean = true; try { fileCount = await countFilesRecursively(codeDir); } catch (error) { throw new Error(`Call count file error: ${error}`); } + try { + fileNameValid = await validateFileNameRecursively(codeDir); + } catch(error) { + throw new Error(`Validate file name error: ${error}`); + } if (fileCount !== undefined && fileCount > 1000) { const errMessage: string = `Too many files(${fileCount} found}) in ${codeDir},` + ` please check if it's a valid code dir`; throw new Error(errMessage); } + + if(!fileNameValid) { + const errMessage: string = `File name in ${codeDir} is not valid, please check file names, only support digit number、alphabet and (.-_) in file name.`; + throw new Error(errMessage); + } return fileCount; } + /** * crete a new directory * @param directory From 61fec44651fba16913245f0ca240494f10e9f144 Mon Sep 17 00:00:00 2001 From: Lijiao <35484733+lvybriage@users.noreply.github.com> Date: Fri, 21 Jun 2019 11:13:36 +0800 Subject: [PATCH 2/2] Enhancement: ability to adjust rendering interval (#1181) * Add interval * update * change back react version * change to alpha version --- src/webui/package.json | 8 +- src/webui/src/App.css | 6 +- src/webui/src/App.tsx | 47 +- src/webui/src/components/Overview.tsx | 77 ++- src/webui/src/components/SlideBar.tsx | 571 +++++++++++++--------- src/webui/src/components/TrialsDetail.tsx | 54 +- src/webui/src/static/style/slideBar.scss | 86 +++- 7 files changed, 567 insertions(+), 282 deletions(-) diff --git a/src/webui/package.json b/src/webui/package.json index bc6ad20f0b..c75a5dd9b4 100644 --- a/src/webui/package.json +++ b/src/webui/package.json @@ -9,11 +9,12 @@ "copy-to-clipboard": "^3.0.8", "echarts": "^4.1.0", "echarts-for-react": "^2.0.14", - "react": "^16.4.2", - "react-dom": "^16.4.2", + "react": "^16.7.0-alpha.2", + "react-dom": "^16.7.0-alpha.2", "react-json-tree": "^0.11.0", "react-monaco-editor": "^0.22.0", "react-router": "3.2.1", + "react-responsive": "^7.0.0", "react-scripts-ts-antd": "2.17.0" }, "scripts": { @@ -28,7 +29,8 @@ "@types/react": "^16.4.17", "@types/react-dom": "^16.0.7", "@types/react-json-tree": "^0.6.8", + "@types/react-responsive": "^3.0.3", "@types/react-router": "3.0.15", "typescript": "^3.0.1" } -} +} \ No newline at end of file diff --git a/src/webui/src/App.css b/src/webui/src/App.css index cc6c20b850..acd5703f2f 100644 --- a/src/webui/src/App.css +++ b/src/webui/src/App.css @@ -15,9 +15,7 @@ border-right: 1px solid #ccc; z-index: 1000; } -.headerCon{ - min-width: 1024px; -} + .contentBox{ width: 100%; } @@ -29,5 +27,3 @@ margin-bottom: 30px; background: #fff; } - - diff --git a/src/webui/src/App.tsx b/src/webui/src/App.tsx index a013a9d258..9839542853 100644 --- a/src/webui/src/App.tsx +++ b/src/webui/src/App.tsx @@ -3,20 +3,59 @@ import { Row, Col } from 'antd'; import './App.css'; import SlideBar from './components/SlideBar'; -class App extends React.Component<{}, {}> { +interface AppState { + interval: number; + whichPageToFresh: string; +} + +class App extends React.Component<{}, AppState> { + public _isMounted: boolean; + constructor(props: {}) { + super(props); + this.state = { + interval: 10, // sendons + whichPageToFresh: '' + }; + } + + changeInterval = (interval: number) => { + if (this._isMounted === true) { + this.setState(() => ({ interval: interval })); + } + } + + changeFresh = (fresh: string) => { + // interval * 1000 + if (this._isMounted === true) { + this.setState(() => ({ whichPageToFresh: fresh })); + } + } + + componentDidMount() { + this._isMounted = true; + } + + componentWillUnmount() { + this._isMounted = false; + } render() { + const { interval, whichPageToFresh } = this.state; + const reactPropsChildren = React.Children.map(this.props.children, child => + // tslint:disable-next-line:no-any + React.cloneElement(child as React.ReactElement, { interval, whichPageToFresh }) + ); return ( - + - + - {this.props.children} + {reactPropsChildren} diff --git a/src/webui/src/components/Overview.tsx b/src/webui/src/components/Overview.tsx index d610db5d36..80e2f4d135 100644 --- a/src/webui/src/components/Overview.tsx +++ b/src/webui/src/components/Overview.tsx @@ -39,13 +39,18 @@ interface OverviewState { isMultiPhase: boolean; } -class Overview extends React.Component<{}, OverviewState> { +interface OverviewProps { + interval: number; // user select + whichPageToFresh: string; +} + +class Overview extends React.Component { public _isMounted = false; public intervalID = 0; public intervalProfile = 1; - constructor(props: {}) { + constructor(props: OverviewProps) { super(props); this.state = { searchSpace: {}, @@ -377,17 +382,19 @@ class Overview extends React.Component<{}, OverviewState> { data: accarr }] }; - this.setState({ accuracyData: accOption }, () => { - if (accarr.length === 0) { - this.setState({ - accNodata: 'No data' - }); - } else { - this.setState({ - accNodata: '' - }); - } - }); + if (this._isMounted) { + this.setState({ accuracyData: accOption }, () => { + if (accarr.length === 0) { + this.setState({ + accNodata: 'No data' + }); + } else { + this.setState({ + accNodata: '' + }); + } + }); + } } clickMaxTop = (event: React.SyntheticEvent) => { @@ -405,23 +412,39 @@ 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: + const { interval } = this.props; + if (status === 'DONE' || status === 'ERROR' || status === 'STOPPED' || + interval === 0 + ) { + window.clearInterval(this.intervalID); + window.clearInterval(this.intervalProfile); + return; + } + } + + componentWillReceiveProps(nextProps: OverviewProps) { + const { interval, whichPageToFresh } = nextProps; + window.clearInterval(this.intervalID); + window.clearInterval(this.intervalProfile); + if (whichPageToFresh.includes('/oview')) { + this.showTrials(); + this.showSessionPro(); + } + if (interval !== 0) { + this.intervalID = window.setInterval(this.showTrials, interval * 1000); + this.intervalProfile = window.setInterval(this.showSessionPro, interval * 1000); } } componentDidMount() { this._isMounted = true; - this.showSessionPro(); + const { interval } = this.props; this.showTrials(); - this.intervalID = window.setInterval(this.showTrials, 10000); - this.intervalProfile = window.setInterval(this.showSessionPro, 60000); + this.showSessionPro(); + if (interval !== 0) { + this.intervalID = window.setInterval(this.showTrials, interval * 1000); + this.intervalProfile = window.setInterval(this.showSessionPro, interval * 1000); + } } componentWillUnmount() { @@ -447,7 +470,7 @@ class Overview extends React.Component<{}, OverviewState> { {/* status graph */} - + { /> {/* experiment parameters search space tuner assessor... */} - + - + {/* the scroll bar all the trial profile in the searchSpace div*/} diff --git a/src/webui/src/components/SlideBar.tsx b/src/webui/src/components/SlideBar.tsx index 4edf76ec08..9d9ce5613f 100644 --- a/src/webui/src/components/SlideBar.tsx +++ b/src/webui/src/components/SlideBar.tsx @@ -1,242 +1,373 @@ import * as React from 'react'; import { Link } from 'react-router'; import axios from 'axios'; -import { DOWNLOAD_IP } from '../static/const'; -import { Row, Col, Menu, Dropdown, Icon } from 'antd'; import { MANAGER_IP } from '../static/const'; +import MediaQuery from 'react-responsive'; +import { DOWNLOAD_IP } from '../static/const'; +import { Row, Col, Menu, Dropdown, Icon, Select } from 'antd'; +const { SubMenu } = Menu; +const { Option } = Select; import '../static/style/slideBar.scss'; import '../static/style/button.scss'; interface SliderState { - version: string; - menuVisible: boolean; + version: string; + menuVisible: boolean; + navBarVisible: boolean; +} + +interface SliderProps { + changeInterval: (value: number) => void; + changeFresh: (value: string) => void; } interface EventPer { - key: string; + key: string; } -class SlideBar extends React.Component<{}, SliderState> { - - public _isMounted = false; - - constructor(props: {}) { - super(props); - this.state = { - version: '', - menuVisible: false - }; - } - - downExperimentContent = () => { - axios - .all([ - axios.get(`${MANAGER_IP}/experiment`), - axios.get(`${MANAGER_IP}/trial-jobs`), - axios.get(`${MANAGER_IP}/metric-data`) - ]) - .then(axios.spread((res, res1, res2) => { - if (res.status === 200 && res1.status === 200 && res2.status === 200) { - if (res.data.params.searchSpace) { - res.data.params.searchSpace = JSON.parse(res.data.params.searchSpace); - } - const isEdge = navigator.userAgent.indexOf('Edge') !== -1 ? true : false; - let trialMessagesArr = res1.data; - const interResultList = res2.data; - Object.keys(trialMessagesArr).map(item => { - // transform hyperparameters as object to show elegantly - trialMessagesArr[item].hyperParameters = JSON.parse(trialMessagesArr[item].hyperParameters); - const trialId = trialMessagesArr[item].id; - // add intermediate result message - trialMessagesArr[item].intermediate = []; - Object.keys(interResultList).map(key => { - const interId = interResultList[key].trialJobId; - if (trialId === interId) { - trialMessagesArr[item].intermediate.push(interResultList[key]); - } +class SlideBar extends React.Component { + + public _isMounted = false; + public divMenu: HTMLDivElement | null; + public countOfMenu: number = 0; + public selectHTML: Select | null; + + constructor(props: SliderProps) { + super(props); + this.state = { + version: '', + menuVisible: false, + navBarVisible: false, + }; + } + + downExperimentContent = () => { + axios + .all([ + axios.get(`${MANAGER_IP}/experiment`), + axios.get(`${MANAGER_IP}/trial-jobs`), + axios.get(`${MANAGER_IP}/metric-data`) + ]) + .then(axios.spread((res, res1, res2) => { + if (res.status === 200 && res1.status === 200 && res2.status === 200) { + if (res.data.params.searchSpace) { + res.data.params.searchSpace = JSON.parse(res.data.params.searchSpace); + } + const isEdge = navigator.userAgent.indexOf('Edge') !== -1 ? true : false; + let trialMessagesArr = res1.data; + const interResultList = res2.data; + Object.keys(trialMessagesArr).map(item => { + // transform hyperparameters as object to show elegantly + trialMessagesArr[item].hyperParameters = JSON.parse(trialMessagesArr[item].hyperParameters); + const trialId = trialMessagesArr[item].id; + // add intermediate result message + trialMessagesArr[item].intermediate = []; + Object.keys(interResultList).map(key => { + const interId = interResultList[key].trialJobId; + if (trialId === interId) { + trialMessagesArr[item].intermediate.push(interResultList[key]); + } + }); + }); + const result = { + experimentParameters: res.data, + trialMessage: trialMessagesArr + }; + const aTag = document.createElement('a'); + const file = new Blob([JSON.stringify(result, null, 4)], { type: 'application/json' }); + aTag.download = 'experiment.json'; + aTag.href = URL.createObjectURL(file); + aTag.click(); + if (!isEdge) { + URL.revokeObjectURL(aTag.href); + } + if (navigator.userAgent.indexOf('Firefox') > -1) { + const downTag = document.createElement('a'); + downTag.addEventListener('click', function () { + downTag.download = 'experiment.json'; + downTag.href = URL.createObjectURL(file); + }); + let eventMouse = document.createEvent('MouseEvents'); + eventMouse.initEvent('click', false, false); + downTag.dispatchEvent(eventMouse); + } + } + })); + } + + downnnimanagerLog = () => { + axios(`${DOWNLOAD_IP}/nnimanager.log`, { + method: 'GET' + }) + .then(res => { + if (res.status === 200) { + const nniLogfile = res.data; + const aTag = document.createElement('a'); + const isEdge = navigator.userAgent.indexOf('Edge') !== -1 ? true : false; + const file = new Blob([nniLogfile], { type: 'application/json' }); + aTag.download = 'nnimanagerLog.log'; + aTag.href = URL.createObjectURL(file); + aTag.click(); + if (!isEdge) { + URL.revokeObjectURL(aTag.href); + } + if (navigator.userAgent.indexOf('Firefox') > -1) { + const downTag = document.createElement('a'); + downTag.addEventListener('click', function () { + downTag.download = 'nnimanagerLog.log'; + downTag.href = URL.createObjectURL(file); + }); + let eventMouse = document.createEvent('MouseEvents'); + eventMouse.initEvent('click', false, false); + downTag.dispatchEvent(eventMouse); + } + } }); - }); - const result = { - experimentParameters: res.data, - trialMessage: trialMessagesArr - }; - const aTag = document.createElement('a'); - const file = new Blob([JSON.stringify(result, null, 4)], { type: 'application/json' }); - aTag.download = 'experiment.json'; - aTag.href = URL.createObjectURL(file); - aTag.click(); - if (!isEdge) { - URL.revokeObjectURL(aTag.href); - } - if (navigator.userAgent.indexOf('Firefox') > -1) { - const downTag = document.createElement('a'); - downTag.addEventListener('click', function () { - downTag.download = 'experiment.json'; - downTag.href = URL.createObjectURL(file); + } + + downDispatcherlog = () => { + axios(`${DOWNLOAD_IP}/dispatcher.log`, { + method: 'GET' + }) + .then(res => { + if (res.status === 200) { + const dispatchLogfile = res.data; + const aTag = document.createElement('a'); + const isEdge = navigator.userAgent.indexOf('Edge') !== -1 ? true : false; + const file = new Blob([dispatchLogfile], { type: 'application/json' }); + aTag.download = 'dispatcherLog.log'; + aTag.href = URL.createObjectURL(file); + aTag.click(); + if (!isEdge) { + URL.revokeObjectURL(aTag.href); + } + if (navigator.userAgent.indexOf('Firefox') > -1) { + const downTag = document.createElement('a'); + downTag.addEventListener('click', function () { + downTag.download = 'dispatcherLog.log'; + downTag.href = URL.createObjectURL(file); + }); + let eventMouse = document.createEvent('MouseEvents'); + eventMouse.initEvent('click', false, false); + downTag.dispatchEvent(eventMouse); + } + } }); - let eventMouse = document.createEvent('MouseEvents'); - eventMouse.initEvent('click', false, false); - downTag.dispatchEvent(eventMouse); - } - } - })); - } - - downnnimanagerLog = () => { - axios(`${DOWNLOAD_IP}/nnimanager.log`, { - method: 'GET' - }) - .then(res => { - if (res.status === 200) { - const nniLogfile = res.data; - const aTag = document.createElement('a'); - const isEdge = navigator.userAgent.indexOf('Edge') !== -1 ? true : false; - const file = new Blob([nniLogfile], { type: 'application/json' }); - aTag.download = 'nnimanager.log'; - aTag.href = URL.createObjectURL(file); - aTag.click(); - if (!isEdge) { - URL.revokeObjectURL(aTag.href); - } - if (navigator.userAgent.indexOf('Firefox') > -1) { - const downTag = document.createElement('a'); - downTag.addEventListener('click', function () { - downTag.download = 'nnimanager.log'; - downTag.href = URL.createObjectURL(file); + } + + getNNIversion = () => { + axios(`${MANAGER_IP}/version`, { + method: 'GET' + }) + .then(res => { + if (res.status === 200 && this._isMounted) { + this.setState({ version: res.data }); + } }); - let eventMouse = document.createEvent('MouseEvents'); - eventMouse.initEvent('click', false, false); - downTag.dispatchEvent(eventMouse); - } + } + + handleMenuClick = (e: EventPer) => { + if (this._isMounted) { this.setState({ menuVisible: false }); } + switch (e.key) { + // download experiment related content + case '1': + this.downExperimentContent(); + break; + // download nnimanager log file + case '2': + this.downnnimanagerLog(); + break; + // download dispatcher log file + case '3': + this.downDispatcherlog(); + break; + case 'close': + case '10': + case '20': + case '30': + case '60': + this.getInterval(e.key); + break; + default: } - }); - } - - downDispatcherlog = () => { - axios(`${DOWNLOAD_IP}/dispatcher.log`, { - method: 'GET' - }) - .then(res => { - if (res.status === 200) { - const dispatchLogfile = res.data; - const aTag = document.createElement('a'); - const isEdge = navigator.userAgent.indexOf('Edge') !== -1 ? true : false; - const file = new Blob([dispatchLogfile], { type: 'application/json' }); - aTag.download = 'dispatcher.log'; - aTag.href = URL.createObjectURL(file); - aTag.click(); - if (!isEdge) { - URL.revokeObjectURL(aTag.href); - } - if (navigator.userAgent.indexOf('Firefox') > -1) { - const downTag = document.createElement('a'); - downTag.addEventListener('click', function () { - downTag.download = 'dispatcher.log'; - downTag.href = URL.createObjectURL(file); - }); - let eventMouse = document.createEvent('MouseEvents'); - eventMouse.initEvent('click', false, false); - downTag.dispatchEvent(eventMouse); - } + } + + handleVisibleChange = (flag: boolean) => { + if (this._isMounted === true) { + this.setState({ menuVisible: flag }); } - }); - } - - getNNIversion = () => { - axios(`${MANAGER_IP}/version`, { - method: 'GET' - }) - .then(res => { - if (res.status === 200 && this._isMounted) { - this.setState({ version: res.data }); + } + + getInterval = (value: string) => { + + if (value === 'close') { + this.props.changeInterval(0); + } else { + this.props.changeInterval(parseInt(value, 10)); + } + } + + menu = () => { + this.countOfMenu = 0; + return ( + + Experiment Parameters + NNImanager Logfile + Dispatcher Logfile + + ); + } + + // nav bar + navigationBar = () => { + const { version } = this.state; + const feedBackLink = `https://github.com/Microsoft/nni/issues/new?labels=${version}`; + return ( + + Overview + Trials detail + + Fresh + + + Feedback + + Version: {version} + + Download + + } + > + Experiment Parameters + NNImanager Logfile + Dispatcher Logfile + + + ); + } + + // nav bar <1299 + showMenu = () => { + if (this.divMenu !== null) { + this.countOfMenu = this.countOfMenu + 1; + if (this.countOfMenu % 2 === 0) { + this.divMenu.setAttribute('class', 'hide'); + } else { + this.divMenu.setAttribute('class', 'show'); + } } - }); - } - - handleMenuClick = (e: EventPer) => { - if (this._isMounted) { this.setState({ menuVisible: false }); } - // download experiment related content - switch (e.key) { - case '1': - this.downExperimentContent(); - break; - case '2': - this.downnnimanagerLog(); - break; - case '3': - this.downDispatcherlog(); - break; - default: - } - } - - handleVisibleChange = (flag: boolean) => { - this.setState({ menuVisible: flag }); - } - - componentDidMount() { - this._isMounted = true; - this.getNNIversion(); - } - - componentWillUnmount() { - this._isMounted = false; - } - - render() { - const { version, menuVisible } = this.state; - const feed = `https://github.com/Microsoft/nni/issues/new?labels=${version}`; - const menu = ( - - Experiment Parameters - NNImanager Logfile - Dispatcher Logfile - - ); - return ( - - -
    -
  • - - NNI logo - -
  • -
  • - - Overview - -
  • -
  • - - Trials detail - -
  • -
- - - - - Download - - - - NNI github issue - Feedback - - Version: {version} - -
- ); - } + } + + select = () => { + return ( + + ); + } + + fresh = (event: React.SyntheticEvent) => { + event.preventDefault(); + const whichPage = window.location.pathname; + this.props.changeFresh(whichPage); + } + + componentDidMount() { + this._isMounted = true; + this.getNNIversion(); + } + + componentWillUnmount() { + this._isMounted = false; + } + + render() { + const { version, menuVisible } = this.state; + const feed = `https://github.com/Microsoft/nni/issues/new?labels=${version}`; + return ( + + + + + + + + + + +
this.divMenu = div} className="hide">{this.navigationBar()}
+ + + + NNI logo + + +
+
+ {this.select()} +
+ ); + } } export default SlideBar; \ No newline at end of file diff --git a/src/webui/src/components/TrialsDetail.tsx b/src/webui/src/components/TrialsDetail.tsx index b48dd18237..76b650962b 100644 --- a/src/webui/src/components/TrialsDetail.tsx +++ b/src/webui/src/components/TrialsDetail.tsx @@ -33,7 +33,12 @@ interface TrialDetailState { intermediateCounts: number; } -class TrialsDetail extends React.Component<{}, TrialDetailState> { +interface TrialsDetailProps { + interval: number; + whichPageToFresh: string; +} + +class TrialsDetail extends React.Component { public _isMounted = false; public interAccuracy = 0; @@ -61,7 +66,7 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { ); - constructor(props: {}) { + constructor(props: TrialsDetailProps) { super(props); this.state = { @@ -227,21 +232,24 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { // close timer 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': + const { interval } = this.props; + if (interval === 0) { + window.clearInterval(this.interTableList); + return; + } else { + axios(`${MANAGER_IP}/check-status`, { + method: 'GET' + }) + .then(res => { + if (res.status === 200 && this._isMounted) { + const expStatus = res.data.status; + if (expStatus === 'DONE' || expStatus === 'ERROR' || expStatus === 'STOPPED') { window.clearInterval(this.interTableList); - break; - default: + return; + } } - } - }); + }); + } } handleEntriesSelect = (value: string) => { @@ -304,11 +312,23 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { }); } + componentWillReceiveProps(nextProps: TrialsDetailProps) { + const { interval, whichPageToFresh } = nextProps; + window.clearInterval(this.interTableList); + if (interval !== 0) { + this.interTableList = window.setInterval(this.getDetailSource, interval * 1000); + } + if (whichPageToFresh.includes('/detail')) { + this.getDetailSource(); + } + } + componentDidMount() { this._isMounted = true; + const { interval } = this.props; this.getDetailSource(); - this.interTableList = window.setInterval(this.getDetailSource, 10000); + this.interTableList = window.setInterval(this.getDetailSource, interval * 1000); this.checkExperimentPlatform(); } @@ -329,7 +349,6 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
- {/* */} { - {/* */} diff --git a/src/webui/src/static/style/slideBar.scss b/src/webui/src/static/style/slideBar.scss index cad32da226..fd5bcddec2 100644 --- a/src/webui/src/static/style/slideBar.scss +++ b/src/webui/src/static/style/slideBar.scss @@ -1,9 +1,14 @@ $barHeight: 56px; +/* drowdown and select default bgcolor */ +$drowBgColor: #f2f2f2; +/* drowdown and select hover bgcolor */ +$drowHoverBgColor: #e2e2e2; .nav{ list-style: none; - width: 94%; + width: 95%; height: $barHeight; margin: 0 auto; + position: relative; .tab{ font-family: 'Segoe'; line-height: $barHeight; @@ -14,7 +19,7 @@ $barHeight: 56px; } } .firstTab{ - margin: 0 32px; + margin: 0 20px; } .logo{ margin-top: 2px; @@ -28,17 +33,26 @@ $barHeight: 56px; } .feedback{ - text-align: right; + position: fixed; + right: 19%; line-height: $barHeight; font-size: 16px; color: #fff; + .fresh{ + span{ + margin: 0 10px 0 3px; + } + } + .fresh:hover{ + cursor: pointer; + } a{ color: #fff; text-decoration: none; margin-left: 10px; } img{ - width: 24px; + width: 20px; margin-right: 8px; } .version{ @@ -51,4 +65,66 @@ $barHeight: 56px; } .dropdown{ margin-right: 10px; -} \ No newline at end of file + /* make dropdown content box position in blue bar bottom */ + padding-bottom: 14px; +} +.interval{ + position: fixed; + right: 7%; + top: 12px; + .ant-select-selection{ + background-color: transparent; + border: none; + color: #fff; + outline: none; + font-size: 16px; + } + .ant-select-arrow{ + color: #fff; + } +} +/* set select bgcolor */ +.ant-select-dropdown-menu{ + background-color: $drowBgColor; +} +.ant-select-dropdown-menu-item:hover{ + background-color: $drowHoverBgColor; +} +.ant-select-dropdown-menu-item-active{ + background-color: transparent; +} +/* set dropdown bgcolor */ +.ant-dropdown{ + .ant-dropdown-menu{ + padding: 0; + background-color: $drowBgColor; + border-radius: 0; + .ant-dropdown-menu-item:hover{ + background-color: $drowHoverBgColor; + } + } +} + +/* nav style*/ +.little{ + width: 100%; + .menu{ + .show{ + display: block; + } + .hide{ + display: none; + } + .more{ + color: #fff; + font-size: 24px; + margin-top: 16px; + } + .more:hover{ + cursor: pointer; + } + } + .logo{ + text-align: center; + } +}