This is an automated email from the ASF dual-hosted git repository. hanahmily pushed a commit to branch feature/5.0.0 in repository https://gitbox.apache.org/repos/asf/incubator-skywalking-ui.git
commit 1080608e0dba0b2e1e4ce4e31e1e502580b2e4f2 Author: gaohongtao <[email protected]> AuthorDate: Fri Dec 22 23:08:11 2017 +0800 First version of demo is finished --- src/main/frontend/src/common/nav.js | 12 ++ .../frontend/src/components/Charts/Line/index.js | 144 +++++++++++++++++ src/main/frontend/src/components/Charts/index.js | 2 + .../src/components/DescriptionList/Description.js | 17 ++ .../components/DescriptionList/DescriptionList.js | 21 +++ .../src/components/DescriptionList/demo/basic.md | 35 +++++ .../components/DescriptionList/demo/vertical.md | 35 +++++ .../src/components/DescriptionList/index.d.ts | 22 +++ .../src/components/DescriptionList/index.js | 5 + .../src/components/DescriptionList/index.less | 75 +++++++++ .../src/components/DescriptionList/index.md | 39 +++++ .../src/components/DescriptionList/responsive.js | 6 + src/main/frontend/src/models/alert.js | 17 ++ src/main/frontend/src/models/server.js | 16 ++ src/main/frontend/src/routes/Alert/Alert.js | 84 ++++++++++ src/main/frontend/src/routes/Alert/Alert.less | 50 ++++++ src/main/frontend/src/routes/Server/Server.js | 172 +++++++++++++++++++++ 17 files changed, 752 insertions(+) diff --git a/src/main/frontend/src/common/nav.js b/src/main/frontend/src/common/nav.js index 7677c3d..cb40d40 100644 --- a/src/main/frontend/src/common/nav.js +++ b/src/main/frontend/src/common/nav.js @@ -28,11 +28,23 @@ export const getNavData = app => [ component: dynamicWrapper(app, ['application'], () => import('../routes/Application/Application')), }, { + name: 'Server', + icon: 'server', + path: 'server', + component: dynamicWrapper(app, ['server'], () => import('../routes/Server/Server')), + }, + { name: 'Service', icon: 'service', path: 'service', component: dynamicWrapper(app, ['service'], () => import('../routes/Service/Service')), }, + { + name: 'Alert', + icon: 'alert', + path: 'alert', + component: dynamicWrapper(app, ['alert'], () => import('../routes/Alert/Alert')), + }, ], }, ]; diff --git a/src/main/frontend/src/components/Charts/Line/index.js b/src/main/frontend/src/components/Charts/Line/index.js new file mode 100644 index 0000000..1a9c9f0 --- /dev/null +++ b/src/main/frontend/src/components/Charts/Line/index.js @@ -0,0 +1,144 @@ +import React, { PureComponent } from 'react'; +import G2 from 'g2'; +import Debounce from 'lodash-decorators/debounce'; +import Bind from 'lodash-decorators/bind'; +import equal from '../equal'; +import styles from '../index.less'; + +class Line extends PureComponent { + state = { + autoHideXLabels: false, + } + + componentDidMount() { + this.renderChart(this.props.data); + + window.addEventListener('resize', this.resize); + } + + componentWillReceiveProps(nextProps) { + if (!equal(this.props, nextProps)) { + this.renderChart(nextProps.data); + } + } + + componentWillUnmount() { + window.removeEventListener('resize', this.resize); + if (this.chart) { + this.chart.destroy(); + } + this.resize.cancel(); + } + + @Bind() + @Debounce(200) + resize() { + if (!this.node) { + return; + } + const canvasWidth = this.node.parentNode.clientWidth; + const { data = [], autoLabel = true } = this.props; + if (!autoLabel) { + return; + } + const minWidth = data.length * 30; + const { autoHideXLabels } = this.state; + + if (canvasWidth <= minWidth) { + if (!autoHideXLabels) { + this.setState({ + autoHideXLabels: true, + }); + this.renderChart(data); + } + } else if (autoHideXLabels) { + this.setState({ + autoHideXLabels: false, + }); + this.renderChart(data); + } + } + + handleRef = (n) => { + this.node = n; + } + + renderChart(data) { + const { autoHideXLabels } = this.state; + const { + height = 0, + fit = true, + margin = [32, 0, (autoHideXLabels ? 8 : 32), 40], + } = this.props; + + + if (!data || (data && data.length < 1)) { + return; + } + + // clean + this.node.innerHTML = ''; + + const { Frame } = G2; + const frame = new Frame(data); + + const chart = new G2.Chart({ + container: this.node, + forceFit: fit, + legend: null, + height: height - 22, + plotCfg: { + margin, + }, + }); + if (autoHideXLabels) { + chart.axis('x', { + title: false, + tickLine: false, + labels: false, + }); + } else { + chart.axis('x', { + title: false, + }); + } + chart.axis('y', { + title: false, + line: false, + tickLine: false, + }); + chart.source(frame, { + x: { + type: 'cat', + }, + y: { + min: 0, + }, + }); + chart.tooltip({ + title: null, + crosshairs: { + type: 'line', + }, + }); + chart.line().position('x*y'); + chart.render(); + + this.chart = chart; + } + + render() { + const { height, title } = this.props; + + return ( + <div className={styles.chart} style={{ height }}> + <div> + { title && <h4>{title}</h4>} + <div ref={this.handleRef} /> + </div> + </div> + ); + } +} + +export default Line; diff --git a/src/main/frontend/src/components/Charts/index.js b/src/main/frontend/src/components/Charts/index.js index cea9949..bed2e72 100644 --- a/src/main/frontend/src/components/Charts/index.js +++ b/src/main/frontend/src/components/Charts/index.js @@ -1,6 +1,7 @@ import numeral from 'numeral'; import ChartCard from './ChartCard'; import Bar from './Bar'; +import Line from './Line'; import Pie from './Pie'; import Radar from './Radar'; import Gauge from './Gauge'; @@ -17,6 +18,7 @@ const yuan = val => `¥ ${numeral(val).format('0,0')}`; export default { yuan, Bar, + Line, Pie, Gauge, Radar, diff --git a/src/main/frontend/src/components/DescriptionList/Description.js b/src/main/frontend/src/components/DescriptionList/Description.js new file mode 100644 index 0000000..2aae62b --- /dev/null +++ b/src/main/frontend/src/components/DescriptionList/Description.js @@ -0,0 +1,17 @@ +import React from 'react'; +import classNames from 'classnames'; +import { Col } from 'antd'; +import styles from './index.less'; +import responsive from './responsive'; + +const Description = ({ term, column, className, children, ...restProps }) => { + const clsString = classNames(styles.description, className); + return ( + <Col className={clsString} {...responsive[column]} {...restProps}> + {term && <div className={styles.term}>{term}</div>} + {children && <div className={styles.detail}>{children}</div>} + </Col> + ); +}; + +export default Description; diff --git a/src/main/frontend/src/components/DescriptionList/DescriptionList.js b/src/main/frontend/src/components/DescriptionList/DescriptionList.js new file mode 100644 index 0000000..7eca3a7 --- /dev/null +++ b/src/main/frontend/src/components/DescriptionList/DescriptionList.js @@ -0,0 +1,21 @@ +import React from 'react'; +import classNames from 'classnames'; +import { Row } from 'antd'; +import styles from './index.less'; + +export default ({ className, title, col = 3, layout = 'horizontal', gutter = 32, + children, size, ...restProps }) => { + const clsString = classNames(styles.descriptionList, styles[layout], className, { + [styles.descriptionListSmall]: size === 'small', + [styles.descriptionListLarge]: size === 'large', + }); + const column = col > 4 ? 4 : col; + return ( + <div className={clsString} {...restProps}> + {title ? <div className={styles.title}>{title}</div> : null} + <Row gutter={gutter}> + {React.Children.map(children, child => React.cloneElement(child, { column }))} + </Row> + </div> + ); +}; diff --git a/src/main/frontend/src/components/DescriptionList/demo/basic.md b/src/main/frontend/src/components/DescriptionList/demo/basic.md new file mode 100644 index 0000000..8dd8534 --- /dev/null +++ b/src/main/frontend/src/components/DescriptionList/demo/basic.md @@ -0,0 +1,35 @@ +--- +order: 0 +title: Basic +--- + +基本描述列表。 + +````jsx +import DescriptionList from 'ant-design-pro/lib/DescriptionList'; + +const { Description } = DescriptionList; + +ReactDOM.render( + <DescriptionList size="large" title="title"> + <Description term="Firefox"> + A free, open source, cross-platform, + graphical web browser developed by the + Mozilla Corporation and hundreds of + volunteers. + </Description> + <Description term="Firefox"> + A free, open source, cross-platform, + graphical web browser developed by the + Mozilla Corporation and hundreds of + volunteers. + </Description> + <Description term="Firefox"> + A free, open source, cross-platform, + graphical web browser developed by the + Mozilla Corporation and hundreds of + volunteers. + </Description> + </DescriptionList> +, mountNode); +```` diff --git a/src/main/frontend/src/components/DescriptionList/demo/vertical.md b/src/main/frontend/src/components/DescriptionList/demo/vertical.md new file mode 100644 index 0000000..a304f94 --- /dev/null +++ b/src/main/frontend/src/components/DescriptionList/demo/vertical.md @@ -0,0 +1,35 @@ +--- +order: 1 +title: Vertical +--- + +垂直布局。 + +````jsx +import DescriptionList from 'ant-design-pro/lib/DescriptionList'; + +const { Description } = DescriptionList; + +ReactDOM.render( + <DescriptionList size="large" title="title" layout="vertical"> + <Description term="Firefox"> + A free, open source, cross-platform, + graphical web browser developed by the + Mozilla Corporation and hundreds of + volunteers. + </Description> + <Description term="Firefox"> + A free, open source, cross-platform, + graphical web browser developed by the + Mozilla Corporation and hundreds of + volunteers. + </Description> + <Description term="Firefox"> + A free, open source, cross-platform, + graphical web browser developed by the + Mozilla Corporation and hundreds of + volunteers. + </Description> + </DescriptionList> +, mountNode); +```` diff --git a/src/main/frontend/src/components/DescriptionList/index.d.ts b/src/main/frontend/src/components/DescriptionList/index.d.ts new file mode 100644 index 0000000..b13c630 --- /dev/null +++ b/src/main/frontend/src/components/DescriptionList/index.d.ts @@ -0,0 +1,22 @@ +import * as React from "react"; +export interface DescriptionListProps { + layout?: "horizontal" | "vertical"; + col?: number; + title: React.ReactNode; + gutter?: number; + size?: "large" | "small"; +} + +declare class Description extends React.Component< + { + term: React.ReactNode; + }, + any +> {} + +export default class DescriptionList extends React.Component< + DescriptionListProps, + any +> { + static Description: typeof Description; +} diff --git a/src/main/frontend/src/components/DescriptionList/index.js b/src/main/frontend/src/components/DescriptionList/index.js new file mode 100644 index 0000000..357f479 --- /dev/null +++ b/src/main/frontend/src/components/DescriptionList/index.js @@ -0,0 +1,5 @@ +import DescriptionList from './DescriptionList'; +import Description from './Description'; + +DescriptionList.Description = Description; +export default DescriptionList; diff --git a/src/main/frontend/src/components/DescriptionList/index.less b/src/main/frontend/src/components/DescriptionList/index.less new file mode 100644 index 0000000..067144e --- /dev/null +++ b/src/main/frontend/src/components/DescriptionList/index.less @@ -0,0 +1,75 @@ +@import "~antd/lib/style/themes/default.less"; + +.descriptionList { + // offset the padding-bottom of last row + :global { + .ant-row { + margin-bottom: -16px; + overflow: hidden; + } + } + + .title { + font-size: 14px; + color: @heading-color; + font-weight: 500; + margin-bottom: 16px; + } + + .term { + line-height: 22px; + padding-bottom: 16px; + margin-right: 8px; + color: @heading-color; + white-space: nowrap; + display: table-cell; + + &:after { + content: ":"; + margin: 0 8px 0 2px; + position: relative; + top: -.5px; + } + } + + .detail { + line-height: 22px; + width: 100%; + padding-bottom: 16px; + color: @text-color; + display: table-cell; + } + + &.vertical { + + .term { + padding-bottom: 8px; + display: block; + } + + .detail { + display: block; + } + } +} + +.descriptionListSmall { + // offset the padding-bottom of last row + :global { + .ant-row { + margin-bottom: -8px; + } + } + .title { + margin-bottom: 12px; + color: @text-color; + } + .term, .detail { + padding-bottom: 8px; + } +} +.descriptionListLarge { + .title { + font-size: 16px; + } +} diff --git a/src/main/frontend/src/components/DescriptionList/index.md b/src/main/frontend/src/components/DescriptionList/index.md new file mode 100644 index 0000000..bfa60cb --- /dev/null +++ b/src/main/frontend/src/components/DescriptionList/index.md @@ -0,0 +1,39 @@ +--- +title: + en-US: DescriptionList + zh-CN: DescriptionList +subtitle: 描述列表 +cols: 1 +order: 4 +--- + +成组展示多个只读字段,常见于详情页的信息展示。 + +## API + +### DescriptionList + +| 参数 | 说明 | 类型 | 默认值 | +|----------|------------------------------------------|-------------|-------| +| layout | 布局方式 | Enum{'horizontal', 'vertical'} | 'horizontal' | +| col | 指定信息最多分几列展示,最终一行几列由 col 配置结合[响应式规则](/components/DescriptionList#响应式规则)决定 | number(0 < col <= 4) | 3 | +| title | 列表标题 | ReactNode | - | +| gutter | 列表项间距,单位为 `px` | number | 32 | +| size | 列表型号,可以设置为 `large` `small` | Enum{'large', 'small'} | - | + +#### 响应式规则 + +| 窗口宽度 | 展示列数 | +|---------------------|---------------------------------------------| +| `≥768px` | `col` | +| `≥576px` | `col < 2 ? col : 2` | +| `<576px` | `1` | + +### DescriptionList.Description + +| 参数 | 说明 | 类型 | 默认值 | +|----------|------------------------------------------|-------------|-------| +| term | 列表项标题 | ReactNode | - | + + + diff --git a/src/main/frontend/src/components/DescriptionList/responsive.js b/src/main/frontend/src/components/DescriptionList/responsive.js new file mode 100644 index 0000000..a5aa73f --- /dev/null +++ b/src/main/frontend/src/components/DescriptionList/responsive.js @@ -0,0 +1,6 @@ +export default { + 1: { xs: 24 }, + 2: { xs: 24, sm: 12 }, + 3: { xs: 24, sm: 12, md: 8 }, + 4: { xs: 24, sm: 12, md: 6 }, +}; diff --git a/src/main/frontend/src/models/alert.js b/src/main/frontend/src/models/alert.js new file mode 100644 index 0000000..ccd3380 --- /dev/null +++ b/src/main/frontend/src/models/alert.js @@ -0,0 +1,17 @@ +// import { xxx } from '../services/xxx'; +export default { + namespace: "alert", + state: {}, + effects: { + *fetch({ payload }, { call, put }) { + }, + }, + reducers: { + save(state, action) { + return { + ...state, + }; + }, + }, +}; + diff --git a/src/main/frontend/src/models/server.js b/src/main/frontend/src/models/server.js new file mode 100644 index 0000000..241cd05 --- /dev/null +++ b/src/main/frontend/src/models/server.js @@ -0,0 +1,16 @@ +// import { xxx } from '../services/xxx'; +export default { + namespace: "server", + state: {}, + effects: { + *fetch({ payload }, { call, put }) { + }, + }, + reducers: { + save(state, action) { + return { + ...state, + }; + }, + }, +}; diff --git a/src/main/frontend/src/routes/Alert/Alert.js b/src/main/frontend/src/routes/Alert/Alert.js new file mode 100644 index 0000000..34ea5b3 --- /dev/null +++ b/src/main/frontend/src/routes/Alert/Alert.js @@ -0,0 +1,84 @@ +import React, { PureComponent } from 'react'; +import { connect } from 'dva'; +import { Card, Table, Input } from 'antd'; +import styles from './Alert.less'; + +const { Search } = Input; + +@connect(state => ({ + alert: state.alert, +})) +export default class Alert extends PureComponent { + render() { + const columns = [{ + title: 'Content', + dataIndex: 'content', + }, { + title: 'Start Time', + dataIndex: 'time', + }, { + title: 'Alert Type', + dataIndex: 'type', + filters: [{ + text: 'Application', + value: 'Application', + }, { + text: 'Server', + value: 'Server', + }, { + text: 'Service', + value: 'Service', + }], + filterMultiple: false, + onFilter: (value, record) => record.type.indexOf(value) === 0, + }]; + + const data = [{ + key: '1', + content: 'Application alert', + time: '19:30', + type: 'Application', + }, { + key: '2', + content: 'Server is down', + time: '13:30', + type: 'Server', + }, { + key: '3', + content: 'Server is slow', + time: '8:30', + type: 'Service', + }, { + key: '4', + content: 'Service sla is low', + time: '3:30', + type: 'Service', + }]; + + function onChange(pagination, filters, sorter) { + console.log('params', pagination, filters, sorter); + } + + const extraContent = ( + <div className={styles.extraContent}> + <Search + className={styles.extraContentSearch} + placeholder="Search..." + onSearch={() => ({})} + /> + </div> + ); + + return ( + <Card + title="Alert List" + bordered={false} + extra={extraContent} + > + <div className={styles.tableList}> + <Table columns={columns} dataSource={data} onChange={onChange} /> + </div> + </Card> + ); + } +} diff --git a/src/main/frontend/src/routes/Alert/Alert.less b/src/main/frontend/src/routes/Alert/Alert.less new file mode 100644 index 0000000..b10d030 --- /dev/null +++ b/src/main/frontend/src/routes/Alert/Alert.less @@ -0,0 +1,50 @@ +@import "~antd/lib/style/themes/default.less"; +@import "../../utils/utils.less"; + +.tableList { + .tableListOperator { + margin-bottom: 16px; + button { + margin-right: 8px; + } + } +} + +.tableListForm { + :global { + .ant-form-item { + margin-bottom: 24px; + margin-right: 0; + display: flex; + > .ant-form-item-label { + width: auto; + line-height: 32px; + padding-right: 8px; + } + } + .ant-form-item-control-wrapper { + flex: 1; + } + } + .submitButtons { + white-space: nowrap; + margin-bottom: 24px; + } +} + +@media screen and (max-width: @screen-lg) { + .tableListForm :global(.ant-form-item) { + margin-right: 24px; + } +} + +@media screen and (max-width: @screen-md) { + .tableListForm :global(.ant-form-item) { + margin-right: 8px; + } +} + +.extraContentSearch { + margin-left: 16px; + width: 272px; +} diff --git a/src/main/frontend/src/routes/Server/Server.js b/src/main/frontend/src/routes/Server/Server.js new file mode 100644 index 0000000..707fe8f --- /dev/null +++ b/src/main/frontend/src/routes/Server/Server.js @@ -0,0 +1,172 @@ +import React, { PureComponent } from 'react'; +import { connect } from 'dva'; +import { Row, Col, Select, Card, Tooltip, Icon, Table } from 'antd'; +import moment from 'moment'; +import { + ChartCard, MiniArea, MiniBar, Line, +} from '../../components/Charts'; +import DescriptionList from '../../components/DescriptionList'; + +const { Description } = DescriptionList; + +@connect(state => ({ + service: state.service, +})) +export default class Dashboard extends PureComponent { + render() { + const visitData = []; + const beginDay = new Date().getTime(); + + const fakeY = [7, 5, 4, 2, 4, 7, 5, 6, 5, 9, 6, 3, 1, 5, 3, 6, 5]; + for (let i = 0; i < fakeY.length; i += 1) { + visitData.push({ + x: moment(new Date(beginDay + (1000 * 60 * 60 * 24 * i))).format('YYYY-MM-DD'), + y: fakeY[i], + }); + } + function handleChange(value) { + console.log(`selected ${value}`); + } + function handleBlur() { + console.log('blur'); + } + + function handleFocus() { + console.log('focus'); + } + const tableColumns = [{ + title: 'Time', + dataIndex: 'time', + key: 'time', + }, { + title: 'Entry', + dataIndex: 'name', + key: 'name', + }, { + title: 'Duration', + dataIndex: 'duration', + key: 'duration', + }]; + const { Option } = Select; + const slowServiceData = [{ + key: '1', + name: 'ServiceA', + time: '2017/12/11 19:22:32', + duration: '5000ms', + }, { + key: '1', + name: 'ServiceA', + time: '2017/12/11 19:22:32', + duration: '5000ms', + }, { + key: '1', + name: 'ServiceA', + time: '2017/12/11 19:22:32', + duration: '5000ms', + }, { + key: '1', + name: 'ServiceA', + time: '2017/12/11 19:22:32', + duration: '5000ms', + }, { + key: '1', + name: 'ServiceA', + time: '2017/12/11 19:22:32', + duration: '5000ms', + }]; + return ( + <div> + <Select + showSearch + style={{ width: 200 }} + placeholder="Select a server" + optionFilterProp="children" + onChange={handleChange} + onFocus={handleFocus} + onBlur={handleBlur} + > + <Option value="Server1">Server1</Option> + <Option value="Server1">Server1</Option> + <Option value="Server1">Server1</Option> + </Select> + <Card title="Info" style={{ marginTop: 24 }} bordered={false}> + <DescriptionList> + <Description term="OS Name">Mac OS X</Description> + <Description term="Host Name">hanahmily</Description> + <Description term="Process Id">21144</Description> + <Description term="IPv4">192.168.1.4</Description> + </DescriptionList> + </Card> + <Row gutter={24}> + <Col xs={24} sm={24} md={24} lg={12} xl={12} style={{ marginTop: 24 }}> + <ChartCard + title="Avg Response Time" + action={<Tooltip title="Tip"><Icon type="info-circle-o" /></Tooltip>} + total="300 ms" + > + <MiniArea + color="#975FE4" + height={46} + data={visitData} + /> + </ChartCard> + </Col> + <Col xs={24} sm={24} md={24} lg={12} xl={12} style={{ marginTop: 24 }}> + <ChartCard + title="Avg TPS" + action={<Tooltip title="Tip"><Icon type="info-circle-o" /></Tooltip>} + total="500" + > + <MiniBar + height={46} + data={visitData} + /> + </ChartCard> + </Col> + </Row> + <Row gutter={24}> + <Col xs={24} sm={24} md={24} lg={24} xl={24} style={{ marginTop: 24 }}> + <Card + title="CPU" + bordered={false} + bodyStyle={{ padding: 0 }} + > + <Line + height={250} + data={visitData} + /> + </Card> + </Col> + </Row> + <Row gutter={24}> + <Col xs={24} sm={24} md={24} lg={24} xl={24} style={{ marginTop: 24 }}> + <Card + title="MEMORY" + bordered={false} + bodyStyle={{ padding: 0 }} + > + <Line + height={250} + data={visitData} + /> + </Card> + </Col> + </Row> + <Row gutter={24}> + <Col xs={24} sm={24} md={24} lg={24} xl={24} style={{ marginTop: 24 }}> + <Card + title="GC" + bordered={false} + bodyStyle={{ padding: 0 }} + > + <Line + height={250} + data={visitData} + /> + </Card> + </Col> + </Row> + </div> + ); + } +} -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
