Commit 8e1e1c03 authored by Lijiao's avatar Lijiao Committed by fishyds
Browse files

[WebUI] Show version and support download logfile (#485)

* [WebUI]Show version and support download logfile

* Update download style
parent 9ff1f9d4
......@@ -9,19 +9,19 @@
left: 0;
top: 0;
width: 100%;
height: 80px;
height: 56px;
background: #0071BC;
border-right: 1px solid #ccc;
z-index: 1000;
}
.headerCon{
min-width: 600px
min-width: 1024px;
}
.content{
width: 86%;
min-width: 1024px;
margin: 0 auto;
margin-top: 90px;
margin-top: 74px;
margin-bottom: 30px;
}
......
......@@ -8,10 +8,11 @@ class App extends React.Component<{}, {}> {
return (
<Row className="nni">
<Row className="header">
<Col span={2} />
<Col span={1} />
<Col className="headerCon" span={22}>
<SlideBar />
</Col>
<Col span={1}/>
</Row>
<Row className="content">
{this.props.children}
......
import * as React from 'react';
import axios from 'axios';
import { Row, Col, Button } from 'antd';
import { Row, Col } from 'antd';
import { MANAGER_IP } from '../static/const';
import {
Experiment, TableObj,
......@@ -30,10 +30,12 @@ interface OverviewState {
option: object;
noData: string;
accuracyData: object;
bestAccuracy: string;
bestAccuracy: number;
accNodata: string;
trialNumber: TrialNumber;
downBool: boolean;
isTop10: boolean;
titleMaxbgcolor: string;
titleMinbgcolor?: string;
}
class Overview extends React.Component<{}, OverviewState> {
......@@ -76,7 +78,7 @@ class Overview extends React.Component<{}, OverviewState> {
// accuracy
accuracyData: {},
accNodata: '',
bestAccuracy: '',
bestAccuracy: 0,
trialNumber: {
succTrial: 0,
failTrial: 0,
......@@ -86,7 +88,8 @@ class Overview extends React.Component<{}, OverviewState> {
unknowTrial: 0,
totalCurrentTrial: 0
},
downBool: false
isTop10: true,
titleMaxbgcolor: '#999'
};
}
......@@ -244,18 +247,37 @@ class Overview extends React.Component<{}, OverviewState> {
default:
}
});
topTableData.sort((a: TableObj, b: TableObj) => {
if (a.acc && b.acc) {
return b.acc - a.acc;
} else {
return NaN;
}
});
// choose top10 or lowest10
const { isTop10 } = this.state;
if (isTop10 === true) {
topTableData.sort((a: TableObj, b: TableObj) => {
if (a.acc && b.acc) {
return b.acc - a.acc;
} else {
return NaN;
}
});
} else {
topTableData.sort((a: TableObj, b: TableObj) => {
if (a.acc && b.acc) {
return a.acc - b.acc;
} else {
return NaN;
}
});
}
topTableData.length = Math.min(10, topTableData.length);
let bestDefaultMetric = 0;
if (topTableData[0] !== undefined) {
if (topTableData[0].acc !== undefined) {
bestDefaultMetric = topTableData[0].acc;
}
}
if (this._isMounted) {
this.setState({
tableData: topTableData,
trialNumber: profile
trialNumber: profile,
bestAccuracy: bestDefaultMetric
});
}
this.checkStatus();
......@@ -265,64 +287,6 @@ class Overview extends React.Component<{}, OverviewState> {
});
}
downExperimentContent = () => {
this.setState(() => ({
downBool: true
}));
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;
const interResultList = res2.data;
const contentOfExperiment = JSON.stringify(res.data, null, 2);
let trialMessagesArr = res1.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 trialMessages = JSON.stringify(trialMessagesArr, null, 2);
const aTag = document.createElement('a');
const file = new Blob([contentOfExperiment, trialMessages], { 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);
}
this.setState(() => ({
downBool: false
}));
}
}));
}
// trial accuracy graph Default Metric
drawPointGraph = () => {
......@@ -342,7 +306,6 @@ class Overview extends React.Component<{}, OverviewState> {
accarr.push(items.acc);
indexarr.push(items.sequenceId);
});
const bestAccnum = Math.max(...accarr);
const accOption = {
tooltip: {
trigger: 'item'
......@@ -355,6 +318,7 @@ class Overview extends React.Component<{}, OverviewState> {
yAxis: {
name: 'Default Metric',
type: 'value',
scale: true,
data: accarr
},
series: [{
......@@ -370,13 +334,25 @@ class Overview extends React.Component<{}, OverviewState> {
});
} else {
this.setState({
accNodata: '',
bestAccuracy: bestAccnum.toFixed(6)
accNodata: ''
});
}
});
}
clickMaxTop = (event: React.SyntheticEvent<EventTarget>) => {
event.stopPropagation();
// #999 panel active bgcolor; #b3b3b3 as usual
this.setState(() => ({ isTop10: true, titleMaxbgcolor: '#999', titleMinbgcolor: '#b3b3b3' }));
this.showTrials();
}
clickMinTop = (event: React.SyntheticEvent<EventTarget>) => {
event.stopPropagation();
this.setState(() => ({ isTop10: false, titleMaxbgcolor: '#b3b3b3', titleMinbgcolor: '#999' }));
this.showTrials();
}
isOffInterval = () => {
const { status } = this.state;
switch (status) {
......@@ -407,36 +383,16 @@ class Overview extends React.Component<{}, OverviewState> {
render() {
const {
trialProfile,
searchSpace,
tableData,
accuracyData,
accNodata,
status,
errorStr,
trialNumber,
bestAccuracy,
downBool
trialProfile, searchSpace, tableData, accuracyData,
accNodata, status, errorStr, trialNumber, bestAccuracy,
titleMaxbgcolor, titleMinbgcolor
} = this.state;
return (
<div className="overview">
{/* status and experiment block */}
<Row>
<Row className="exbgcolor">
<Col span={4}><Title1 text="Experiment" icon="11.png" /></Col>
<Col span={4}>
<Button
type="primary"
className="changeBtu download"
onClick={this.downExperimentContent}
disabled={downBool}
>
<span>Download</span>
<img src={require('../static/img/icon/download.png')} alt="icon" />
</Button>
</Col>
</Row>
<Title1 text="Experiment" icon="11.png" />
<BasicInfo trialProfile={trialProfile} status={status} />
</Row>
<Row className="overMessage">
......@@ -472,8 +428,26 @@ class Overview extends React.Component<{}, OverviewState> {
</Col>
</Row>
<Row className="overGraph">
<Row className="top10bg">
<Col span={4} className="top10Title">
<Title1 text="Top10 Trials" icon="7.png" />
</Col>
<Col
span={2}
className="title"
onClick={this.clickMaxTop}
>
<Title1 text="Maximal" icon="max.png" bgcolor={titleMaxbgcolor} />
</Col>
<Col
span={2}
className="title minTitle"
onClick={this.clickMinTop}
>
<Title1 text="Minimal" icon="min.png" bgcolor={titleMinbgcolor} />
</Col>
</Row>
<Col span={8} className="overviewBoder">
<Title1 text="Optimization Progress" icon="3.png" />
<Row className="accuracy">
<Accuracy
accuracyData={accuracyData}
......@@ -483,7 +457,6 @@ class Overview extends React.Component<{}, OverviewState> {
</Row>
</Col>
<Col span={16} id="succeTable">
<Title1 text="Top10 Trials" icon="7.png" />
<SuccessTable tableSource={tableData} />
</Col>
</Row>
......
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 '../static/style/slideBar.scss';
import '../static/style/button.scss';
class SlideBar extends React.Component<{}, {}> {
interface SliderState {
version: string;
menuVisible: boolean;
}
interface EventPer {
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;
const interResultList = res2.data;
const contentOfExperiment = JSON.stringify(res.data, null, 2);
let trialMessagesArr = res1.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 trialMessages = JSON.stringify(trialMessagesArr, null, 2);
const aTag = document.createElement('a');
const file = new Blob([contentOfExperiment, trialMessages], { 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.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 = 'nnimanagerLog.json';
downTag.href = URL.createObjectURL(file);
});
let eventMouse = document.createEvent('MouseEvents');
eventMouse.initEvent('click', false, false);
downTag.dispatchEvent(eventMouse);
}
}
});
}
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.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 = 'dispatcherLog.json';
downTag.href = URL.createObjectURL(file);
});
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) {
this.setState({ version: res.data });
}
});
}
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 menu = (
<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>
);
return (
<ul className="nav">
<li className="logo">
<Link to={'/oview'}>
<img src={require('../static/img/logo.png')} style={{ width: 156 }} alt="NNI logo" />
</Link>
</li>
<li className="tab">
<Link to={'/oview'} activeClassName="high">
Overview
<Row className="nav">
<Col span={8}>
<ul className="link">
<li className="logo">
<Link to={'/oview'}>
<img src={require('../static/img/logo2.png')} style={{ width: 88 }} alt="NNI logo" />
</Link>
</li>
<li className="tab firstTab">
<Link to={'/oview'} activeClassName="high">
Overview
</Link>
</li>
<li className="tab">
<Link to={'/detail'} activeClassName="high">
Trials Detail
</li>
<li className="tab">
<Link to={'/detail'} activeClassName="high">
Trials Detail
</Link>
</li>
</ul>
</li>
</ul>
</Col>
<Col span={16} className="feedback">
<Dropdown
className="dropdown"
overlay={menu}
onVisibleChange={this.handleVisibleChange}
visible={menuVisible}
>
<a className="ant-dropdown-link" href="#">
Download <Icon type="down" />
</a>
</Dropdown>
<a href="https://github.com/Microsoft/nni/issues/new" target="_blank">
<img
src={require('../static/img/icon/issue.png')}
alt="NNI github issue"
/>
FeedBack
</a>
<span className="version">Version: {version}</span>
</Col>
</Row>
);
}
}
......
......@@ -14,14 +14,10 @@ class BasicInfo extends React.Component<BasicInfoProps, {}> {
constructor(props: BasicInfoProps) {
super(props);
}
render() {
const { trialProfile,
// status
} = this.props;
const { trialProfile } = this.props;
return (
<Row className="main">
<Col span={8} className="padItem basic">
......
......@@ -17,7 +17,7 @@ import '../../static/style/probar.scss';
interface ProgressProps {
trialProfile: Experiment;
trialNumber: TrialNumber;
bestAccuracy: string;
bestAccuracy: number;
status: string;
errors: string;
updateFile: Function;
......@@ -26,64 +26,67 @@ interface ProgressProps {
interface ProgressState {
btnName: string;
isEnable: boolean;
userInputVal?: string; // get user input
userInputVal: string; // get user input
cancelSty: string;
}
class Progressed extends React.Component<ProgressProps, ProgressState> {
public conInput: HTMLInputElement | null;
public _isMounted = false;
constructor(props: ProgressProps) {
super(props);
this.state = {
btnName: 'Edit',
isEnable: true,
userInputVal: this.props.trialProfile.runConcurren.toString(),
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();
}
})
if (this._isMounted) {
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 === '0') {
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) {
......@@ -93,27 +96,30 @@ class Progressed extends React.Component<ProgressProps, ProgressState> {
}
}
});
// btn -> edit
this.setState(() => ({
btnName: 'Edit',
isEnable: true,
cancelSty: 'none'
}));
// 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._isMounted) {
this.setState(
() => ({
btnName: 'Edit',
isEnable: true,
cancelSty: 'none',
}));
}
if (this.conInput !== null) {
this.conInput.value = trialProfile.runConcurren.toString();
}
......@@ -141,6 +147,14 @@ class Progressed extends React.Component<ProgressProps, ProgressState> {
}
}
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
const { trialProfile, trialNumber, bestAccuracy, status, errors } = this.props;
const { isEnable, btnName, cancelSty } = this.state;
......@@ -164,51 +178,54 @@ class Progressed extends React.Component<ProgressProps, ProgressState> {
}
return (
<Row className="progress" id="barBack">
<Row className="basic">
<p>Status</p>
<div className="status">
<span className={status}>{status}</span>
{
status === 'ERROR'
?
<Popover
placement="rightTop"
content={errorContent}
title="Error"
trigger="hover"
>
<span className="errorBtn">i</span>
</Popover>
:
<span />
}
</div>
</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 className="basic lineBasic">
<Col span={12}>
<p>Status</p>
<div className="status">
<span className={status}>{status}</span>
{
status === 'ERROR'
?
<Popover
placement="rightTop"
content={errorContent}
title="Error"
trigger="hover"
>
<span className="errorBtn">i</span>
</Popover>
:
<span />
}
</div>
</Col>
<Col span={12}>
{/* modify concurrency */}
<p>Concurrency</p>
<Row className="inputBox">
<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>
</Col>
</Row>
<ProgressBar
who="Duration"
......
......@@ -19,7 +19,7 @@ class SearchSpace extends React.Component<SearchspaceProps, {}> {
<div className="searchSpace">
<MonacoEditor
width="100%"
height="398"
height="380"
language="json"
theme="vs-light"
value={JSON.stringify(searchSpace, null, 2)}
......
......@@ -3,6 +3,7 @@ import * as React from 'react';
interface Title1Props {
text: string;
icon: string;
bgcolor?: string;
}
class Title1 extends React.Component<Title1Props, {}> {
......@@ -12,10 +13,10 @@ class Title1 extends React.Component<Title1Props, {}> {
}
render() {
const { text, icon } = this.props;
const { text, icon, bgcolor } = this.props;
return (
<div>
<div className="panelTitle">
<div className="panelTitle" style={{backgroundColor: bgcolor}}>
<img src={require(`../../static/img/icon/${icon}`)} alt="icon" />
<span>{text}</span>
</div>
......
......@@ -29,7 +29,7 @@ class TrialInfo extends React.Component<TrialInfoProps, {}> {
<div className="profile">
<MonacoEditor
width="100%"
height="396"
height="380"
language="json"
theme="vs-light"
value={JSON.stringify(showProInfo[0], null, 2)}
......
export const MANAGER_IP = `/api/v1/nni`;
export const DOWNLOAD_IP = `/logs`;
export const trialJobStatus = [
'UNKNOWN',
'WAITING',
......
/* new style */
.overMessage{
height: 456px;
height: 430px;
}
.overGraph{
height: 362px;
......@@ -27,7 +27,6 @@
}
.searchSpace{
height: 386px;
line-height: 22px;
font-size: 14px;
padding: 15px 0;
......@@ -50,28 +49,3 @@
}
}
.exbgcolor {
background: #b3b3b3;
.download{
height: 30px;
border: 1px solid #fff;
border-radius: 0;
margin-top: 4px;
background-color: #0071BC;
span{
font-family: 'Segoe';
font-size: 14px;
}
img{
height: 14px;
margin-left: 10px;
margin-top: -6px;
}
}
}
.exbgcolor .download:hover{
border: 1px solid #fff;
}
\ No newline at end of file
$titleBgcolor: #b3b3b3;
.overview .overviewBoder{
height: 100%;
border-right: 2px solid white;
}
.panelTitle{
display: block;
width: 100%;
height: 38px;
background: #b3b3b3;
background: $titleBgcolor;
img{
height: 22px;
......@@ -23,4 +23,26 @@
color: #333;
line-height: 38px;
}
}
\ No newline at end of file
}
.top10bg{
background-color: $titleBgcolor;
.top10Title{
width: 160px;
}
.title{
width: 135px;
border-left: 2px solid #fff;
}
.minTitle{
border-right: 2px solid #fff;
}
.title:hover{
cursor: pointer;
}
}
#barBack{
/* status: 'INITIALIZED' | 'EXPERIMENT_RUNNING' | 'ERROR' | 'STOPPING' | 'STOPPED' | 'DONE' */
.EXPERIMENT_RUNNING, .STOPPING, .INITIALIZED{
/* status: 'INITIALIZED' | 'RUNNING' | 'ERROR' | 'STOPPING' | 'STOPPED' | 'DONE' */
.RUNNING, .STOPPING, .INITIALIZED{
/* specific status color */
color: #0071bc;
.ant-progress-bg {
......
.progress{
margin: 15px 20px;
margin: 15px 17px;
.status{
color: #0573bc;
font-size: 20px;
font-weight: bold;
margin-top: 5px;
}
.probar{
......@@ -71,24 +72,21 @@
}
*/
.inputBox{
height: 30px;
margin-top: 8px;
border-bottom: 1px solid #ccc;
padding-bottom: 44px;
.title{
font-size: 14px;
margin-right: 10px;
}
height: 26px;
.concurrencyInput{
width: 25%;
height: 32px;
height: 26px;
padding-left: 8px;
outline: none;
border: 1px solid #ccc;
}
.editStyle{
height: 32px;
height: 26px;
padding: 0 9px;
}
}
.lineBasic{
padding-bottom: 14px;
border-bottom: 1px solid #ccc;
}
$barHeight: 56px;
.nav{
list-style: none;
width: 100%;
height: 80px;
li{
float: left;
margin-right: 40px;
width: 94%;
height: $barHeight;
margin: 0 auto;
.tab{
font-family: 'Segoe';
line-height: $barHeight;
a{
display: block;
padding: 0 10px;
font-size: 18px;
color: #b8c7ce;
text-decoration: none;
}
}
.firstTab{
margin: 0 32px;
}
.logo{
height: 80px;
line-height: 80px;
margin-top: 2px;
max-width: 128px;
}
.tab{
position: relative;
top: 36px;
line-height: 30px;
}
.tab a:hover, .tab a.high{
color: white;
border-bottom: 1px solid #fff;
}
.feedback{
text-align: right;
line-height: $barHeight;
font-size: 16px;
color: #fff;
a{
color: #fff;
text-decoration: none;
margin-left: 10px;
}
img{
width: 24px;
margin-right: 8px;
}
.last{
margin-right: 0;
.version{
margin-left: 16px;
}
}
.nav li a:hover, .nav a.high{
color: white;
border-bottom: 1px solid white;
.link li{
float: left;
}
.dropdown{
margin-right: 10px;
}
\ No newline at end of file
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