Unverified Commit f6343344 authored by SparkSnail's avatar SparkSnail Committed by GitHub
Browse files

Merge pull request #185 from microsoft/master

merge master
parents d48ad027 61fec446
...@@ -374,6 +374,40 @@ function countFilesRecursively(directory: string, timeoutMilliSeconds?: number): ...@@ -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<boolean> {
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 * get the version of current package
*/ */
...@@ -474,6 +508,6 @@ function unixPathJoin(...paths: any[]): string { ...@@ -474,6 +508,6 @@ function unixPathJoin(...paths: any[]): string {
return dir; return dir;
} }
export {countFilesRecursively, getRemoteTmpDir, generateParamFileName, getMsgDispatcherCommand, getCheckpointDir, export {countFilesRecursively, validateFileNameRecursively, getRemoteTmpDir, generateParamFileName, getMsgDispatcherCommand, getCheckpointDir,
getLogDir, getExperimentRootDir, getJobCancelStatus, getDefaultDatabaseDir, getIPV4Address, unixPathJoin, getLogDir, getExperimentRootDir, getJobCancelStatus, getDefaultDatabaseDir, getIPV4Address, unixPathJoin,
mkDirP, delay, prepareUnitTest, parseArg, cleanupUnitTest, uniqueString, randomSelect, getLogLevel, getVersion, getCmdPy, getTunerProc, isAlive, killPid, getNewLine }; mkDirP, delay, prepareUnitTest, parseArg, cleanupUnitTest, uniqueString, randomSelect, getLogLevel, getVersion, getCmdPy, getTunerProc, isAlive, killPid, getNewLine };
...@@ -25,7 +25,7 @@ import * as fs from 'fs'; ...@@ -25,7 +25,7 @@ import * as fs from 'fs';
import * as os from 'os'; import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import { String } from 'typescript-string-operations'; 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 { file } from '../../node_modules/@types/tmp';
import { GPU_INFO_COLLECTOR_FORMAT_LINUX, GPU_INFO_COLLECTOR_FORMAT_WINDOWS } from './gpuData'; 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 ...@@ -38,22 +38,33 @@ import { GPU_INFO_COLLECTOR_FORMAT_LINUX, GPU_INFO_COLLECTOR_FORMAT_WINDOWS } fr
// tslint:disable: no-redundant-jsdoc // tslint:disable: no-redundant-jsdoc
export async function validateCodeDir(codeDir: string) : Promise<number> { export async function validateCodeDir(codeDir: string) : Promise<number> {
let fileCount: number | undefined; let fileCount: number | undefined;
let fileNameValid: boolean = true;
try { try {
fileCount = await countFilesRecursively(codeDir); fileCount = await countFilesRecursively(codeDir);
} catch (error) { } catch (error) {
throw new Error(`Call count file error: ${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) { if (fileCount !== undefined && fileCount > 1000) {
const errMessage: string = `Too many files(${fileCount} found}) in ${codeDir},` const errMessage: string = `Too many files(${fileCount} found}) in ${codeDir},`
+ ` please check if it's a valid code dir`; + ` please check if it's a valid code dir`;
throw new Error(errMessage); 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; return fileCount;
} }
/** /**
* crete a new directory * crete a new directory
* @param directory * @param directory
......
...@@ -9,11 +9,12 @@ ...@@ -9,11 +9,12 @@
"copy-to-clipboard": "^3.0.8", "copy-to-clipboard": "^3.0.8",
"echarts": "^4.1.0", "echarts": "^4.1.0",
"echarts-for-react": "^2.0.14", "echarts-for-react": "^2.0.14",
"react": "^16.4.2", "react": "^16.7.0-alpha.2",
"react-dom": "^16.4.2", "react-dom": "^16.7.0-alpha.2",
"react-json-tree": "^0.11.0", "react-json-tree": "^0.11.0",
"react-monaco-editor": "^0.22.0", "react-monaco-editor": "^0.22.0",
"react-router": "3.2.1", "react-router": "3.2.1",
"react-responsive": "^7.0.0",
"react-scripts-ts-antd": "2.17.0" "react-scripts-ts-antd": "2.17.0"
}, },
"scripts": { "scripts": {
...@@ -28,7 +29,8 @@ ...@@ -28,7 +29,8 @@
"@types/react": "^16.4.17", "@types/react": "^16.4.17",
"@types/react-dom": "^16.0.7", "@types/react-dom": "^16.0.7",
"@types/react-json-tree": "^0.6.8", "@types/react-json-tree": "^0.6.8",
"@types/react-responsive": "^3.0.3",
"@types/react-router": "3.0.15", "@types/react-router": "3.0.15",
"typescript": "^3.0.1" "typescript": "^3.0.1"
} }
} }
\ No newline at end of file
...@@ -15,9 +15,7 @@ ...@@ -15,9 +15,7 @@
border-right: 1px solid #ccc; border-right: 1px solid #ccc;
z-index: 1000; z-index: 1000;
} }
.headerCon{
min-width: 1024px;
}
.contentBox{ .contentBox{
width: 100%; width: 100%;
} }
...@@ -29,5 +27,3 @@ ...@@ -29,5 +27,3 @@
margin-bottom: 30px; margin-bottom: 30px;
background: #fff; background: #fff;
} }
...@@ -3,20 +3,59 @@ import { Row, Col } from 'antd'; ...@@ -3,20 +3,59 @@ import { Row, Col } from 'antd';
import './App.css'; import './App.css';
import SlideBar from './components/SlideBar'; 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() { 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<any>, { interval, whichPageToFresh })
);
return ( return (
<Row className="nni" style={{minHeight: window.innerHeight}}> <Row className="nni" style={{ minHeight: window.innerHeight }}>
<Row className="header"> <Row className="header">
<Col span={1} /> <Col span={1} />
<Col className="headerCon" span={22}> <Col className="headerCon" span={22}>
<SlideBar /> <SlideBar changeInterval={this.changeInterval} changeFresh={this.changeFresh}/>
</Col> </Col>
<Col span={1} /> <Col span={1} />
</Row> </Row>
<Row className="contentBox"> <Row className="contentBox">
<Row className="content"> <Row className="content">
{this.props.children} {reactPropsChildren}
</Row> </Row>
</Row> </Row>
</Row> </Row>
......
...@@ -39,13 +39,18 @@ interface OverviewState { ...@@ -39,13 +39,18 @@ interface OverviewState {
isMultiPhase: boolean; isMultiPhase: boolean;
} }
class Overview extends React.Component<{}, OverviewState> { interface OverviewProps {
interval: number; // user select
whichPageToFresh: string;
}
class Overview extends React.Component<OverviewProps, OverviewState> {
public _isMounted = false; public _isMounted = false;
public intervalID = 0; public intervalID = 0;
public intervalProfile = 1; public intervalProfile = 1;
constructor(props: {}) { constructor(props: OverviewProps) {
super(props); super(props);
this.state = { this.state = {
searchSpace: {}, searchSpace: {},
...@@ -377,17 +382,19 @@ class Overview extends React.Component<{}, OverviewState> { ...@@ -377,17 +382,19 @@ class Overview extends React.Component<{}, OverviewState> {
data: accarr data: accarr
}] }]
}; };
this.setState({ accuracyData: accOption }, () => { if (this._isMounted) {
if (accarr.length === 0) { this.setState({ accuracyData: accOption }, () => {
this.setState({ if (accarr.length === 0) {
accNodata: 'No data' this.setState({
}); accNodata: 'No data'
} else { });
this.setState({ } else {
accNodata: '' this.setState({
}); accNodata: ''
} });
}); }
});
}
} }
clickMaxTop = (event: React.SyntheticEvent<EventTarget>) => { clickMaxTop = (event: React.SyntheticEvent<EventTarget>) => {
...@@ -405,23 +412,39 @@ class Overview extends React.Component<{}, OverviewState> { ...@@ -405,23 +412,39 @@ class Overview extends React.Component<{}, OverviewState> {
isOffInterval = () => { isOffInterval = () => {
const { status } = this.state; const { status } = this.state;
switch (status) { const { interval } = this.props;
case 'DONE': if (status === 'DONE' || status === 'ERROR' || status === 'STOPPED' ||
case 'ERROR': interval === 0
case 'STOPPED': ) {
window.clearInterval(this.intervalID); window.clearInterval(this.intervalID);
window.clearInterval(this.intervalProfile); window.clearInterval(this.intervalProfile);
break; return;
default: }
}
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() { componentDidMount() {
this._isMounted = true; this._isMounted = true;
this.showSessionPro(); const { interval } = this.props;
this.showTrials(); this.showTrials();
this.intervalID = window.setInterval(this.showTrials, 10000); this.showSessionPro();
this.intervalProfile = window.setInterval(this.showSessionPro, 60000); if (interval !== 0) {
this.intervalID = window.setInterval(this.showTrials, interval * 1000);
this.intervalProfile = window.setInterval(this.showSessionPro, interval * 1000);
}
} }
componentWillUnmount() { componentWillUnmount() {
...@@ -447,7 +470,7 @@ class Overview extends React.Component<{}, OverviewState> { ...@@ -447,7 +470,7 @@ class Overview extends React.Component<{}, OverviewState> {
</Row> </Row>
<Row className="overMessage"> <Row className="overMessage">
{/* status graph */} {/* status graph */}
<Col span={9} className="prograph overviewBoder"> <Col span={9} className="prograph overviewBoder cc">
<Title1 text="Status" icon="5.png" /> <Title1 text="Status" icon="5.png" />
<Progressed <Progressed
trialNumber={trialNumber} trialNumber={trialNumber}
...@@ -459,13 +482,13 @@ class Overview extends React.Component<{}, OverviewState> { ...@@ -459,13 +482,13 @@ class Overview extends React.Component<{}, OverviewState> {
/> />
</Col> </Col>
{/* experiment parameters search space tuner assessor... */} {/* experiment parameters search space tuner assessor... */}
<Col span={7} className="overviewBoder"> <Col span={7} className="overviewBoder cc">
<Title1 text="Search space" icon="10.png" /> <Title1 text="Search space" icon="10.png" />
<Row className="experiment"> <Row className="experiment">
<SearchSpace searchSpace={searchSpace} /> <SearchSpace searchSpace={searchSpace} />
</Row> </Row>
</Col> </Col>
<Col span={8} className="overviewBoder"> <Col span={8} className="overviewBoder cc">
<Title1 text="Profile" icon="4.png" /> <Title1 text="Profile" icon="4.png" />
<Row className="experiment"> <Row className="experiment">
{/* the scroll bar all the trial profile in the searchSpace div*/} {/* the scroll bar all the trial profile in the searchSpace div*/}
......
import * as React from 'react'; import * as React from 'react';
import { Link } from 'react-router'; import { Link } from 'react-router';
import axios from 'axios'; 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 { 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/slideBar.scss';
import '../static/style/button.scss'; import '../static/style/button.scss';
interface SliderState { interface SliderState {
version: string; version: string;
menuVisible: boolean; menuVisible: boolean;
navBarVisible: boolean;
}
interface SliderProps {
changeInterval: (value: number) => void;
changeFresh: (value: string) => void;
} }
interface EventPer { interface EventPer {
key: string; key: string;
} }
class SlideBar extends React.Component<{}, SliderState> { class SlideBar extends React.Component<SliderProps, SliderState> {
public _isMounted = false; public _isMounted = false;
public divMenu: HTMLDivElement | null;
constructor(props: {}) { public countOfMenu: number = 0;
super(props); public selectHTML: Select | null;
this.state = {
version: '', constructor(props: SliderProps) {
menuVisible: false super(props);
}; this.state = {
} version: '',
menuVisible: false,
downExperimentContent = () => { navBarVisible: false,
axios };
.all([ }
axios.get(`${MANAGER_IP}/experiment`),
axios.get(`${MANAGER_IP}/trial-jobs`), downExperimentContent = () => {
axios.get(`${MANAGER_IP}/metric-data`) axios
]) .all([
.then(axios.spread((res, res1, res2) => { axios.get(`${MANAGER_IP}/experiment`),
if (res.status === 200 && res1.status === 200 && res2.status === 200) { axios.get(`${MANAGER_IP}/trial-jobs`),
if (res.data.params.searchSpace) { axios.get(`${MANAGER_IP}/metric-data`)
res.data.params.searchSpace = JSON.parse(res.data.params.searchSpace); ])
} .then(axios.spread((res, res1, res2) => {
const isEdge = navigator.userAgent.indexOf('Edge') !== -1 ? true : false; if (res.status === 200 && res1.status === 200 && res2.status === 200) {
let trialMessagesArr = res1.data; if (res.data.params.searchSpace) {
const interResultList = res2.data; res.data.params.searchSpace = JSON.parse(res.data.params.searchSpace);
Object.keys(trialMessagesArr).map(item => { }
// transform hyperparameters as object to show elegantly const isEdge = navigator.userAgent.indexOf('Edge') !== -1 ? true : false;
trialMessagesArr[item].hyperParameters = JSON.parse(trialMessagesArr[item].hyperParameters); let trialMessagesArr = res1.data;
const trialId = trialMessagesArr[item].id; const interResultList = res2.data;
// add intermediate result message Object.keys(trialMessagesArr).map(item => {
trialMessagesArr[item].intermediate = []; // transform hyperparameters as object to show elegantly
Object.keys(interResultList).map(key => { trialMessagesArr[item].hyperParameters = JSON.parse(trialMessagesArr[item].hyperParameters);
const interId = interResultList[key].trialJobId; const trialId = trialMessagesArr[item].id;
if (trialId === interId) { // add intermediate result message
trialMessagesArr[item].intermediate.push(interResultList[key]); 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, downDispatcherlog = () => {
trialMessage: trialMessagesArr axios(`${DOWNLOAD_IP}/dispatcher.log`, {
}; method: 'GET'
const aTag = document.createElement('a'); })
const file = new Blob([JSON.stringify(result, null, 4)], { type: 'application/json' }); .then(res => {
aTag.download = 'experiment.json'; if (res.status === 200) {
aTag.href = URL.createObjectURL(file); const dispatchLogfile = res.data;
aTag.click(); const aTag = document.createElement('a');
if (!isEdge) { const isEdge = navigator.userAgent.indexOf('Edge') !== -1 ? true : false;
URL.revokeObjectURL(aTag.href); const file = new Blob([dispatchLogfile], { type: 'application/json' });
} aTag.download = 'dispatcherLog.log';
if (navigator.userAgent.indexOf('Firefox') > -1) { aTag.href = URL.createObjectURL(file);
const downTag = document.createElement('a'); aTag.click();
downTag.addEventListener('click', function () { if (!isEdge) {
downTag.download = 'experiment.json'; URL.revokeObjectURL(aTag.href);
downTag.href = URL.createObjectURL(file); }
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); getNNIversion = () => {
} axios(`${MANAGER_IP}/version`, {
} method: 'GET'
})); })
} .then(res => {
if (res.status === 200 && this._isMounted) {
downnnimanagerLog = () => { this.setState({ version: res.data });
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);
}); });
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:
} }
}); }
}
handleVisibleChange = (flag: boolean) => {
downDispatcherlog = () => { if (this._isMounted === true) {
axios(`${DOWNLOAD_IP}/dispatcher.log`, { this.setState({ menuVisible: flag });
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);
}
} }
}); }
}
getInterval = (value: string) => {
getNNIversion = () => {
axios(`${MANAGER_IP}/version`, { if (value === 'close') {
method: 'GET' this.props.changeInterval(0);
}) } else {
.then(res => { this.props.changeInterval(parseInt(value, 10));
if (res.status === 200 && this._isMounted) { }
this.setState({ version: res.data }); }
menu = () => {
this.countOfMenu = 0;
return (
<Menu onClick={this.handleMenuClick}>
<Menu.Item key="1">Experiment Parameters</Menu.Item>
<Menu.Item key="2">NNImanager Logfile</Menu.Item>
<Menu.Item key="3">Dispatcher Logfile</Menu.Item>
</Menu>
);
}
// nav bar
navigationBar = () => {
const { version } = this.state;
const feedBackLink = `https://github.com/Microsoft/nni/issues/new?labels=${version}`;
return (
<Menu onClick={this.handleMenuClick} mode="inline">
<Menu.Item key="overview"><Link to={'/oview'}>Overview</Link></Menu.Item>
<Menu.Item key="detail"><Link to={'/detail'}>Trials detail</Link></Menu.Item>
<Menu.Item key="fresh">
<span className="fresh" onClick={this.fresh}><span>Fresh</span></span>
</Menu.Item>
<Menu.Item key="feedback">
<a href={feedBackLink} target="_blank">Feedback</a>
</Menu.Item>
<Menu.Item key="version">Version: {version}</Menu.Item>
<SubMenu
key="download"
onChange={this.handleVisibleChange}
title={
<span>
<span>Download</span>
</span>
}
>
<Menu.Item key="1">Experiment Parameters</Menu.Item>
<Menu.Item key="2">NNImanager Logfile</Menu.Item>
<Menu.Item key="3">Dispatcher Logfile</Menu.Item>
</SubMenu>
</Menu>
);
}
// 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');
}
} }
}); }
}
select = () => {
handleMenuClick = (e: EventPer) => { return (
if (this._isMounted) { this.setState({ menuVisible: false }); } <Select
// download experiment related content onSelect={this.getInterval}
switch (e.key) { defaultValue="Refresh every 10s"
case '1': className="interval"
this.downExperimentContent(); >
break; <Option value="close">Disable Auto Refresh</Option>
case '2': <Option value="10">Refresh every 10s</Option>
this.downnnimanagerLog(); <Option value="20">Refresh every 20s</Option>
break; <Option value="30">Refresh every 30s</Option>
case '3': <Option value="60">Refresh every 1min</Option>
this.downDispatcherlog(); </Select>
break; );
default: }
}
} fresh = (event: React.SyntheticEvent<EventTarget>) => {
event.preventDefault();
handleVisibleChange = (flag: boolean) => { const whichPage = window.location.pathname;
this.setState({ menuVisible: flag }); this.props.changeFresh(whichPage);
} }
componentDidMount() { componentDidMount() {
this._isMounted = true; this._isMounted = true;
this.getNNIversion(); this.getNNIversion();
} }
componentWillUnmount() { componentWillUnmount() {
this._isMounted = false; this._isMounted = false;
} }
render() { render() {
const { version, menuVisible } = this.state; const { version, menuVisible } = this.state;
const feed = `https://github.com/Microsoft/nni/issues/new?labels=${version}`; const feed = `https://github.com/Microsoft/nni/issues/new?labels=${version}`;
const menu = ( return (
<Menu onClick={this.handleMenuClick}> <Row>
<Menu.Item key="1">Experiment Parameters</Menu.Item> <MediaQuery query="(min-width: 1299px)">
<Menu.Item key="2">NNImanager Logfile</Menu.Item> <Row className="nav">
<Menu.Item key="3">Dispatcher Logfile</Menu.Item> <ul className="link">
</Menu> <li className="logo">
); <Link to={'/oview'}>
return ( <img
<Row className="nav"> src={require('../static/img/logo2.png')}
<Col span={8}> style={{ width: 88 }}
<ul className="link"> alt="NNI logo"
<li className="logo"> />
<Link to={'/oview'}> </Link>
<img src={require('../static/img/logo2.png')} style={{ width: 88 }} alt="NNI logo" /> </li>
</Link> <li className="tab firstTab">
</li> <Link to={'/oview'} activeClassName="high">
<li className="tab firstTab"> Overview
<Link to={'/oview'} activeClassName="high"> </Link>
Overview </li>
</Link> <li className="tab">
</li> <Link to={'/detail'} activeClassName="high">
<li className="tab"> Trials detail
<Link to={'/detail'} activeClassName="high"> </Link>
Trials detail </li>
</Link> <li className="feedback">
</li> <span className="fresh" onClick={this.fresh}>
</ul> <Icon type="sync"/><span>Fresh</span>
</Col> </span>
<Col span={16} className="feedback"> <Dropdown
<Dropdown className="dropdown"
className="dropdown" overlay={this.menu()}
overlay={menu} onVisibleChange={this.handleVisibleChange}
onVisibleChange={this.handleVisibleChange} visible={menuVisible}
visible={menuVisible} trigger={['click']}
> >
<a className="ant-dropdown-link" href="#"> <a className="ant-dropdown-link" href="#">
Download <Icon type="down" /> Download <Icon type="down" />
</a> </a>
</Dropdown> </Dropdown>
<a href={feed} target="_blank"> <a href={feed} target="_blank">
<img <img
src={require('../static/img/icon/issue.png')} src={require('../static/img/icon/issue.png')}
alt="NNI github issue" alt="NNI github issue"
/> />
Feedback Feedback
</a> </a>
<span className="version">Version: {version}</span> <span className="version">Version: {version}</span>
</Col> </li>
</Row> </ul>
); </Row>
} </MediaQuery>
<MediaQuery query="(max-width: 1299px)">
<Row className="little">
<Col span={6} className="menu">
<Icon type="unordered-list" className="more" onClick={this.showMenu} />
<div ref={div => this.divMenu = div} className="hide">{this.navigationBar()}</div>
</Col>
<Col span={10} className="logo">
<Link to={'/oview'}>
<img
src={require('../static/img/logo2.png')}
style={{ width: 88 }}
alt="NNI logo"
/>
</Link>
</Col>
</Row>
</MediaQuery>
{this.select()}
</Row>
);
}
} }
export default SlideBar; export default SlideBar;
\ No newline at end of file
...@@ -33,7 +33,12 @@ interface TrialDetailState { ...@@ -33,7 +33,12 @@ interface TrialDetailState {
intermediateCounts: number; intermediateCounts: number;
} }
class TrialsDetail extends React.Component<{}, TrialDetailState> { interface TrialsDetailProps {
interval: number;
whichPageToFresh: string;
}
class TrialsDetail extends React.Component<TrialsDetailProps, TrialDetailState> {
public _isMounted = false; public _isMounted = false;
public interAccuracy = 0; public interAccuracy = 0;
...@@ -61,7 +66,7 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { ...@@ -61,7 +66,7 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
</div> </div>
); );
constructor(props: {}) { constructor(props: TrialsDetailProps) {
super(props); super(props);
this.state = { this.state = {
...@@ -227,21 +232,24 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { ...@@ -227,21 +232,24 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
// close timer // close timer
isOffIntervals = () => { isOffIntervals = () => {
axios(`${MANAGER_IP}/check-status`, { const { interval } = this.props;
method: 'GET' if (interval === 0) {
}) window.clearInterval(this.interTableList);
.then(res => { return;
if (res.status === 200 && this._isMounted) { } else {
switch (res.data.status) { axios(`${MANAGER_IP}/check-status`, {
case 'DONE': method: 'GET'
case 'ERROR': })
case 'STOPPED': .then(res => {
if (res.status === 200 && this._isMounted) {
const expStatus = res.data.status;
if (expStatus === 'DONE' || expStatus === 'ERROR' || expStatus === 'STOPPED') {
window.clearInterval(this.interTableList); window.clearInterval(this.interTableList);
break; return;
default: }
} }
} });
}); }
} }
handleEntriesSelect = (value: string) => { handleEntriesSelect = (value: string) => {
...@@ -304,11 +312,23 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { ...@@ -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() { componentDidMount() {
this._isMounted = true; this._isMounted = true;
const { interval } = this.props;
this.getDetailSource(); this.getDetailSource();
this.interTableList = window.setInterval(this.getDetailSource, 10000); this.interTableList = window.setInterval(this.getDetailSource, interval * 1000);
this.checkExperimentPlatform(); this.checkExperimentPlatform();
} }
...@@ -329,7 +349,6 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { ...@@ -329,7 +349,6 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
<div> <div>
<div className="trial" id="tabsty"> <div className="trial" id="tabsty">
<Tabs type="card" onChange={this.handleWhichTabs}> <Tabs type="card" onChange={this.handleWhichTabs}>
{/* <TabPane tab={this.titleOfacc} key="1" destroyInactiveTabPane={true}> */}
<TabPane tab={this.titleOfacc} key="1"> <TabPane tab={this.titleOfacc} key="1">
<Row className="graph"> <Row className="graph">
<DefaultPoint <DefaultPoint
...@@ -350,7 +369,6 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> { ...@@ -350,7 +369,6 @@ class TrialsDetail extends React.Component<{}, TrialDetailState> {
</TabPane> </TabPane>
<TabPane tab={this.titleOfDuration} key="3"> <TabPane tab={this.titleOfDuration} key="3">
<Duration source={source} whichGraph={whichGraph} /> <Duration source={source} whichGraph={whichGraph} />
{/* <Duration source={source} whichGraph={whichGraph} clickCounts={durationCounts} /> */}
</TabPane> </TabPane>
<TabPane tab={this.titleOfIntermediate} key="4"> <TabPane tab={this.titleOfIntermediate} key="4">
<Intermediate source={source} whichGraph={whichGraph} /> <Intermediate source={source} whichGraph={whichGraph} />
......
$barHeight: 56px; $barHeight: 56px;
/* drowdown and select default bgcolor */
$drowBgColor: #f2f2f2;
/* drowdown and select hover bgcolor */
$drowHoverBgColor: #e2e2e2;
.nav{ .nav{
list-style: none; list-style: none;
width: 94%; width: 95%;
height: $barHeight; height: $barHeight;
margin: 0 auto; margin: 0 auto;
position: relative;
.tab{ .tab{
font-family: 'Segoe'; font-family: 'Segoe';
line-height: $barHeight; line-height: $barHeight;
...@@ -14,7 +19,7 @@ $barHeight: 56px; ...@@ -14,7 +19,7 @@ $barHeight: 56px;
} }
} }
.firstTab{ .firstTab{
margin: 0 32px; margin: 0 20px;
} }
.logo{ .logo{
margin-top: 2px; margin-top: 2px;
...@@ -28,17 +33,26 @@ $barHeight: 56px; ...@@ -28,17 +33,26 @@ $barHeight: 56px;
} }
.feedback{ .feedback{
text-align: right; position: fixed;
right: 19%;
line-height: $barHeight; line-height: $barHeight;
font-size: 16px; font-size: 16px;
color: #fff; color: #fff;
.fresh{
span{
margin: 0 10px 0 3px;
}
}
.fresh:hover{
cursor: pointer;
}
a{ a{
color: #fff; color: #fff;
text-decoration: none; text-decoration: none;
margin-left: 10px; margin-left: 10px;
} }
img{ img{
width: 24px; width: 20px;
margin-right: 8px; margin-right: 8px;
} }
.version{ .version{
...@@ -51,4 +65,66 @@ $barHeight: 56px; ...@@ -51,4 +65,66 @@ $barHeight: 56px;
} }
.dropdown{ .dropdown{
margin-right: 10px; margin-right: 10px;
} /* make dropdown content box position in blue bar bottom */
\ No newline at end of file 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;
}
}
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