This is an automated email from the ASF dual-hosted git repository.
hanahmily pushed a commit to branch update-antdpro
in repository https://gitbox.apache.org/repos/asf/incubator-skywalking-ui.git
The following commit(s) were added to refs/heads/update-antdpro by this push:
new 651f29c Add routes
651f29c is described below
commit 651f29c7eaef2e2fe781d3eb34192bc76f40b77c
Author: hanahmily <[email protected]>
AuthorDate: Mon Feb 26 11:55:57 2018 +0800
Add routes
---
src/routes/Alarm/Alarm.js | 132 ++++++
src/routes/Alarm/Alarm.less | 50 +++
src/routes/Application/Application.js | 121 +++++
.../Monitor.less => Application/Application.less} | 5 +
src/routes/Dashboard/Analysis.js | 488 ---------------------
src/routes/Dashboard/Analysis.less | 170 -------
src/routes/Dashboard/Dashboard.js | 137 ++++++
.../Dashboard/{Monitor.less => Dashboard.less} | 5 +
src/routes/Dashboard/Monitor.js | 171 --------
src/routes/Dashboard/Workplace.js | 275 ------------
src/routes/Dashboard/Workplace.less | 233 ----------
src/routes/Server/Server.js | 193 ++++++++
src/routes/Service/Service.js | 139 ++++++
src/routes/Service/Service.less | 0
src/routes/Topology/Topology.js | 37 ++
src/routes/Trace/Trace.js | 184 ++++++++
src/routes/Trace/Trace.less | 48 ++
17 files changed, 1051 insertions(+), 1337 deletions(-)
diff --git a/src/routes/Alarm/Alarm.js b/src/routes/Alarm/Alarm.js
new file mode 100644
index 0000000..1ab3f8b
--- /dev/null
+++ b/src/routes/Alarm/Alarm.js
@@ -0,0 +1,132 @@
+import React, { PureComponent } from 'react';
+import { connect } from 'dva';
+import { Card, Input, Tabs, List, Avatar } from 'antd';
+import { Panel } from '../../components/Page';
+import styles from './Alarm.less';
+
+const { Search } = Input;
+const { TabPane } = Tabs;
+const defaultPaging = {
+ pageNum: 1,
+ pageSize: 10,
+ needTotal: true,
+};
+
+@connect(state => ({
+ alarm: state.alarm,
+ globalVariables: state.global.globalVariables,
+ loading: state.loading.models.alarm,
+}))
+export default class Alarm extends PureComponent {
+ componentDidMount() {
+ const { alarm: { variables: { values } } } = this.props;
+ if (!values.alarmType) {
+ this.props.dispatch({
+ type: 'alarm/saveVariables',
+ payload: { values: {
+ alarmType: 'APPLICATION',
+ paging: defaultPaging,
+ } },
+ });
+ }
+ }
+ handleSearch = (keyword) => {
+ this.props.dispatch({
+ type: 'alarm/saveVariables',
+ payload: { values: {
+ keyword,
+ paging: defaultPaging,
+ } },
+ });
+ }
+ handlePageChange = (pag) => {
+ this.props.dispatch({
+ type: 'alarm/saveVariables',
+ payload: { values: {
+ paging: {
+ pageNum: pag,
+ pageSize: 10,
+ needTotal: true,
+ },
+ } },
+ });
+ }
+ changeAlarmType = (alarmType) => {
+ this.props.dispatch({
+ type: 'alarm/saveVariables',
+ payload: { values: {
+ alarmType,
+ paging: defaultPaging,
+ } },
+ });
+ }
+ handleChange = (variables) => {
+ const type = variables.alarmType.charAt(0) +
variables.alarmType.slice(1).toLowerCase();
+ this.props.dispatch({
+ type: 'alarm/fetchData',
+ payload: { variables, reducer: `save${type}AlarmList` },
+ });
+ }
+ renderList = ({ items, total }) => {
+ const { alarm: { variables: { values: { paging = defaultPaging } } },
loading } = this.props;
+ const pagination = {
+ pageSize: paging.pageSize,
+ current: paging.pageNum,
+ total,
+ onChange: this.handlePageChange,
+ };
+ return (
+ <List
+ className="demo-loadmore-list"
+ loading={loading}
+ itemLayout="horizontal"
+ dataSource={items}
+ pagination={pagination}
+ renderItem={item => (
+ <List.Item>
+ <List.Item.Meta
+ avatar={
+ <Avatar
+ style={item.causeType === 'LOW_SUCCESS_RATE' ? {
backgroundColor: '#e68a00' } : { backgroundColor: '#b32400' }}
+ icon={item.causeType === 'LOW_SUCCESS_RATE' ?
'clock-circle-o' : 'close'}
+ />}
+ title={item.title}
+ description={item.content}
+ />
+ <div>{item.startTime}</div>
+ </List.Item>
+ )}
+ />);
+ }
+ render() {
+ const extraContent = (
+ <div className={styles.extraContent}>
+ <Search
+ className={styles.extraContentSearch}
+ placeholder="Search..."
+ onSearch={this.handleSearch}
+ />
+ </div>
+ );
+ const { alarm: { variables: { values }, data } } = this.props;
+ return (
+ <Panel
+ variables={values}
+ globalVariables={this.props.globalVariables}
+ onChange={this.handleChange}
+ >
+ <Card
+ title="Alarm List"
+ bordered={false}
+ extra={extraContent}
+ >
+ <Tabs activeKey={values.alarmType} onChange={this.changeAlarmType}>
+ <TabPane tab="Application"
key="APPLICATION">{this.renderList(data.applicationAlarmList)}</TabPane>
+ <TabPane tab="Server"
key="SERVER">{this.renderList(data.serverAlarmList)}</TabPane>
+ <TabPane tab="Service"
key="SERVICE">{this.renderList(data.serviceAlarmList)}</TabPane>
+ </Tabs>
+ </Card>
+ </Panel>
+ );
+ }
+}
diff --git a/src/routes/Alarm/Alarm.less b/src/routes/Alarm/Alarm.less
new file mode 100644
index 0000000..1aa3571
--- /dev/null
+++ b/src/routes/Alarm/Alarm.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/routes/Application/Application.js
b/src/routes/Application/Application.js
new file mode 100644
index 0000000..ab07b15
--- /dev/null
+++ b/src/routes/Application/Application.js
@@ -0,0 +1,121 @@
+import React, { PureComponent } from 'react';
+import { connect } from 'dva';
+import { Row, Col, Select, Card, Form } from 'antd';
+import { AppTopology } from '../../components/Topology';
+import { Panel, Ranking } from '../../components/Page';
+
+const { Option } = Select;
+const { Item: FormItem } = Form;
+
+const middleColResponsiveProps = {
+ xs: 24,
+ sm: 24,
+ md: 12,
+ lg: 12,
+ xl: 12,
+ style: { marginBottom: 24, marginTop: 24 },
+};
+
+@connect(state => ({
+ application: state.application,
+ globalVariables: state.global.globalVariables,
+}))
[email protected]({
+ mapPropsToFields(props) {
+ const { variables: { values, labels } } = props.application;
+ return {
+ applicationId: Form.createFormField({
+ value: { key: values.applicationId ? values.applicationId : '', label:
labels.applicationId ? labels.applicationId : '' },
+ }),
+ };
+ },
+})
+export default class Application extends PureComponent {
+ componentDidMount() {
+ this.props.dispatch({
+ type: 'application/initOptions',
+ payload: { variables: this.props.globalVariables },
+ });
+ }
+ componentWillUpdate(nextProps) {
+ if (nextProps.globalVariables.duration ===
this.props.globalVariables.duration) {
+ return;
+ }
+ this.props.dispatch({
+ type: 'application/initOptions',
+ payload: { variables: nextProps.globalVariables },
+ });
+ }
+ handleSelect = (selected) => {
+ this.props.dispatch({
+ type: 'application/saveVariables',
+ payload: {
+ values: { applicationId: selected.key },
+ labels: { applicationId: selected.label },
+ },
+ });
+ }
+ handleChange = (variables) => {
+ this.props.dispatch({
+ type: 'application/fetchData',
+ payload: { variables },
+ });
+ }
+ render() {
+ const { getFieldDecorator } = this.props.form;
+ const { variables: { values, options }, data } = this.props.application;
+ return (
+ <div>
+ <Form layout="inline">
+ <FormItem>
+ {getFieldDecorator('applicationId')(
+ <Select
+ showSearch
+ style={{ width: 200 }}
+ placeholder="Select a application"
+ labelInValue
+ onSelect={this.handleSelect.bind(this)}
+ >
+ {options.applicationId && options.applicationId.map((app) => {
+ return (<Option key={app.key}
value={app.key}>{app.label}</Option>);
+ })}
+ </Select>
+ )}
+ </FormItem>
+ </Form>
+ <Panel
+ variables={values}
+ globalVariables={this.props.globalVariables}
+ onChange={this.handleChange}
+ >
+ <Card
+ bordered={false}
+ bodyStyle={{ padding: 0, marginTop: 24 }}
+ >
+ <AppTopology elements={data.getApplicationTopology} layout={{
name: 'concentric', minNodeSpacing: 200 }} />
+ </Card>
+ <Row gutter={24}>
+ <Col {...middleColResponsiveProps}>
+ <Card
+ title="Slow Service"
+ bordered={false}
+ bodyStyle={{ padding: '0px 10px' }}
+ >
+ <Ranking data={data.getSlowService} title="name"
content="avgResponseTime" unit="ms" />
+ </Card>
+ </Col>
+ <Col {...middleColResponsiveProps}>
+ <Card
+ title="Servers Throughput"
+ bordered={false}
+ bodyStyle={{ padding: '0px 10px' }}
+ >
+ <Ranking data={data.getServerThroughput} title="name"
content="tps" unit="t/s" />
+ </Card>
+ </Col>
+ </Row>
+ </Panel>
+ </div>
+ );
+ }
+}
diff --git a/src/routes/Dashboard/Monitor.less
b/src/routes/Application/Application.less
similarity index 86%
copy from src/routes/Dashboard/Monitor.less
copy to src/routes/Application/Application.less
index e52dd30..8e13f67 100644
--- a/src/routes/Dashboard/Monitor.less
+++ b/src/routes/Application/Application.less
@@ -21,3 +21,8 @@
height: auto;
}
}
+
+.trendText {
+ margin-left: 8px;
+ color: @heading-color;
+}
diff --git a/src/routes/Dashboard/Analysis.js b/src/routes/Dashboard/Analysis.js
deleted file mode 100644
index 2f98da8..0000000
--- a/src/routes/Dashboard/Analysis.js
+++ /dev/null
@@ -1,488 +0,0 @@
-import React, { Component } from 'react';
-import { connect } from 'dva';
-import {
- Row,
- Col,
- Icon,
- Card,
- Tabs,
- Table,
- Radio,
- DatePicker,
- Tooltip,
- Menu,
- Dropdown,
-} from 'antd';
-import numeral from 'numeral';
-import {
- ChartCard,
- yuan,
- MiniArea,
- MiniBar,
- MiniProgress,
- Field,
- Bar,
- Pie,
- TimelineChart,
-} from '../../components/Charts';
-import Trend from '../../components/Trend';
-import NumberInfo from '../../components/NumberInfo';
-import { getTimeDistance } from '../../utils/utils';
-
-import styles from './Analysis.less';
-
-const { TabPane } = Tabs;
-const { RangePicker } = DatePicker;
-
-const rankingListData = [];
-for (let i = 0; i < 7; i += 1) {
- rankingListData.push({
- title: `工专路 ${i} 号店`,
- total: 323234,
- });
-}
-
-@connect(({ chart, loading }) => ({
- chart,
- loading: loading.effects['chart/fetch'],
-}))
-export default class Analysis extends Component {
- state = {
- salesType: 'all',
- currentTabKey: '',
- rangePickerValue: getTimeDistance('year'),
- };
-
- componentDidMount() {
- this.props.dispatch({
- type: 'chart/fetch',
- });
- }
-
- componentWillUnmount() {
- const { dispatch } = this.props;
- dispatch({
- type: 'chart/clear',
- });
- }
-
- handleChangeSalesType = (e) => {
- this.setState({
- salesType: e.target.value,
- });
- };
-
- handleTabChange = (key) => {
- this.setState({
- currentTabKey: key,
- });
- };
-
- handleRangePickerChange = (rangePickerValue) => {
- this.setState({
- rangePickerValue,
- });
-
- this.props.dispatch({
- type: 'chart/fetchSalesData',
- });
- };
-
- selectDate = (type) => {
- this.setState({
- rangePickerValue: getTimeDistance(type),
- });
-
- this.props.dispatch({
- type: 'chart/fetchSalesData',
- });
- };
-
- isActive(type) {
- const { rangePickerValue } = this.state;
- const value = getTimeDistance(type);
- if (!rangePickerValue[0] || !rangePickerValue[1]) {
- return;
- }
- if (
- rangePickerValue[0].isSame(value[0], 'day') &&
- rangePickerValue[1].isSame(value[1], 'day')
- ) {
- return styles.currentDate;
- }
- }
-
- render() {
- const { rangePickerValue, salesType, currentTabKey } = this.state;
- const { chart, loading } = this.props;
- const {
- visitData,
- visitData2,
- salesData,
- searchData,
- offlineData,
- offlineChartData,
- salesTypeData,
- salesTypeDataOnline,
- salesTypeDataOffline,
- } = chart;
-
- const salesPieData =
- salesType === 'all'
- ? salesTypeData
- : salesType === 'online' ? salesTypeDataOnline : salesTypeDataOffline;
-
- const menu = (
- <Menu>
- <Menu.Item>操作一</Menu.Item>
- <Menu.Item>操作二</Menu.Item>
- </Menu>
- );
-
- const iconGroup = (
- <span className={styles.iconGroup}>
- <Dropdown overlay={menu} placement="bottomRight">
- <Icon type="ellipsis" />
- </Dropdown>
- </span>
- );
-
- const salesExtra = (
- <div className={styles.salesExtraWrap}>
- <div className={styles.salesExtra}>
- <a className={this.isActive('today')} onClick={() =>
this.selectDate('today')}>
- 今日
- </a>
- <a className={this.isActive('week')} onClick={() =>
this.selectDate('week')}>
- 本周
- </a>
- <a className={this.isActive('month')} onClick={() =>
this.selectDate('month')}>
- 本月
- </a>
- <a className={this.isActive('year')} onClick={() =>
this.selectDate('year')}>
- 全年
- </a>
- </div>
- <RangePicker
- value={rangePickerValue}
- onChange={this.handleRangePickerChange}
- style={{ width: 256 }}
- />
- </div>
- );
-
- const columns = [
- {
- title: '排名',
- dataIndex: 'index',
- key: 'index',
- },
- {
- title: '搜索关键词',
- dataIndex: 'keyword',
- key: 'keyword',
- render: text => <a href="/">{text}</a>,
- },
- {
- title: '用户数',
- dataIndex: 'count',
- key: 'count',
- sorter: (a, b) => a.count - b.count,
- className: styles.alignRight,
- },
- {
- title: '周涨幅',
- dataIndex: 'range',
- key: 'range',
- sorter: (a, b) => a.range - b.range,
- render: (text, record) => (
- <Trend flag={record.status === 1 ? 'down' : 'up'}>
- <span style={{ marginRight: 4 }}>{text}%</span>
- </Trend>
- ),
- align: 'right',
- },
- ];
-
- const activeKey = currentTabKey || (offlineData[0] && offlineData[0].name);
-
- const CustomTab = ({ data, currentTabKey: currentKey }) => (
- <Row gutter={8} style={{ width: 138, margin: '8px 0' }}>
- <Col span={12}>
- <NumberInfo
- title={data.name}
- subTitle="转化率"
- gap={2}
- total={`${data.cvr * 100}%`}
- theme={currentKey !== data.name && 'light'}
- />
- </Col>
- <Col span={12} style={{ paddingTop: 36 }}>
- <Pie
- animate={false}
- color={currentKey !== data.name && '#BDE4FF'}
- inner={0.55}
- tooltip={false}
- margin={[0, 0, 0, 0]}
- percent={data.cvr * 100}
- height={64}
- />
- </Col>
- </Row>
- );
-
- const topColResponsiveProps = {
- xs: 24,
- sm: 12,
- md: 12,
- lg: 12,
- xl: 6,
- style: { marginBottom: 24 },
- };
-
- return (
- <div>
- <Row gutter={24}>
- <Col {...topColResponsiveProps}>
- <ChartCard
- bordered={false}
- title="总销售额"
- action={
- <Tooltip title="指标说明">
- <Icon type="info-circle-o" />
- </Tooltip>
- }
- total={yuan(126560)}
- footer={<Field label="日均销售额"
value={`¥${numeral(12423).format('0,0')}`} />}
- contentHeight={46}
- >
- <Trend flag="up" style={{ marginRight: 16 }}>
- 周同比<span className={styles.trendText}>12%</span>
- </Trend>
- <Trend flag="down">
- 日环比<span className={styles.trendText}>11%</span>
- </Trend>
- </ChartCard>
- </Col>
- <Col {...topColResponsiveProps}>
- <ChartCard
- bordered={false}
- title="访问量"
- action={
- <Tooltip title="指标说明">
- <Icon type="info-circle-o" />
- </Tooltip>
- }
- total={numeral(8846).format('0,0')}
- footer={<Field label="日访问量" value={numeral(1234).format('0,0')}
/>}
- contentHeight={46}
- >
- <MiniArea color="#975FE4" data={visitData} />
- </ChartCard>
- </Col>
- <Col {...topColResponsiveProps}>
- <ChartCard
- bordered={false}
- title="支付笔数"
- action={
- <Tooltip title="指标说明">
- <Icon type="info-circle-o" />
- </Tooltip>
- }
- total={numeral(6560).format('0,0')}
- footer={<Field label="转化率" value="60%" />}
- contentHeight={46}
- >
- <MiniBar data={visitData} />
- </ChartCard>
- </Col>
- <Col {...topColResponsiveProps}>
- <ChartCard
- bordered={false}
- title="运营活动效果"
- action={
- <Tooltip title="指标说明">
- <Icon type="info-circle-o" />
- </Tooltip>
- }
- total="78%"
- footer={
- <div style={{ whiteSpace: 'nowrap', overflow: 'hidden' }}>
- <Trend flag="up" style={{ marginRight: 16 }}>
- 周同比<span className={styles.trendText}>12%</span>
- </Trend>
- <Trend flag="down">
- 日环比<span className={styles.trendText}>11%</span>
- </Trend>
- </div>
- }
- contentHeight={46}
- >
- <MiniProgress percent={78} strokeWidth={8} target={80}
color="#13C2C2" />
- </ChartCard>
- </Col>
- </Row>
-
- <Card loading={loading} bordered={false} bodyStyle={{ padding: 0 }}>
- <div className={styles.salesCard}>
- <Tabs tabBarExtraContent={salesExtra} size="large" tabBarStyle={{
marginBottom: 24 }}>
- <TabPane tab="销售额" key="sales">
- <Row>
- <Col xl={16} lg={12} md={12} sm={24} xs={24}>
- <div className={styles.salesBar}>
- <Bar height={295} title="销售额趋势" data={salesData} />
- </div>
- </Col>
- <Col xl={8} lg={12} md={12} sm={24} xs={24}>
- <div className={styles.salesRank}>
- <h4 className={styles.rankingTitle}>门店销售额排名</h4>
- <ul className={styles.rankingList}>
- {rankingListData.map((item, i) => (
- <li key={item.title}>
- <span className={i < 3 ? styles.active : ''}>{i +
1}</span>
- <span>{item.title}</span>
- <span>{numeral(item.total).format('0,0')}</span>
- </li>
- ))}
- </ul>
- </div>
- </Col>
- </Row>
- </TabPane>
- <TabPane tab="访问量" key="views">
- <Row>
- <Col xl={16} lg={12} md={12} sm={24} xs={24}>
- <div className={styles.salesBar}>
- <Bar height={292} title="访问量趋势" data={salesData} />
- </div>
- </Col>
- <Col xl={8} lg={12} md={12} sm={24} xs={24}>
- <div className={styles.salesRank}>
- <h4 className={styles.rankingTitle}>门店访问量排名</h4>
- <ul className={styles.rankingList}>
- {rankingListData.map((item, i) => (
- <li key={item.title}>
- <span className={i < 3 ? styles.active : ''}>{i +
1}</span>
- <span>{item.title}</span>
- <span>{numeral(item.total).format('0,0')}</span>
- </li>
- ))}
- </ul>
- </div>
- </Col>
- </Row>
- </TabPane>
- </Tabs>
- </div>
- </Card>
-
- <Row gutter={24}>
- <Col xl={12} lg={24} md={24} sm={24} xs={24}>
- <Card
- loading={loading}
- bordered={false}
- title="线上热门搜索"
- extra={iconGroup}
- style={{ marginTop: 24 }}
- >
- <Row gutter={68}>
- <Col sm={12} xs={24} style={{ marginBottom: 24 }}>
- <NumberInfo
- subTitle={
- <span>
- 搜索用户数
- <Tooltip title="指标文案">
- <Icon style={{ marginLeft: 8 }} type="info-circle-o"
/>
- </Tooltip>
- </span>
- }
- gap={8}
- total={numeral(12321).format('0,0')}
- status="up"
- subTotal={17.1}
- />
- <MiniArea line height={45} data={visitData2} />
- </Col>
- <Col sm={12} xs={24} style={{ marginBottom: 24 }}>
- <NumberInfo
- subTitle="人均搜索次数"
- total={2.7}
- status="down"
- subTotal={26.2}
- gap={8}
- />
- <MiniArea line height={45} data={visitData2} />
- </Col>
- </Row>
- <Table
- rowKey={record => record.index}
- size="small"
- columns={columns}
- dataSource={searchData}
- pagination={{
- style: { marginBottom: 0 },
- pageSize: 5,
- }}
- />
- </Card>
- </Col>
- <Col xl={12} lg={24} md={24} sm={24} xs={24}>
- <Card
- loading={loading}
- className={styles.salesCard}
- bordered={false}
- title="销售额类别占比"
- bodyStyle={{ padding: 24 }}
- extra={
- <div className={styles.salesCardExtra}>
- {iconGroup}
- <div className={styles.salesTypeRadio}>
- <Radio.Group value={salesType}
onChange={this.handleChangeSalesType}>
- <Radio.Button value="all">全部渠道</Radio.Button>
- <Radio.Button value="online">线上</Radio.Button>
- <Radio.Button value="offline">门店</Radio.Button>
- </Radio.Group>
- </div>
- </div>
- }
- style={{ marginTop: 24, minHeight: 509 }}
- >
- <h4 style={{ marginTop: 8, marginBottom: 32 }}>销售额</h4>
- <Pie
- hasLegend
- subTitle="销售额"
- total={yuan(salesPieData.reduce((pre, now) => now.y + pre, 0))}
- data={salesPieData}
- valueFormat={val => yuan(val)}
- height={248}
- lineWidth={4}
- />
- </Card>
- </Col>
- </Row>
-
- <Card
- loading={loading}
- className={styles.offlineCard}
- bordered={false}
- bodyStyle={{ padding: '0 0 32px 0' }}
- style={{ marginTop: 32 }}
- >
- <Tabs activeKey={activeKey} onChange={this.handleTabChange}>
- {offlineData.map(shop => (
- <TabPane tab={<CustomTab data={shop} currentTabKey={activeKey}
/>} key={shop.name}>
- <div style={{ padding: '0 24px' }}>
- <TimelineChart
- height={400}
- data={offlineChartData}
- titleMap={{ y1: '客流量', y2: '支付笔数' }}
- />
- </div>
- </TabPane>
- ))}
- </Tabs>
- </Card>
- </div>
- );
- }
-}
diff --git a/src/routes/Dashboard/Analysis.less
b/src/routes/Dashboard/Analysis.less
deleted file mode 100644
index 38682c3..0000000
--- a/src/routes/Dashboard/Analysis.less
+++ /dev/null
@@ -1,170 +0,0 @@
-@import "~antd/lib/style/themes/default.less";
-@import "../../utils/utils.less";
-
-.iconGroup {
- i {
- transition: color 0.32s;
- color: @text-color-secondary;
- cursor: pointer;
- margin-left: 16px;
- &:hover {
- color: @text-color;
- }
- }
-}
-
-.rankingList {
- margin: 25px 0 0;
- padding: 0;
- list-style: none;
- li {
- .clearfix();
- margin-top: 16px;
- span {
- color: @text-color;
- font-size: 14px;
- line-height: 22px;
- }
- span:first-child {
- background-color: @background-color-base;
- border-radius: 20px;
- display: inline-block;
- font-size: 12px;
- font-weight: 600;
- margin-right: 24px;
- height: 20px;
- line-height: 20px;
- width: 20px;
- text-align: center;
- }
- span.active {
- //background-color: @primary-color;
- background-color: #314659;
- color: #fff;
- }
- span:last-child {
- float: right;
- }
- }
-}
-
-.salesExtra {
- display: inline-block;
- margin-right: 24px;
- a {
- color: @text-color;
- margin-left: 24px;
- &:hover {
- color: @primary-color;
- }
- &.currentDate {
- color: @primary-color;
- }
- }
-}
-
-.salesCard {
- .salesBar {
- padding: 0 0 32px 32px;
- }
- .salesRank {
- padding: 0 32px 32px 72px;
- }
- :global {
- .ant-tabs-bar {
- padding-left: 16px;
- .ant-tabs-nav .ant-tabs-tab {
- padding-top: 16px;
- padding-bottom: 14px;
- line-height: 24px;
- }
- }
- .ant-tabs-extra-content {
- padding-right: 24px;
- line-height: 55px;
- }
- .ant-card-head {
- position: relative;
- }
- }
-}
-
-.salesCardExtra {
- height: 68px;
-}
-
-.salesTypeRadio {
- position: absolute;
- left: 24px;
- bottom: 15px;
-}
-
-.offlineCard {
- :global {
- .ant-tabs-ink-bar {
- bottom: auto;
- }
- .ant-tabs-bar {
- border-bottom: none;
- }
- .ant-tabs-nav-container-scrolling {
- padding-left: 40px;
- padding-right: 40px;
- }
- .ant-tabs-tab-prev-icon:before {
- position: relative;
- left: 6px;
- }
- .ant-tabs-tab-next-icon:before {
- position: relative;
- right: 6px;
- }
- }
-
- :global(.ant-tabs-tab-active) h4 {
- color: @primary-color;
- }
-}
-
-.trendText {
- margin-left: 8px;
- color: @heading-color;
-}
-
-@media screen and (max-width: @screen-lg) {
- .salesExtra {
- display: none;
- }
-
- .rankingList {
- li {
- span:first-child {
- margin-right: 8px;
- }
- }
- }
-}
-
-@media screen and (max-width: @screen-md) {
- .rankingTitle {
- margin-top: 16px;
- }
-
- .salesCard .salesBar {
- padding: 16px;
- }
-}
-
-@media screen and (max-width: @screen-sm) {
- .salesExtraWrap {
- display: none;
- }
-
- .salesCard {
- :global {
- .ant-tabs-content {
- padding-top: 30px;
- }
- }
- }
-}
diff --git a/src/routes/Dashboard/Dashboard.js
b/src/routes/Dashboard/Dashboard.js
new file mode 100644
index 0000000..c27079d
--- /dev/null
+++ b/src/routes/Dashboard/Dashboard.js
@@ -0,0 +1,137 @@
+import React, { PureComponent } from 'react';
+import { connect } from 'dva';
+import { Row, Col, Card } from 'antd';
+import {
+ ChartCard, Pie, MiniArea, Field,
+} from '../../components/Charts';
+import { timeRange } from '../../utils/utils';
+import { Panel, Ranking } from '../../components/Page';
+
+@connect(state => ({
+ dashboard: state.dashboard,
+ duration: state.global.duration,
+ globalVariables: state.global.globalVariables,
+}))
+export default class Dashboard extends PureComponent {
+ handleDurationChange = (variables) => {
+ this.props.dispatch({
+ type: 'dashboard/fetchData',
+ payload: { variables },
+ });
+ }
+ render() {
+ const { data } = this.props.dashboard;
+ const visitData = [];
+ const { numOfAlarmRate } = data.getAlarmTrend;
+ const accuracy = 100;
+ let avg = 0;
+ let max = 0;
+ let min = 0;
+ if (numOfAlarmRate && numOfAlarmRate.length > 0) {
+ timeRange(this.props.duration).forEach((v, i) => {
+ visitData.push({ x: v, y: numOfAlarmRate[i] / accuracy });
+ });
+ avg = numOfAlarmRate.reduce((acc, curr) => acc + curr) /
numOfAlarmRate.length / accuracy;
+ max = numOfAlarmRate.reduce((acc, curr) => { return acc < curr ? curr :
acc; }) / accuracy;
+ min = numOfAlarmRate.reduce((acc, curr) => { return acc > curr ? curr :
acc; }) / accuracy;
+ }
+ return (
+ <Panel globalVariables={this.props.globalVariables}
onChange={this.handleDurationChange}>
+ <Row gutter={24}>
+ <Col xs={24} sm={24} md={12} lg={6} xl={6}>
+ <ChartCard
+ title="App"
+ avatar={<img style={{ width: 56, height: 56 }} src="app.svg"
alt="app" />}
+ total={data.getClusterBrief.numOfApplication}
+ />
+ </Col>
+ <Col xs={24} sm={24} md={12} lg={6} xl={6}>
+ <ChartCard
+ title="Service"
+ avatar={<img style={{ width: 56, height: 56 }} src="service.svg"
alt="service" />}
+ total={data.getClusterBrief.numOfService}
+ />
+ </Col>
+ <Col xs={24} sm={24} md={12} lg={6} xl={6}>
+ <ChartCard
+ title="Store"
+ avatar={<img style={{ width: 48, height: 56 }}
src="database.svg" alt="database" />}
+ total={data.getClusterBrief.numOfDatabase
+ + data.getClusterBrief.numOfCache}
+ />
+ </Col>
+ <Col xs={24} sm={24} md={12} lg={6} xl={6}>
+ <ChartCard
+ title="MQ"
+ avatar={<img style={{ width: 56, height: 56 }} src="redis.svg"
alt="redis" />}
+ total={data.getClusterBrief.numOfMQ}
+ />
+ </Col>
+ </Row>
+ <Row gutter={24}>
+ <Col xs={24} sm={24} md={24} lg={12} xl={12} style={{ marginTop: 24
}}>
+ <ChartCard
+ title="Avg Application Alarm"
+ avatar={<img style={{ width: 56, height: 56 }} src="alert.svg"
alt="app" />}
+ total={`${avg.toFixed(2)}%`}
+ footer={<div><Field label="Max" value={`${max}%`} /> <Field
label="Min" value={`${min}%`} /></div>}
+ >
+ <MiniArea
+ animate={false}
+ color="#D87093"
+ borderColor="#B22222"
+ line="true"
+ height={143}
+ data={visitData}
+ yAxis={{
+ formatter(val) {
+ return `${val} %`;
+ },
+ }}
+ />
+ </ChartCard>
+ </Col>
+ <Col xs={24} sm={24} md={24} lg={12} xl={12} style={{ marginTop: 24
}}>
+ <Card
+ bordered={false}
+ bodyStyle={{ padding: 0 }}
+ >
+ <Pie
+ animate={false}
+ hasLegend
+ title="Database"
+ subTitle="Total"
+ total={data.getConjecturalApps.apps
+ .reduce((pre, now) => now.num + pre, 0)}
+ data={data.getConjecturalApps.apps
+ .map((v) => { return { x: v.name, y: v.num }; })}
+ height={300}
+ lineWidth={4}
+ />
+ </Card>
+ </Col>
+ </Row>
+ <Row gutter={24}>
+ <Col xs={24} sm={24} md={24} lg={16} xl={16} style={{ marginTop: 24
}}>
+ <Card
+ title="Slow Service"
+ bordered={false}
+ bodyStyle={{ padding: '0px 10px' }}
+ >
+ <Ranking data={data.getTopNSlowService} title="name"
content="avgResponseTime" unit="ms" />
+ </Card>
+ </Col>
+ <Col xs={24} sm={24} md={24} lg={8} xl={8} style={{ marginTop: 24 }}>
+ <Card
+ title="Application Throughput"
+ bordered={false}
+ bodyStyle={{ padding: '0px 10px' }}
+ >
+ <Ranking data={data.getTopNApplicationThroughput}
title="applicationCode" content="tps" unit="t/s" />
+ </Card>
+ </Col>
+ </Row>
+ </Panel>
+ );
+ }
+}
diff --git a/src/routes/Dashboard/Monitor.less
b/src/routes/Dashboard/Dashboard.less
similarity index 86%
rename from src/routes/Dashboard/Monitor.less
rename to src/routes/Dashboard/Dashboard.less
index e52dd30..8e13f67 100644
--- a/src/routes/Dashboard/Monitor.less
+++ b/src/routes/Dashboard/Dashboard.less
@@ -21,3 +21,8 @@
height: auto;
}
}
+
+.trendText {
+ margin-left: 8px;
+ color: @heading-color;
+}
diff --git a/src/routes/Dashboard/Monitor.js b/src/routes/Dashboard/Monitor.js
deleted file mode 100644
index 02b1ff8..0000000
--- a/src/routes/Dashboard/Monitor.js
+++ /dev/null
@@ -1,171 +0,0 @@
-import React, { PureComponent } from 'react';
-import { connect } from 'dva';
-import { Row, Col, Card, Tooltip } from 'antd';
-import numeral from 'numeral';
-import Authorized from '../../utils/Authorized';
-import { Pie, WaterWave, Gauge, TagCloud } from '../../components/Charts';
-import NumberInfo from '../../components/NumberInfo';
-import CountDown from '../../components/CountDown';
-import ActiveChart from '../../components/ActiveChart';
-import styles from './Monitor.less';
-
-const { Secured } = Authorized;
-
-const targetTime = new Date().getTime() + 3900000;
-
-// use permission as a parameter
-const havePermissionAsync = new Promise((resolve) => {
- // Call resolve on behalf of passed
- setTimeout(() => resolve(), 1000);
-});
-@Secured(havePermissionAsync)
-@connect(({ monitor, loading }) => ({
- monitor,
- loading: loading.models.monitor,
-}))
-export default class Monitor extends PureComponent {
- componentDidMount() {
- this.props.dispatch({
- type: 'monitor/fetchTags',
- });
- }
-
- render() {
- const { monitor, loading } = this.props;
- const { tags } = monitor;
-
- return (
- <div>
- <Row gutter={24}>
- <Col xl={18} lg={24} md={24} sm={24} xs={24} style={{ marginBottom:
24 }}>
- <Card title="活动实时交易情况" bordered={false}>
- <Row>
- <Col md={6} sm={12} xs={24}>
- <NumberInfo
- subTitle="今日交易总额"
- suffix="元"
- total={numeral(124543233).format('0,0')}
- />
- </Col>
- <Col md={6} sm={12} xs={24}>
- <NumberInfo
- subTitle="销售目标完成率"
- total="92%"
- />
- </Col>
- <Col md={6} sm={12} xs={24}>
- <NumberInfo subTitle="活动剩余时间" total={<CountDown
target={targetTime} />} />
- </Col>
- <Col md={6} sm={12} xs={24}>
- <NumberInfo
- subTitle="每秒交易总额"
- suffix="元"
- total={numeral(234).format('0,0')}
- />
- </Col>
- </Row>
- <div className={styles.mapChart}>
- <Tooltip title="等待后期实现">
- <img
src="https://gw.alipayobjects.com/zos/rmsportal/HBWnDEUXCnGnGrRfrpKa.png"
alt="map" />
- </Tooltip>
- </div>
- </Card>
- </Col>
- <Col xl={6} lg={24} md={24} sm={24} xs={24}>
- <Card title="活动情况预测" style={{ marginBottom: 24 }} bordered={false}>
- <ActiveChart />
- </Card>
- <Card
- title="券核效率"
- style={{ marginBottom: 24 }}
- bodyStyle={{ textAlign: 'center' }}
- bordered={false}
- >
- <Gauge
- format={(val) => {
- switch (parseInt(val, 10)) {
- case 20:
- return '差';
- case 40:
- return '中';
- case 60:
- return '良';
- case 80:
- return '优';
- default:
- return '';
- }
- }}
- title="跳出率"
- height={180}
- percent={87}
- />
- </Card>
- </Col>
- </Row>
- <Row gutter={24}>
- <Col xl={12} lg={24} sm={24} xs={24}>
- <Card
- title="各品类占比"
- style={{ marginBottom: 24 }}
- bordered={false}
- className={styles.pieCard}
- >
- <Row style={{ padding: '16px 0' }}>
- <Col span={8}>
- <Pie
- animate={false}
- percent={28}
- subTitle="中式快餐"
- total="28%"
- height={128}
- lineWidth={2}
- />
- </Col>
- <Col span={8}>
- <Pie
- animate={false}
- color="#5DDECF"
- percent={22}
- subTitle="西餐"
- total="22%"
- height={128}
- lineWidth={2}
- />
- </Col>
- <Col span={8}>
- <Pie
- animate={false}
- color="#2FC25B"
- percent={32}
- subTitle="火锅"
- total="32%"
- height={128}
- lineWidth={2}
- />
- </Col>
- </Row>
- </Card>
- </Col>
- <Col xl={6} lg={12} sm={24} xs={24} style={{ marginBottom: 24 }}>
- <Card title="热门搜索" loading={loading} bordered={false} bodyStyle={{
overflow: 'hidden' }}>
- <TagCloud
- data={tags}
- height={161}
- />
- </Card>
- </Col>
- <Col xl={6} lg={12} sm={24} xs={24} style={{ marginBottom: 24 }}>
- <Card title="资源剩余" bodyStyle={{ textAlign: 'center', fontSize: 0
}} bordered={false}>
- <WaterWave
- height={161}
- title="补贴资金剩余"
- percent={34}
- />
- </Card>
- </Col>
- </Row>
- </div>
- );
- }
-}
diff --git a/src/routes/Dashboard/Workplace.js
b/src/routes/Dashboard/Workplace.js
deleted file mode 100644
index 9159cbf..0000000
--- a/src/routes/Dashboard/Workplace.js
+++ /dev/null
@@ -1,275 +0,0 @@
-import React, { PureComponent } from 'react';
-import moment from 'moment';
-import { connect } from 'dva';
-import { Link } from 'dva/router';
-import { Row, Col, Card, List, Avatar } from 'antd';
-
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
-import EditableLinkGroup from '../../components/EditableLinkGroup';
-import { Radar } from '../../components/Charts';
-
-import styles from './Workplace.less';
-
-const links = [
- {
- title: '操作一',
- href: '',
- },
- {
- title: '操作二',
- href: '',
- },
- {
- title: '操作三',
- href: '',
- },
- {
- title: '操作四',
- href: '',
- },
- {
- title: '操作五',
- href: '',
- },
- {
- title: '操作六',
- href: '',
- },
-];
-
-const members = [
- {
- id: 'members-1',
- title: '科学搬砖组',
- logo:
'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
- link: '',
- },
- {
- id: 'members-2',
- title: '程序员日常',
- logo:
'https://gw.alipayobjects.com/zos/rmsportal/cnrhVkzwxjPwAaCfPbdc.png',
- link: '',
- },
- {
- id: 'members-3',
- title: '设计天团',
- logo:
'https://gw.alipayobjects.com/zos/rmsportal/gaOngJwsRYRaVAuXXcmB.png',
- link: '',
- },
- {
- id: 'members-4',
- title: '中二少女团',
- logo:
'https://gw.alipayobjects.com/zos/rmsportal/ubnKSIfAJTxIgXOKlciN.png',
- link: '',
- },
- {
- id: 'members-5',
- title: '骗你学计算机',
- logo:
'https://gw.alipayobjects.com/zos/rmsportal/WhxKECPNujWoWEFNdnJE.png',
- link: '',
- },
-];
-
-@connect(({ project, activities, chart, loading }) => ({
- project,
- activities,
- chart,
- projectLoading: loading.effects['project/fetchNotice'],
- activitiesLoading: loading.effects['activities/fetchList'],
-}))
-export default class Workplace extends PureComponent {
- componentDidMount() {
- const { dispatch } = this.props;
- dispatch({
- type: 'project/fetchNotice',
- });
- dispatch({
- type: 'activities/fetchList',
- });
- dispatch({
- type: 'chart/fetch',
- });
- }
-
- componentWillUnmount() {
- const { dispatch } = this.props;
- dispatch({
- type: 'chart/clear',
- });
- }
-
- renderActivities() {
- const {
- activities: { list },
- } = this.props;
- return list.map((item) => {
- const events = item.template.split(/@\{([^{}]*)\}/gi).map((key) => {
- if (item[key]) {
- return <a href={item[key].link}
key={item[key].name}>{item[key].name}</a>;
- }
- return key;
- });
- return (
- <List.Item key={item.id}>
- <List.Item.Meta
- avatar={<Avatar src={item.user.avatar} />}
- title={
- <span>
- <a className={styles.username}>{item.user.name}</a>
-
- <span className={styles.event}>{events}</span>
- </span>
- }
- description={
- <span className={styles.datetime} title={item.updatedAt}>
- {moment(item.updatedAt).fromNow()}
- </span>
- }
- />
- </List.Item>
- );
- });
- }
-
- render() {
- const {
- project: { notice },
- projectLoading,
- activitiesLoading,
- chart: { radarData },
- } = this.props;
-
- const pageHeaderContent = (
- <div className={styles.pageHeaderContent}>
- <div className={styles.avatar}>
- <Avatar size="large"
src="https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png" />
- </div>
- <div className={styles.content}>
- <div className={styles.contentTitle}>早安,曲丽丽,祝你开心每一天!</div>
- <div>交互专家 | 蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED</div>
- </div>
- </div>
- );
-
- const extraContent = (
- <div className={styles.extraContent}>
- <div className={styles.statItem}>
- <p>项目数</p>
- <p>56</p>
- </div>
- <div className={styles.statItem}>
- <p>团队内排名</p>
- <p>8<span> / 24</span></p>
- </div>
- <div className={styles.statItem}>
- <p>项目访问</p>
- <p>2,223</p>
- </div>
- </div>
- );
-
- return (
- <PageHeaderLayout
- content={pageHeaderContent}
- extraContent={extraContent}
- >
- <Row gutter={24}>
- <Col xl={16} lg={24} md={24} sm={24} xs={24}>
- <Card
- className={styles.projectList}
- style={{ marginBottom: 24 }}
- title="进行中的项目"
- bordered={false}
- extra={<Link to="/">全部项目</Link>}
- loading={projectLoading}
- bodyStyle={{ padding: 0 }}
- >
- {
- notice.map(item => (
- <Card.Grid className={styles.projectGrid} key={item.id}>
- <Card bodyStyle={{ padding: 0 }} bordered={false}>
- <Card.Meta
- title={(
- <div className={styles.cardTitle}>
- <Avatar size="small" src={item.logo} />
- <Link to={item.href}>{item.title}</Link>
- </div>
- )}
- description={item.description}
- />
- <div className={styles.projectItemContent}>
- <Link to={item.memberLink}>{item.member || ''}</Link>
- {item.updatedAt && (
- <span className={styles.datetime}
title={item.updatedAt}>
- {moment(item.updatedAt).fromNow()}
- </span>
- )}
- </div>
- </Card>
- </Card.Grid>
- ))
- }
- </Card>
- <Card
- bodyStyle={{ padding: 0 }}
- bordered={false}
- className={styles.activeCard}
- title="动态"
- loading={activitiesLoading}
- >
- <List loading={activitiesLoading} size="large">
- <div className={styles.activitiesList}>
- {this.renderActivities()}
- </div>
- </List>
- </Card>
- </Col>
- <Col xl={8} lg={24} md={24} sm={24} xs={24}>
- <Card
- style={{ marginBottom: 24 }}
- title="快速开始 / 便捷导航"
- bordered={false}
- bodyStyle={{ padding: 0 }}
- >
- <EditableLinkGroup
- onAdd={() => {}}
- links={links}
- linkElement={Link}
- />
- </Card>
- <Card
- style={{ marginBottom: 24 }}
- bordered={false}
- title="XX 指数"
- loading={radarData.length === 0}
- >
- <div className={styles.chart}>
- <Radar hasLegend height={343} data={radarData} />
- </div>
- </Card>
- <Card
- bodyStyle={{ paddingTop: 12, paddingBottom: 12 }}
- bordered={false}
- title="团队"
- >
- <div className={styles.members}>
- <Row gutter={48}>
- {
- members.map(item => (
- <Col span={12} key={`members-item-${item.id}`}>
- <Link to={item.link}>
- <Avatar src={item.logo} size="small" />
- <span className={styles.member}>{item.title}</span>
- </Link>
- </Col>
- ))
- }
- </Row>
- </div>
- </Card>
- </Col>
- </Row>
- </PageHeaderLayout>
- );
- }
-}
diff --git a/src/routes/Dashboard/Workplace.less
b/src/routes/Dashboard/Workplace.less
deleted file mode 100644
index 4c87529..0000000
--- a/src/routes/Dashboard/Workplace.less
+++ /dev/null
@@ -1,233 +0,0 @@
-@import "~antd/lib/style/themes/default.less";
-@import "../../utils/utils.less";
-
-.activitiesList {
- padding: 0 24px 8px 24px;
- .username {
- color: @text-color;
- }
- .event {
- font-weight: normal;
- }
-}
-
-.pageHeaderContent {
- display: flex;
- .avatar {
- flex: 0 1 72px;
- margin-bottom: 8px;
- & > span {
- border-radius: 72px;
- display: block;
- width: 72px;
- height: 72px;
- }
- }
- .content {
- position: relative;
- top: 4px;
- margin-left: 24px;
- flex: 1 1 auto;
- color: @text-color-secondary;
- line-height: 22px;
- .contentTitle {
- font-size: 20px;
- line-height: 28px;
- font-weight: 500;
- color: @heading-color;
- margin-bottom: 12px;
- }
- }
-}
-
-.extraContent {
- .clearfix();
- float: right;
- white-space: nowrap;
- .statItem {
- padding: 0 32px;
- position: relative;
- display: inline-block;
- > p:first-child {
- color: @text-color-secondary;
- font-size: @font-size-base;
- line-height: 22px;
- margin-bottom: 4px;
- }
- > p {
- color: @heading-color;
- font-size: 30px;
- line-height: 38px;
- margin: 0;
- > span {
- color: @text-color-secondary;
- font-size: 20px;
- }
- }
- &:after {
- background-color: @border-color-split;
- position: absolute;
- top: 8px;
- right: 0;
- width: 1px;
- height: 40px;
- content: '';
- }
- &:last-child {
- padding-right: 0;
- &:after {
- display: none;
- }
- }
- }
-}
-
-.members {
- a {
- display: block;
- margin: 12px 0;
- line-height: 24px;
- height: 24px;
- .textOverflow();
- .member {
- font-size: @font-size-base;
- color: @text-color;
- line-height: 24px;
- max-width: 100px;
- vertical-align: top;
- margin-left: 12px;
- transition: all .3s;
- display: inline-block;
- .textOverflow();
- }
- &:hover {
- span {
- color: @primary-color;
- }
- }
- }
-}
-
-.projectList {
- :global {
- .ant-card-meta-description {
- color: @text-color-secondary;
- height: 44px;
- line-height: 22px;
- overflow: hidden;
- }
- }
- .cardTitle {
- font-size: 0;
- a {
- color: @heading-color;
- margin-left: 12px;
- line-height: 24px;
- height: 24px;
- display: inline-block;
- vertical-align: top;
- font-size: @font-size-base;
- &:hover {
- color: @primary-color;
- }
- }
- }
- .projectGrid {
- width: 33.33%;
- }
- .projectItemContent {
- display: flex;
- margin-top: 8px;
- overflow: hidden;
- font-size: 12px;
- height: 20px;
- line-height: 20px;
- .textOverflow();
- a {
- color: @text-color-secondary;
- display: inline-block;
- flex: 1 1 0;
- .textOverflow();
- &:hover {
- color: @primary-color;
- }
- }
- .datetime {
- color: @disabled-color;
- flex: 0 0 auto;
- float: right;
- }
- }
-}
-
-.datetime {
- color: @disabled-color;
-}
-
-@media screen and (max-width: @screen-xl) and (min-width: @screen-lg) {
- .activeCard {
- margin-bottom: 24px;
- }
- .members {
- margin-bottom: 0;
- }
- .extraContent {
- margin-left: -44px;
- .statItem {
- padding: 0 16px;
- }
- }
-}
-
-@media screen and (max-width: @screen-lg) {
- .activeCard {
- margin-bottom: 24px;
- }
- .members {
- margin-bottom: 0;
- }
- .extraContent {
- float: none;
- margin-right: 0;
- .statItem {
- padding: 0 16px;
- text-align: left;
- &:after {
- display: none;
- }
- }
- }
-}
-
-@media screen and (max-width: @screen-md) {
- .extraContent {
- margin-left: -16px;
- }
- .projectList {
- .projectGrid {
- width: 50%;
- }
- }
-}
-
-@media screen and (max-width: @screen-sm) {
- .pageHeaderContent {
- display: block;
- .content {
- margin-left: 0;
- }
- }
- .extraContent {
- .statItem {
- float: none;
- }
- }
-}
-
-@media screen and (max-width: @screen-xs) {
- .projectList {
- .projectGrid {
- width: 100%;
- }
- }
-}
diff --git a/src/routes/Server/Server.js b/src/routes/Server/Server.js
new file mode 100644
index 0000000..835f05b
--- /dev/null
+++ b/src/routes/Server/Server.js
@@ -0,0 +1,193 @@
+import React, { PureComponent } from 'react';
+import { connect } from 'dva';
+import { Row, Col, Card, Form } from 'antd';
+import {
+ ChartCard, MiniArea, MiniBar, Line, Area, StackBar,
+} from '../../components/Charts';
+import DescriptionList from '../../components/DescriptionList';
+import { timeRange } from '../../utils/utils';
+import { Panel, Search } from '../../components/Page';
+
+const { Description } = DescriptionList;
+const { Item: FormItem } = Form;
+
+@connect(state => ({
+ server: state.server,
+ duration: state.global.duration,
+ globalVariables: state.global.globalVariables,
+}))
[email protected]({
+ mapPropsToFields(props) {
+ const { variables: { values, labels } } = props.server;
+ return {
+ serverId: Form.createFormField({
+ value: { key: values.serverId ? values.serverId : '', label:
labels.serverId ? labels.serverId : '' },
+ }),
+ };
+ },
+})
+export default class Server extends PureComponent {
+ handleSelect = (selected) => {
+ this.props.dispatch({
+ type: 'server/save',
+ payload: {
+ variables: {
+ values: { serverId: selected.key },
+ labels: { serverId: selected.label },
+ },
+ data: {
+ serverInfo: selected,
+ },
+ },
+ });
+ }
+ handleChange = (variables) => {
+ this.props.dispatch({
+ type: 'server/fetchData',
+ payload: { variables },
+ });
+ }
+ avg = list => (list.length > 0 ?
+ (list.reduce((acc, curr) => acc + curr) / list.length).toFixed(2) : 0)
+ render() {
+ const { getFieldDecorator } = this.props.form;
+ const { variables: { values }, data } = this.props.server;
+ const { serverInfo, getServerResponseTimeTrend, getServerTPSTrend,
+ getCPUTrend, getMemoryTrend, getGCTrend } = data;
+ const timeRangeArray = timeRange(this.props.duration);
+ return (
+ <div>
+ <Form layout="inline">
+ <FormItem>
+ {getFieldDecorator('serverId')(
+ <Search
+ placeholder="Select a server"
+ onSelect={this.handleSelect.bind(this)}
+ url="/server/search"
+ variables={{ duration: this.props.globalVariables.duration }}
+ query={`
+ query SearchServer($keyword: String!, $duration: Duration!) {
+ searchServer(keyword: $keyword, duration: $duration) {
+ key: id
+ label: name
+ host
+ pid
+ ipv4
+ }
+ }
+ `}
+ />
+ )}
+ </FormItem>
+ </Form>
+ <Panel
+ variables={values}
+ globalVariables={this.props.globalVariables}
+ onChange={this.handleChange}
+ >
+ <Card title="Info" style={{ marginTop: 24 }} bordered={false}>
+ <DescriptionList>
+ <Description term="OS">{serverInfo.label}</Description>
+ <Description term="Host Name">{serverInfo.host}</Description>
+ <Description term="Process Id">{serverInfo.pid}</Description>
+ <Description term="IPv4">{serverInfo.ipv4 ?
serverInfo.ipv4.join() : ''}</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"
+ total={`${this.avg(getServerResponseTimeTrend.trendList)} ms`}
+ >
+ <MiniArea
+ animate={false}
+ color="#975FE4"
+ height={46}
+ data={getServerResponseTimeTrend.trendList
+ .map((v, i) => { return { x: timeRangeArray[i], y: v }; })}
+ />
+ </ChartCard>
+ </Col>
+ <Col xs={24} sm={24} md={24} lg={12} xl={12} style={{ marginTop:
24 }}>
+ <ChartCard
+ title="Avg TPS"
+ total={`${this.avg(getServerTPSTrend.trendList)} ms`}
+ >
+ <MiniBar
+ animate={false}
+ height={46}
+ data={getServerTPSTrend.trendList
+ .map((v, i) => { return { x: timeRangeArray[i], y: v }; })}
+ />
+ </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={getCPUTrend.cost
+ .map((v, i) => { return { x: timeRangeArray[i], y: v }; })}
+ />
+ </Card>
+ </Col>
+ </Row>
+ <Row gutter={24}>
+ <Col xs={24} sm={24} md={12} lg={12} xl={12} style={{ marginTop:
24 }}>
+ <Card
+ title="Heap"
+ bordered={false}
+ bodyStyle={{ padding: 0 }}
+ >
+ <Area
+ height={250}
+ data={getMemoryTrend.heap
+ .map((v, i) => ({ x: timeRangeArray[i], y: v, type:
'value' }))
+ .concat(getMemoryTrend.maxHeap
+ .map((v, i) => ({ x: timeRangeArray[i], y: v, type: 'limit
' })))}
+ />
+ </Card>
+ </Col>
+ <Col xs={24} sm={24} md={12} lg={12} xl={12} style={{ marginTop:
24 }}>
+ <Card
+ title="No-Heap"
+ bordered={false}
+ bodyStyle={{ padding: 0 }}
+ >
+ <Area
+ height={250}
+ data={getMemoryTrend.noheap
+ .map((v, i) => ({ x: timeRangeArray[i], y: v, type:
'value' }))
+ .concat(getMemoryTrend.maxNoheap
+ .map((v, i) => ({ x: timeRangeArray[i], y: v, type: 'limit
' })))}
+ />
+ </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 }}
+ >
+ <StackBar
+ height={250}
+ data={getGCTrend.oldGC
+ .map((v, i) => ({ x: timeRangeArray[i], y: v, type:
'oldGC' }))
+ .concat(getGCTrend.youngGC
+ .map((v, i) => ({ x: timeRangeArray[i], y: v, type:
'youngGC' })))}
+ />
+ </Card>
+ </Col>
+ </Row>
+ </Panel>
+ </div>
+ );
+ }
+}
diff --git a/src/routes/Service/Service.js b/src/routes/Service/Service.js
new file mode 100644
index 0000000..4abb882
--- /dev/null
+++ b/src/routes/Service/Service.js
@@ -0,0 +1,139 @@
+import React, { PureComponent } from 'react';
+import { connect } from 'dva';
+import { Row, Col, Card, Form } from 'antd';
+import {
+ ChartCard, MiniArea, MiniBar,
+} from '../../components/Charts';
+import { timeRange } from '../../utils/utils';
+import { ServiceTopology } from '../../components/Topology';
+import { Panel, Search } from '../../components/Page';
+
+const { Item: FormItem } = Form;
+
+@connect(state => ({
+ service: state.service,
+ duration: state.global.duration,
+ globalVariables: state.global.globalVariables,
+}))
[email protected]({
+ mapPropsToFields(props) {
+ const { variables: { values, labels } } = props.service;
+ return {
+ serviceId: Form.createFormField({
+ value: { key: values.serviceId ? values.serviceId : '', label:
labels.serviceId ? labels.serviceId : '' },
+ }),
+ };
+ },
+})
+export default class Service extends PureComponent {
+ handleSelect = (selected) => {
+ this.props.dispatch({
+ type: 'service/save',
+ payload: {
+ variables: {
+ values: { serviceId: selected.key },
+ labels: { serviceId: selected.label },
+ },
+ data: {
+ serviceInfo: selected,
+ },
+ },
+ });
+ }
+ handleChange = (variables) => {
+ this.props.dispatch({
+ type: 'service/fetchData',
+ payload: { variables },
+ });
+ }
+ avg = list => (list.length > 0 ?
+ (list.reduce((acc, curr) => acc + curr) / list.length).toFixed(2) : 0)
+ render() {
+ const { getFieldDecorator } = this.props.form;
+ const { variables: { values }, data } = this.props.service;
+ const { getServiceResponseTimeTrend, getServiceTPSTrend,
+ getServiceSLATrend, getServiceTopology } = data;
+ const timeRangeArray = timeRange(this.props.duration);
+ return (
+ <div>
+ <Form layout="inline">
+ <FormItem>
+ {getFieldDecorator('serviceId')(
+ <Search
+ placeholder="Search a service"
+ onSelect={this.handleSelect.bind(this)}
+ url="/service/search"
+ query={`
+ query SearchService($keyword: String!) {
+ searchService(keyword: $keyword, topN: 10) {
+ key: id
+ label: name
+ }
+ }
+ `}
+ />
+ )}
+ </FormItem>
+ </Form>
+ <Panel
+ variables={values}
+ globalVariables={this.props.globalVariables}
+ onChange={this.handleChange}
+ >
+ <Row gutter={24}>
+ <Col xs={24} sm={24} md={24} lg={8} xl={8} style={{ marginTop: 24
}}>
+ <ChartCard
+ title="Avg Throughout"
+ total={`${this.avg(getServiceTPSTrend.trendList)}`}
+ >
+ <MiniArea
+ animate={false}
+ color="#975FE4"
+ height={46}
+ data={getServiceTPSTrend.trendList
+ .map((v, i) => { return { x: timeRangeArray[i], y: v }; })}
+ />
+ </ChartCard>
+ </Col>
+ <Col xs={24} sm={24} md={24} lg={8} xl={8} style={{ marginTop: 24
}}>
+ <ChartCard
+ title="Avg Response Time"
+ total={`${this.avg(getServiceResponseTimeTrend.trendList)} ms`}
+ >
+ <MiniArea
+ animate={false}
+ height={46}
+ data={getServiceResponseTimeTrend.trendList
+ .map((v, i) => { return { x: timeRangeArray[i], y: v }; })}
+ />
+ </ChartCard>
+ </Col>
+ <Col xs={24} sm={24} md={24} lg={8} xl={8} style={{ marginTop: 24
}}>
+ <ChartCard
+ title="Avg SLA"
+ total={`${this.avg(getServiceSLATrend.trendList) / 100} %`}
+ >
+ <MiniBar
+ animate={false}
+ height={46}
+ data={getServiceSLATrend.trendList
+ .map((v, i) => { return { x: timeRangeArray[i], y: v / 100
}; })}
+ />
+ </ChartCard>
+ </Col>
+ </Row>
+ <Row gutter={24}>
+ <Col xs={24} sm={24} md={24} lg={24} xl={24} style={{ marginTop:
24 }}>
+ <Card
+ bordered={false}
+ bodyStyle={{ padding: 0 }}
+ >
+ <ServiceTopology elements={getServiceTopology} layout={{ name:
'concentric', minNodeSpacing: 200 }} />
+ </Card>
+ </Col>
+ </Row>
+ </Panel>
+ </div>
+ );
+ }
+}
diff --git a/src/routes/Service/Service.less b/src/routes/Service/Service.less
new file mode 100644
index 0000000..e69de29
diff --git a/src/routes/Topology/Topology.js b/src/routes/Topology/Topology.js
new file mode 100644
index 0000000..b234ce8
--- /dev/null
+++ b/src/routes/Topology/Topology.js
@@ -0,0 +1,37 @@
+import React, { PureComponent } from 'react';
+import { connect } from 'dva';
+import { ChartCard } from '../../components/Charts';
+import { AppTopology } from '../../components/Topology';
+import { Panel } from '../../components/Page';
+
+@connect(state => ({
+ topology: state.topology,
+ duration: state.global.duration,
+ globalVariables: state.global.globalVariables,
+}))
+export default class Topology extends PureComponent {
+ static defaultProps = {
+ graphHeight: 600,
+ };
+ handleChange = (variables) => {
+ this.props.dispatch({
+ type: 'topology/fetchData',
+ payload: { variables },
+ });
+ }
+ render() {
+ const { data } = this.props.topology;
+ return (
+ <Panel globalVariables={this.props.globalVariables}
onChange={this.handleChange}>
+ <ChartCard
+ title="Topolgy Map"
+ >
+ <AppTopology
+ height={this.props.graphHeight}
+ elements={data.getClusterTopology}
+ />
+ </ChartCard>
+ </Panel>
+ );
+ }
+}
diff --git a/src/routes/Trace/Trace.js b/src/routes/Trace/Trace.js
new file mode 100644
index 0000000..0730ed9
--- /dev/null
+++ b/src/routes/Trace/Trace.js
@@ -0,0 +1,184 @@
+import React, { PureComponent } from 'react';
+import { connect } from 'dva';
+import { Row, Col, Form, Input, Select, Button, Card, InputNumber } from
'antd';
+import TraceTable from '../../components/TraceTable';
+import { Panel } from '../../components/Page';
+import styles from './Trace.less';
+
+const { Option } = Select;
+const FormItem = Form.Item;
+
+
+@connect(state => ({
+ trace: state.trace,
+ duration: state.global.duration,
+ loading: state.loading.models.trace,
+ globalVariables: state.global.globalVariables,
+}))
[email protected]({
+ mapPropsToFields(props) {
+ const { variables: { values } } = props.trace;
+ const result = {};
+ Object.keys(values).forEach((_) => {
+ result[_] = Form.createFormField({
+ value: values[_],
+ });
+ });
+ return result;
+ },
+})
+export default class Trace extends PureComponent {
+ componentDidMount() {
+ this.props.dispatch({
+ type: 'trace/initOptions',
+ payload: { variables: this.props.globalVariables },
+ });
+ }
+ componentWillUpdate(nextProps) {
+ if (nextProps.globalVariables.duration ===
this.props.globalVariables.duration) {
+ return;
+ }
+ this.props.dispatch({
+ type: 'trace/initOptions',
+ payload: { variables: nextProps.globalVariables },
+ });
+ }
+ handleChange = (variables) => {
+ const filteredVariables = { ...variables };
+ filteredVariables.queryDuration = filteredVariables.duration;
+ delete filteredVariables.duration;
+ if (filteredVariables.applicationId &&
!Array.isArray(filteredVariables.applicationId)) {
+ filteredVariables.applicationId = [filteredVariables.applicationId];
+ }
+ this.props.dispatch({
+ type: 'trace/fetchData',
+ payload: { variables: { condition: filteredVariables } },
+ });
+ }
+ handleSearch = (e) => {
+ if (e) {
+ e.preventDefault();
+ }
+ const { dispatch, form, globalVariables: { duration } } = this.props;
+
+ form.validateFields((err, fieldsValue) => {
+ if (err) return;
+ dispatch({
+ type: 'trace/saveVariables',
+ payload: {
+ values: {
+ ...fieldsValue,
+ queryDuration: duration,
+ paging: {
+ pageNum: 1,
+ pageSize: 10,
+ needTotal: true,
+ },
+ },
+ },
+ });
+ });
+ }
+ handleTableChange = (pagination) => {
+ const { dispatch, globalVariables: { duration },
+ trace: { variables: { values } } } = this.props;
+ dispatch({
+ type: 'trace/saveVariables',
+ payload: {
+ values: {
+ ...values,
+ queryDuration: duration,
+ paging: {
+ pageNum: pagination.current,
+ pageSize: pagination.pageSize,
+ needTotal: true,
+ },
+ },
+ },
+ });
+ }
+ handleTableExpand = (expanded, record) => {
+ const { dispatch } = this.props;
+ if (expanded && !record.spans) {
+ dispatch({
+ type: 'trace/fetchSpans',
+ payload: { variables: { traceId: record.traceId } },
+ });
+ }
+ }
+ renderForm() {
+ const { getFieldDecorator } = this.props.form;
+ const { trace: { variables: { options } } } = this.props;
+ return (
+ <Form onSubmit={this.handleSearch} layout="inline">
+ <Row gutter={{ md: 8, lg: 8, xl: 8 }}>
+ <Col xl={4} sm={24}>
+ <FormItem label="Application">
+ {getFieldDecorator('applicationId')(
+ <Select placeholder="No application" style={{ width: '100%' }}>
+ {options.applicationId && options.applicationId.map((app) =>
{
+ return (<Option value={app.key}>{app.label}</Option>);
+ })}
+ </Select>
+ )}
+ </FormItem>
+ </Col>
+ <Col xl={4} sm={24}>
+ <FormItem label="TraceId">
+ {getFieldDecorator('traceId')(
+ <Input placeholder="Input trace id" />
+ )}
+ </FormItem>
+ </Col>
+ <Col xl={6} sm={24}>
+ <FormItem label="OperationName">
+ {getFieldDecorator('operationName')(
+ <Input placeholder="Input operation name" />
+ )}
+ </FormItem>
+ </Col>
+ <Col xl={6} sm={24}>
+ <FormItem label="DurationRange">
+ {getFieldDecorator('minTraceDuration')(
+ <InputNumber style={{ width: '40%' }} />
+ )}~
+ {getFieldDecorator('maxTraceDuration')(
+ <InputNumber style={{ width: '40%' }} />
+ )}
+ </FormItem>
+ </Col>
+ <Col xl={4} sm={24}>
+ <span className={styles.submitButtons}>
+ <Button type="primary" htmlType="submit">Search</Button>
+ </span>
+ </Col>
+ </Row>
+ </Form>
+ );
+ }
+ render() {
+ const { trace: { variables: { values }, data: { queryBasicTraces } },
loading } = this.props;
+ return (
+ <Card bordered={false}>
+ <div className={styles.tableList}>
+ <div className={styles.tableListForm}>
+ {this.renderForm()}
+ </div>
+ <Panel
+ variables={values}
+ globalVariables={this.props.globalVariables}
+ onChange={this.handleChange}
+ >
+ <TraceTable
+ loading={loading}
+ data={queryBasicTraces.traces}
+ pagination={{ ...values.paging, total: queryBasicTraces.total }}
+ onChange={this.handleTableChange}
+ onExpand={this.handleTableExpand}
+ />
+ </Panel>
+ </div>
+ </Card>
+ );
+ }
+}
diff --git a/src/routes/Trace/Trace.less b/src/routes/Trace/Trace.less
new file mode 100644
index 0000000..5920cd1
--- /dev/null
+++ b/src/routes/Trace/Trace.less
@@ -0,0 +1,48 @@
+@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 {
+ line-height: 32px;
+ }
+ }
+ .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;
+ }
+}
--
To stop receiving notification emails like this one, please contact
[email protected].