|
@@ -0,0 +1,1741 @@
|
|
|
+import debug from 'debug';
|
|
|
+import React from 'react';
|
|
|
+import classNames from 'classnames';
|
|
|
+import PropTypes from 'prop-types';
|
|
|
+import { connect } from 'react-redux';
|
|
|
+import { Map as makeMap } from 'immutable';
|
|
|
+import { noop } from 'lodash';
|
|
|
+
|
|
|
+import { clickCloseDetails, clickShowTopologyForNode } from '../actions/request-actions';
|
|
|
+import { brightenColor, getNeutralColor, getNodeColorDark,getStatusColor,setNodeColor } from '../utils/color-utils';
|
|
|
+import { isGenericTable, isPropertyList } from '../utils/node-details-utils';
|
|
|
+import { resetDocumentTitle, setDocumentTitle } from '../utils/title-utils';
|
|
|
+
|
|
|
+import Overlay from './overlay';
|
|
|
+import MatchedText from './matched-text';
|
|
|
+import NodeDetailsControls from './node-details/node-details-controls';
|
|
|
+import NodeDetailsGenericTable from './node-details/node-details-generic-table';
|
|
|
+import NodeDetailsPropertyList from './node-details/node-details-property-list';
|
|
|
+import NodeDetailsHealth from './node-details/node-details-health';
|
|
|
+import f from './node-details/node-details-info';
|
|
|
+import NodeDetailsRelatives from './node-details/node-details-relatives';
|
|
|
+import NodeDetailsTable from './node-details/node-details-table';
|
|
|
+import Warning from './warning';
|
|
|
+
|
|
|
+
|
|
|
+import * as echarts from 'echarts'
|
|
|
+import axios from 'axios'
|
|
|
+import moment from 'moment'
|
|
|
+import { Table,Radio,Checkbox,Button, Drawer, Descriptions } from "antd";
|
|
|
+import "antd/dist/antd.css";
|
|
|
+import '../../styles/nodeDetail.less'
|
|
|
+import getToken from '../utils/get-token'
|
|
|
+const log = debug('scope:node-details');
|
|
|
+
|
|
|
+function getTruncationText(count) {
|
|
|
+ return 'This section was too long to be handled efficiently and has been truncated'
|
|
|
+ + ` (${count} extra entries not included). We are working to remove this limitation.`;
|
|
|
+}
|
|
|
+
|
|
|
+class NodeDetails extends React.Component {
|
|
|
+ constructor(props, context) {
|
|
|
+ super(props, context);
|
|
|
+ this.state = {
|
|
|
+ // baseUrl:'http://observe-server.cestong.com.cn', //本地调试使用
|
|
|
+ // traceUrl:'http://observe-front.cestong.com.cn', //本地调试使用
|
|
|
+ // coreBaseUrl: 'http://observe-front.cestong.com.cn/core', //本地调试使用
|
|
|
+ coreBaseUrl:'',//上线时打开
|
|
|
+ baseUrl:'/re', //上线时打开
|
|
|
+ traceUrl:'',//上线时打开
|
|
|
+ nodeData:{}, //节点基础信息
|
|
|
+ AnalystData:{},//散点图
|
|
|
+ queryParams:{
|
|
|
+ start_time:Math.round((new Date().getTime())/1000 - (5*60)),
|
|
|
+ end_time:Math.round(new Date().getTime()/1000),
|
|
|
+ app_alias:'opentelemetry-demo',
|
|
|
+ service_name:'frontend',
|
|
|
+ percentile:0.5,
|
|
|
+ sort_field:'Timestamp',
|
|
|
+ sort_type: 'DESC'
|
|
|
+ },
|
|
|
+ traceData:[],
|
|
|
+ livenessData:[],
|
|
|
+ barData:[],
|
|
|
+ tableData: [],
|
|
|
+ bgColor:setNodeColor('R'),
|
|
|
+ pagination: {
|
|
|
+ pageIndex:1,
|
|
|
+ pageSize:10,
|
|
|
+ total:0,
|
|
|
+ current:1,
|
|
|
+ },
|
|
|
+ pagination2: {
|
|
|
+ page_num:1,
|
|
|
+ page_size:10,
|
|
|
+ total:0,
|
|
|
+ current:1,
|
|
|
+ },
|
|
|
+ traceQuery:{
|
|
|
+ only_exception:0,
|
|
|
+ only_database:0,
|
|
|
+ },
|
|
|
+ timeoutId: null,
|
|
|
+ messaging_stats: {
|
|
|
+ topic_stats:[]
|
|
|
+ },
|
|
|
+ cloudTable: [],
|
|
|
+ cloudTable2: [],
|
|
|
+ cloudPagination: {
|
|
|
+ page_num:1,
|
|
|
+ page_size:10,
|
|
|
+ total:0,
|
|
|
+ current:1,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ handleClickClose = (ev) => {
|
|
|
+ ev.preventDefault();
|
|
|
+ this.props.clickCloseDetails(this.props.nodeId);
|
|
|
+ }
|
|
|
+
|
|
|
+ handleShowTopologyForNode = (ev) => {
|
|
|
+ ev.preventDefault();
|
|
|
+ this.props.clickShowTopologyForNode(this.props.topologyId, this.props.nodeId);
|
|
|
+ }
|
|
|
+
|
|
|
+ componentDidMount() {
|
|
|
+ let _this = this
|
|
|
+ //上线时打开开始
|
|
|
+ this.setQueryParams();//做为iframe嵌套时打开,上线时打开
|
|
|
+ const traceURL = `http://${parent.location.hostname}`
|
|
|
+ const coreBaseURL = `http://${parent.location.hostname}/core`
|
|
|
+ this.setState({
|
|
|
+ traceUrl:traceURL,
|
|
|
+ coreBaseUrl:coreBaseURL
|
|
|
+ },()=>{
|
|
|
+ })
|
|
|
+ //上线时打开结束
|
|
|
+ // 接受父组件参数
|
|
|
+
|
|
|
+ // window.top == window true 自己本身没有被嵌套
|
|
|
+ if(window.top !== window){// false 被嵌套
|
|
|
+ const data = JSON.parse(parent.localStorage.global_times)
|
|
|
+ const queryParams = _this.state.queryParams
|
|
|
+ queryParams.start_time = data.startTime
|
|
|
+ queryParams.end_time = data.endTime
|
|
|
+ _this.setState({
|
|
|
+ queryParams: queryParams
|
|
|
+ },()=>{
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ window.addEventListener('message', function(event) {
|
|
|
+ // 处理接收到的消息
|
|
|
+ const data = event.data
|
|
|
+ const queryParams = _this.state.queryParams
|
|
|
+ if (data.eventType == 'globalTimesChange') {
|
|
|
+ queryParams.start_time = data.data.startTime
|
|
|
+ queryParams.end_time = data.data.endTime
|
|
|
+ _this.setState({
|
|
|
+ queryParams: queryParams
|
|
|
+ },()=>{
|
|
|
+
|
|
|
+ if (_this.props.shape == 'cylinder'){
|
|
|
+ _this.getTableData(); //table
|
|
|
+ _this.getBarData(); // 柱形图
|
|
|
+ }
|
|
|
+ if (_this.props.shape == 'dottedcylinder'){
|
|
|
+ _this.getDottBasicsData() // dottedcylinder 基础信息
|
|
|
+ }
|
|
|
+ if (_this.props.shape == 'circle'){
|
|
|
+ _this.getNodeBasic();
|
|
|
+ _this.getNodeAnalyst();//获取散点图
|
|
|
+ _this.getNodeLiveness(); // 折线图
|
|
|
+ _this.getServiceSpans();
|
|
|
+ }
|
|
|
+ if (_this.props.shape == 'cloud'){
|
|
|
+ _this.getCloudTableData()
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else if (data.eventType == 'getLoginAuth') {
|
|
|
+ _this.setState({
|
|
|
+ getToken: data.data
|
|
|
+ }, ()=>{})
|
|
|
+ }
|
|
|
+
|
|
|
+ }, false);
|
|
|
+ }
|
|
|
+ componentWillUnmount() {
|
|
|
+ clearTimeout(this.timeoutId);
|
|
|
+ }
|
|
|
+ setQueryParams(){
|
|
|
+ var strr = parent.location.href; //上线做为iframe嵌套时使用
|
|
|
+ let param = this.parseQueryString(strr); //全链路需要的参数多,因此解析成对象形式
|
|
|
+ const queryParams = this.state.queryParams;
|
|
|
+ if(parseInt(param.start_time)!=0 && parseInt(param.end_time) !=0){ //上线时打开
|
|
|
+ let newStartTime = parseInt(param.start_time); // 设置新的属性值
|
|
|
+ let newEndTime = parseInt(param.end_time); // 设置新的属性值
|
|
|
+ queryParams.start_time = newStartTime
|
|
|
+ queryParams.end_time = newEndTime
|
|
|
+ }
|
|
|
+ const newAppAlias = param.app_alias
|
|
|
+ queryParams.app_alias = newAppAlias
|
|
|
+ this.setState({
|
|
|
+ queryParams: queryParams
|
|
|
+ },()=>{
|
|
|
+
|
|
|
+ if (this.props.shape == 'cylinder'){
|
|
|
+ this.getTableData(); //table
|
|
|
+ this.getBarData(); // 柱形图
|
|
|
+ }
|
|
|
+ if (this.props.shape == 'dottedcylinder'){
|
|
|
+ this.getDottBasicsData() // dottedcylinder 基础信息
|
|
|
+ }
|
|
|
+ if (this.props.shape == 'circle'){
|
|
|
+ this.getNodeBasic();
|
|
|
+ this.getNodeAnalyst();//获取散点图
|
|
|
+ this.getNodeLiveness(); // 折线图
|
|
|
+ this.getServiceSpans();
|
|
|
+ }
|
|
|
+ if (this.props.shape == 'cloud') {
|
|
|
+ this.getCloudTableData()
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ }
|
|
|
+ //解析URL
|
|
|
+ parseQueryString(url){
|
|
|
+ var json = {};
|
|
|
+ var arr = url.substr(url.indexOf('?') + 1).split('&');
|
|
|
+ arr.forEach(item=>{
|
|
|
+ var tmp = item.split('=');
|
|
|
+ json[tmp[0]] = tmp[1];
|
|
|
+ });
|
|
|
+ return json;
|
|
|
+ }
|
|
|
+ componentWillUnmount() {
|
|
|
+ resetDocumentTitle();
|
|
|
+ }
|
|
|
+
|
|
|
+ renderTools() {
|
|
|
+ const showSwitchTopology = this.props.nodeId !== this.props.selectedNodeId;
|
|
|
+ const topologyTitle = `View ${this.props.label} in ${this.props.topologyId}`;
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="node-details-tools-wrapper">
|
|
|
+ <div className="node-details-tools">
|
|
|
+ {showSwitchTopology
|
|
|
+ && (
|
|
|
+ <i
|
|
|
+ title={topologyTitle}
|
|
|
+ className="fa fa-long-arrow-alt-left"
|
|
|
+ onClick={this.handleShowTopologyForNode}>
|
|
|
+ <span>
|
|
|
+Show in
|
|
|
+ {/* <span>{this.props.topologyId.replace(/-/g, ' ')}</span> */}
|
|
|
+ </span>
|
|
|
+ </i>
|
|
|
+ )
|
|
|
+ }
|
|
|
+ <i
|
|
|
+ title="Close details"
|
|
|
+ className="fa fa-times close-details"
|
|
|
+ onClick={this.handleClickClose}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ renderLoading() {
|
|
|
+ const node = this.props.nodes.get(this.props.nodeId);
|
|
|
+ const label = node ? node.get('label') : this.props.label;
|
|
|
+ // NOTE: If we start the fa-spin animation before the node details panel has been
|
|
|
+ // mounted, the spinner is displayed blurred the whole time in Chrome (possibly
|
|
|
+ // caused by a bug having to do with animating the details panel).
|
|
|
+ const spinnerClassName = classNames('fa fa-circle-notch', { 'fa-spin': this.props.mounted });
|
|
|
+ const nodeColor = (node
|
|
|
+ ? getNodeColorDark(node.get('rank'), label, node.get('pseudo'))
|
|
|
+ : getNeutralColor());
|
|
|
+ const tools = this.renderTools();
|
|
|
+ const styles = {
|
|
|
+ header: {
|
|
|
+ backgroundColor: nodeColor
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="node-details">
|
|
|
+ {tools}
|
|
|
+ <div className="node-details-header" style={styles.header}>
|
|
|
+ <div className="node-details-header-wrapper">
|
|
|
+ <h2 className="node-details-header-label truncate">
|
|
|
+ {label}
|
|
|
+ </h2>
|
|
|
+ <div className="node-details-relatives truncate">
|
|
|
+ Loading...
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className="node-details-content" style="padding:0 12px">
|
|
|
+ <div className="node-details-content-loading">
|
|
|
+ <span className={spinnerClassName} />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ renderNotAvailable() {
|
|
|
+ const tools = this.renderTools();
|
|
|
+ return (
|
|
|
+ <div className="node-details">
|
|
|
+ {tools}
|
|
|
+ <div className="node-details-header node-details-header-notavailable">
|
|
|
+ <div className="node-details-header-wrapper">
|
|
|
+ <h2 className="node-details-header-label">
|
|
|
+ {this.props.label}
|
|
|
+ </h2>
|
|
|
+ <div className="node-details-relatives truncate">
|
|
|
+ n/a
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className="node-details-content">
|
|
|
+ <p className="node-details-content-info">
|
|
|
+ <strong>{this.props.label}</strong>
|
|
|
+ {' '}
|
|
|
+not found!
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ <Overlay faded={this.props.transitioning} />
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ render() {
|
|
|
+ // if (this.props.notFound) {
|
|
|
+ // return this.renderNotAvailable();
|
|
|
+ // }
|
|
|
+
|
|
|
+ // if (this.props.details) {
|
|
|
+ // return this.renderDetails();
|
|
|
+ // }
|
|
|
+
|
|
|
+ // return this.renderLoading();
|
|
|
+
|
|
|
+ return this.renderNodeDetails();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ renderDetails() {
|
|
|
+ const {
|
|
|
+ details, nodeControlStatus, nodeMatches = makeMap(), topologyId
|
|
|
+ } = this.props;
|
|
|
+ const showControls = details.controls && details.controls.length > 0;
|
|
|
+ const nodeColor = getNodeColorDark(details.rank, details.label, details.pseudo);
|
|
|
+ // const nodeColor= setNodeColor(details.color)
|
|
|
+ const {error, pending} = nodeControlStatus ? nodeControlStatus.toJS() : {};
|
|
|
+ const tools = this.renderTools();
|
|
|
+ const styles = {
|
|
|
+ controls: {
|
|
|
+ backgroundColor: brightenColor(nodeColor)
|
|
|
+ },
|
|
|
+ header: {
|
|
|
+ backgroundColor: nodeColor
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="tour-step-anchor node-details">
|
|
|
+ {tools}
|
|
|
+ <div className="node-details-header" style={styles.header}>
|
|
|
+ <div className="node-details-header-wrapper">
|
|
|
+ <h2 className="node-details-header-label truncate" title={details.label}>
|
|
|
+ <MatchedText text={details.label} match={nodeMatches.get('label')} />
|
|
|
+ </h2>
|
|
|
+ <div className="node-details-header-relatives">
|
|
|
+ {details.parents && (
|
|
|
+ <NodeDetailsRelatives
|
|
|
+ matches={nodeMatches.get('parents')}
|
|
|
+ relatives={details.parents} />
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {showControls
|
|
|
+ && (
|
|
|
+ <div className="tour-step-anchor node-details-controls-wrapper" style={styles.controls}>
|
|
|
+ <NodeDetailsControls
|
|
|
+ nodeId={this.props.nodeId}
|
|
|
+ controls={details.controls}
|
|
|
+ pending={pending}
|
|
|
+ error={error} />
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ <div className="node-details-content">
|
|
|
+ {details.metrics
|
|
|
+ && (
|
|
|
+ <div className="node-details-content-section">
|
|
|
+ <div className="node-details-content-section-header">Status</div>
|
|
|
+ <NodeDetailsHealth
|
|
|
+ metrics={details.metrics}
|
|
|
+ topologyId={topologyId}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ }
|
|
|
+ {details.metadata
|
|
|
+ && (
|
|
|
+ <div className="node-details-content-section">
|
|
|
+ <div className="node-details-content-section-header">Info</div>
|
|
|
+ <NodeDetailsInfo rows={details.metadata} matches={nodeMatches.get('metadata')} />
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ {details.connections && details.connections.filter(cs => cs.connections.length > 0)
|
|
|
+ .map(connections => (
|
|
|
+ <div className="node-details-content-section" key={connections.id}>
|
|
|
+ <NodeDetailsTable
|
|
|
+ {...connections}
|
|
|
+ nodes={connections.connections}
|
|
|
+ nodeIdKey="nodeId"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+
|
|
|
+ {details.children && details.children.map(children => (
|
|
|
+ <div className="node-details-content-section" key={children.topologyId}>
|
|
|
+ <NodeDetailsTable {...children} />
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+
|
|
|
+ {details.tables && details.tables.length > 0 && details.tables.map((table) => {
|
|
|
+ if (table.rows.length > 0) {
|
|
|
+ return (
|
|
|
+ <div className="node-details-content-section" key={table.id}>
|
|
|
+ <div className="node-details-content-section-header">
|
|
|
+ {table.label && table.label.length > 0 && table.label}
|
|
|
+ {table.truncationCount > 0
|
|
|
+ && (
|
|
|
+ <span
|
|
|
+ className="node-details-content-section-header-warning">
|
|
|
+ <Warning text={getTruncationText(table.truncationCount)} />
|
|
|
+ </span>
|
|
|
+ )
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ {this.renderTable(table)}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ })}
|
|
|
+
|
|
|
+ {this.props.renderNodeDetailsExtras({ details, topologyId })}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <Overlay faded={this.props.transitioning} />
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ renderNodeDetails(){
|
|
|
+ const {
|
|
|
+ details, nodeControlStatus, nodeMatches = makeMap(), topologyId
|
|
|
+ } = this.props;
|
|
|
+ const node = this.props.nodes.get(this.props.nodeId);
|
|
|
+ const label = node ? node.get('label') : this.props.label;
|
|
|
+ // const nodeColor = getNodeColorDark(details.rank, details.label, details.pseudo);
|
|
|
+ // const nodeColor= setNodeColor(details.color)
|
|
|
+ const {error, pending} = nodeControlStatus ? nodeControlStatus.toJS() : {};
|
|
|
+ const tools = this.renderTools();
|
|
|
+ const styles = {
|
|
|
+ controls: {
|
|
|
+ // backgroundColor: brightenColor(nodeColor)
|
|
|
+ },
|
|
|
+ header: {
|
|
|
+ // backgroundColor: '#5BB2FA'
|
|
|
+ backgroundColor:this.state.bgColor
|
|
|
+ }
|
|
|
+ };
|
|
|
+ const columns = [
|
|
|
+ {
|
|
|
+ title: 'TraceID',
|
|
|
+ dataIndex: 'trace_id',
|
|
|
+ key: 'trace_id',
|
|
|
+ width:'20%',
|
|
|
+ ellipsis:true,
|
|
|
+ align:'center',
|
|
|
+ // scopedSlots:{customRender:'trace_id'},
|
|
|
+ render: (text,record) => <a target='_blank' href={`${this.state.traceUrl}/#/latency/index?traceId=${text}&app_alias=${this.state.queryParams.app_alias}&span_id=${record.span_id}&datetime=${Date.parse(record.datetime)/1000}`}>{text}</a>
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '方法',
|
|
|
+ dataIndex: 'method',
|
|
|
+ key: 'method',
|
|
|
+ width:'15%',
|
|
|
+ ellipsis:true,
|
|
|
+ align:'center'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '状态码',
|
|
|
+ dataIndex: 'code',
|
|
|
+ key: 'code',
|
|
|
+ width:'15%',
|
|
|
+ ellipsis:true,
|
|
|
+ align:'center'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '请求时长(ms)',
|
|
|
+ dataIndex: 'duration',
|
|
|
+ key: 'duration',
|
|
|
+ width:'30%',
|
|
|
+ ellipsis:true,
|
|
|
+ align:'center',
|
|
|
+ render:(text) => <span>{text.toFixed(2)}</span>
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '日期',
|
|
|
+ dataIndex: 'datetime',
|
|
|
+ key: 'datetime',
|
|
|
+ width:'30%',
|
|
|
+ ellipsis:true,
|
|
|
+ align:'center',
|
|
|
+ defaultSortOrder: 'descend',
|
|
|
+ sorter:true
|
|
|
+ },
|
|
|
+ ]
|
|
|
+ const colums2 = [
|
|
|
+ {
|
|
|
+ title: '服务名',
|
|
|
+ dataIndex: 'service_name',
|
|
|
+ key: 'service_name',
|
|
|
+ ellipsis:true,
|
|
|
+ align:'center',
|
|
|
+ render: (text, record) => (
|
|
|
+ <span>{record.service_name_cn? record.service_name_cn: record.service_name}</span>
|
|
|
+ )
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '执行语句',
|
|
|
+ dataIndex: 'query',
|
|
|
+ key: 'query',
|
|
|
+ width:'40%',
|
|
|
+ ellipsis:true,
|
|
|
+ align:'center',
|
|
|
+ render: (text, record) => (
|
|
|
+ <textarea value={record.query} disabled rows="3" />
|
|
|
+ )
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '慢查询次数',
|
|
|
+ dataIndex: 'slow_num',
|
|
|
+ key: 'slow_num',
|
|
|
+ ellipsis:true,
|
|
|
+ align:'center'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '错误数量',
|
|
|
+ dataIndex: 'error_num',
|
|
|
+ key: 'error_num',
|
|
|
+ ellipsis:true,
|
|
|
+ align:'center'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ const cloudColums = [
|
|
|
+ {
|
|
|
+ title: '应用名称',
|
|
|
+ dataIndex: 'app_name',
|
|
|
+ align:'center',
|
|
|
+ key: 'app_name'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '请求总数',
|
|
|
+ dataIndex: 'request_total',
|
|
|
+ align:'center',
|
|
|
+ key: 'request_total',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '错误数',
|
|
|
+ dataIndex: 'error_num',
|
|
|
+ align:'center',
|
|
|
+ key: 'error_num',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '平均延迟',
|
|
|
+ dataIndex: 'duration_average',
|
|
|
+ align:'center',
|
|
|
+ key: 'duration_average',
|
|
|
+ render: (text, record) => (
|
|
|
+ <span>{record.duration_average.toFixed(2)}</span>
|
|
|
+ )
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '中位延迟',
|
|
|
+ dataIndex: 'duration_median',
|
|
|
+ align:'center',
|
|
|
+ key: 'duration_median',
|
|
|
+ render: (text, record) => (
|
|
|
+ <span>{record.duration_median.toFixed(2)}</span>
|
|
|
+ )
|
|
|
+ }
|
|
|
+ // {
|
|
|
+ // title: '错误数',
|
|
|
+ // dataIndex: 'duration_p90',
|
|
|
+ // key: 'duration_p90',
|
|
|
+ // },
|
|
|
+ // {
|
|
|
+ // title: '错误数',
|
|
|
+ // dataIndex: 'duration_p99',
|
|
|
+ // key: 'duration_p99',
|
|
|
+ // },
|
|
|
+ ];
|
|
|
+ const cloudColums2 = [
|
|
|
+ {
|
|
|
+ title: '应用名称',
|
|
|
+ align:'center',
|
|
|
+ dataIndex: 'name',
|
|
|
+ key: 'name'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '请求总数',
|
|
|
+ align:'center',
|
|
|
+ dataIndex: 'request_total',
|
|
|
+ key: 'request_total',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '错误数',
|
|
|
+ align:'center',
|
|
|
+ dataIndex: 'error_num',
|
|
|
+ key: 'error_num',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '平均延迟',
|
|
|
+ dataIndex: 'duration_average',
|
|
|
+ align:'center',
|
|
|
+ key: 'duration_average',
|
|
|
+ render: (text, record) => (
|
|
|
+ <span>{record.duration_average.toFixed(2)}</span>
|
|
|
+ )
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '中位延迟',
|
|
|
+ dataIndex: 'duration_median',
|
|
|
+ align:'center',
|
|
|
+ key: 'duration_median',
|
|
|
+ render: (text, record) => (
|
|
|
+ <span>{record.duration_median.toFixed(2)}</span>
|
|
|
+ )
|
|
|
+ }
|
|
|
+ ];
|
|
|
+ const expandedRowRender = (record) => {
|
|
|
+ const columns = [
|
|
|
+ {
|
|
|
+ title: '服务名',
|
|
|
+ dataIndex: 'service_name_cn',
|
|
|
+ align:'center',
|
|
|
+ key: 'service_name_cn',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '请求总数',
|
|
|
+ dataIndex: 'request_total',
|
|
|
+ align:'center',
|
|
|
+ key: 'request_total',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '错误数',
|
|
|
+ dataIndex: 'error_num',
|
|
|
+ align:'center',
|
|
|
+ key: 'error_num',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '平均延迟',
|
|
|
+ dataIndex: 'duration_average',
|
|
|
+ key: 'duration_average',
|
|
|
+ align:'center',
|
|
|
+ render: (text, record) => (
|
|
|
+ <span>{record.duration_average.toFixed(2)}</span>
|
|
|
+ )
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '中位延迟',
|
|
|
+ dataIndex: 'duration_median',
|
|
|
+ key: 'duration_median',
|
|
|
+ align:'center',
|
|
|
+ render: (text, record) => (
|
|
|
+ <span>{record.duration_median.toFixed(2)}</span>
|
|
|
+ )
|
|
|
+ }
|
|
|
+ ];
|
|
|
+ return <Table columns={columns} rowKey={'service_name'} dataSource={record.service_list} pagination={false} size='small' />;
|
|
|
+ };
|
|
|
+ return (
|
|
|
+ <Drawer placement="right"
|
|
|
+ onClose={this.handleClickClose} visible={true}
|
|
|
+ destroyOnClose={true}
|
|
|
+ width="65%">
|
|
|
+ <div className="tour-step-anchor node-details">
|
|
|
+ {tools}
|
|
|
+ <div className="node-details-header" style={styles.header}>
|
|
|
+ <div className="node-details-header-wrapper">
|
|
|
+ <h2 className="node-details-header-label truncate">
|
|
|
+ {label}
|
|
|
+ </h2>
|
|
|
+ <div className="node-details-header-relatives">
|
|
|
+ {this.state.nodeData.subtitle}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ {/* {showControls
|
|
|
+ && (
|
|
|
+ <div className="tour-step-anchor node-details-controls-wrapper" style={styles.controls}>
|
|
|
+ <NodeDetailsControls
|
|
|
+ nodeId={this.props.nodeId}
|
|
|
+ controls={details.controls}
|
|
|
+ pending={pending}
|
|
|
+ error={error} />
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ } */}
|
|
|
+ {
|
|
|
+ this.props.shape == 'cylinder'&&
|
|
|
+ <div className="node-details-content" >
|
|
|
+ <div style={{marginTop:'16px'}}>
|
|
|
+ <div className="node-details-content-section-header" style={{marginBottom:0}}>执行次数</div>
|
|
|
+ <div id="chartContent" style={{width: '100%', height: '300px', borderRadius: '12px'}}></div>
|
|
|
+ <div>
|
|
|
+ <Table dataSource={this.state.tableData} columns={colums2} rowKey={'service_name'+ Math.random()} pagination={this.state.pagination2} onChange={this.handleTableChange2} size='small'>
|
|
|
+ </Table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ { this.props.shape == 'circle'&&
|
|
|
+ (<div className="node-details-content">
|
|
|
+ <div>
|
|
|
+ <div className="node-details-content-section">
|
|
|
+ <div className="node-details-content-section-header">基本信息</div>
|
|
|
+ <div className='node-details-info'>
|
|
|
+ <div style={{textAlign:'right'}}>
|
|
|
+ {/* <Button size={size}>进入服务详情</Button> */}
|
|
|
+ <Button size='small'><a target='_blank' style={{fontSize:'12px'}} href={`${this.state.traceUrl}/#/service/serviceDetail/index?app_alias=${this.state.queryParams.app_alias}&service_name=${label}`}>服务详情</a></Button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* https://zhongdian.feishu.cn/docx/HkXSdrGa2ou84zxPvvycGX5Fn3b 中让去掉的 */}
|
|
|
+ {/* <div className="node-details-content-section">
|
|
|
+ <div className="node-details-content-section-header">状态</div>
|
|
|
+ <div>
|
|
|
+ <div className='node-details-info-field'>
|
|
|
+ <div className='node-details-info-field-label truncate w50' style={{width:"50%"}}>可用性</div>
|
|
|
+ <div className='node-details-info-field-value truncate w50' style={{width:"50%"}}>
|
|
|
+ {this.state.nodeData.apdex?Math.floor(this.state.nodeData.apdex*100):0}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className='node-details-info-field'>
|
|
|
+ <div className='node-details-info-field-label truncate w50' style={{width:"50%"}}>成功率</div>
|
|
|
+ <div className='node-details-info-field-value truncate w50' style={{width:"50%"}}>{this.state.nodeData.arc__success?(this.state.nodeData.arc__success*100).toFixed(2):0}%</div>
|
|
|
+ </div>
|
|
|
+ <div className='node-details-info-field'>
|
|
|
+ <div className='node-details-info-field-label truncate w50' style={{width:"50%"}}>失败率</div>
|
|
|
+ <div className='node-details-info-field-value truncate w50' style={{width:"50%"}}>{this.state.nodeData.arc__faild?(this.state.nodeData.arc__faild*100).toFixed(2):0}%</div>
|
|
|
+ </div>
|
|
|
+ <div className='node-details-info-field'>
|
|
|
+ <div className='node-details-info-field-label truncate w50' style={{width:"50%"}}>接收数量</div>
|
|
|
+ <div className='node-details-info-field-value truncate w50' style={{width:"50%"}}>{this.state.nodeData.receive?this.state.nodeData.receive:0}</div>
|
|
|
+ </div>
|
|
|
+ <div className='node-details-info-field'>
|
|
|
+ <div className='node-details-info-field-label truncate w50' style={{width:"50%"}}>发送数量</div>
|
|
|
+ <div className='node-details-info-field-value truncate w50' style={{width:"50%"}}>{this.state.nodeData.send?this.state.nodeData.send:0}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div> */}
|
|
|
+ <div className="node-details-content-section">
|
|
|
+ <div className="node-details-content-section-header">调用次数</div>
|
|
|
+ <div className='node-details-info'>
|
|
|
+ {
|
|
|
+ this.state.livenessData.length>0?
|
|
|
+ (<div>
|
|
|
+ <div id='box' className="echartsbox"></div>
|
|
|
+ </div>)
|
|
|
+ :(
|
|
|
+ <div className='noData'>暂无数据</div>
|
|
|
+ )
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="node-details-content-section">
|
|
|
+ <div className="node-details-content-section-header">延迟比例</div>
|
|
|
+ <div className='node-details-info' style={{marginTop: "-15px",position:'relative'}}>
|
|
|
+ {
|
|
|
+ JSON.stringify(this.state.AnalystData)!="{}"?
|
|
|
+ (<div>
|
|
|
+ <div id='main' className="echartsbox" style={{height:'240px'}}></div>
|
|
|
+ </div>)
|
|
|
+ :(
|
|
|
+ <div className='noData'>暂无数据</div>
|
|
|
+ )
|
|
|
+ }
|
|
|
+ <div className='LatencySelect'>
|
|
|
+ <Radio.Group onChange={this.onChange} value={this.state.queryParams.percentile} size="small">
|
|
|
+ <Radio value={0.5}>50分位</Radio>
|
|
|
+ {/* <Radio value={0.95}>p.95</Radio> */}
|
|
|
+ <Radio value={0.99}>99分位</Radio>
|
|
|
+ </Radio.Group>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className='node-details-content-section'>
|
|
|
+ <div className="node-details-content-section-header">异常Trace</div>
|
|
|
+ <div className='node-serch'>
|
|
|
+ <Checkbox onChange={this.onChangeError}>仅异常</Checkbox>
|
|
|
+ <Checkbox onChange={this.onChangeSql}>仅SQL</Checkbox>
|
|
|
+ </div>
|
|
|
+ <Table dataSource={this.state.traceData} columns={columns} rowKey='span_id' pagination={this.state.pagination} onChange={this.handleTableChange} size='small'>
|
|
|
+ {/* <span slot='trace_id' slot-scope='text,record'>
|
|
|
+ <template>
|
|
|
+ <div>
|
|
|
+ <a target='_blank' href={`${this.state.traceUrl}/#/latency/index?traceId=${record.trace_id}&app_alias=${this.state.queryParams.app_alias}&span_id=${record.span_id}`}>{record.trace_id}</a>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </span> */}
|
|
|
+ </Table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>)
|
|
|
+ }
|
|
|
+ {
|
|
|
+ this.props.shape == 'dottedcylinder' && (
|
|
|
+ <div className='ant-descriptions-header'>
|
|
|
+ <Descriptions title="" >
|
|
|
+ {/* <Descriptions.Item label="系统名称">{this.state.messaging_stats.name}</Descriptions.Item> */}
|
|
|
+ <Descriptions.Item label="生产消息数量">{this.state.messaging_stats.produce_num || 0}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="消费消息数量">{this.state.messaging_stats.consume_num || 0}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="生产消息错误率">{this.state.messaging_stats.produce_error_rate?(this.state.messaging_stats.produce_error_rate*100).toFixed(2):0}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="消费消息错误率">{this.state.messaging_stats.consume_error_rate?(this.state.messaging_stats.consume_error_rate*100).toFixed(2):0}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="生产消息平均耗时">{this.state.messaging_stats.produce_duration_average?this.state.messaging_stats.produce_duration_average.toFixed(2):0}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="消费消息耗时">{this.state.messaging_stats.consume_duration_average?this.state.messaging_stats.consume_duration_average.toFixed(2):0}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="平均消息大小">{this.state.messaging_stats.message_size_average?this.state.messaging_stats.message_size_average.toFixed(2):0}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="主题数量">{this.state.messaging_stats.topic_num || 0}</Descriptions.Item>
|
|
|
+ </Descriptions>
|
|
|
+ <div className="bisic-title">主题统计信息</div>
|
|
|
+ <div>
|
|
|
+ {this.state.messaging_stats.topic_stats.length > 0 && this.state.messaging_stats.topic_stats.map((item, index)=>{
|
|
|
+ return <Descriptions key={index+item.name} >
|
|
|
+ <Descriptions.Item label="主题名称" >{item.name || '--'}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="生产消息数量">{item.produce_num || 0}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="消费消息数量">{item.consume_num || 0}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="生产消息错误率">{item.produce_error_rate ? (item.produce_error_rate*100).toFixed(2)+'%' :0}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="消费消息错误率">{item.consume_error_rate?(item.consume_error_rate*!100).toFixed(2)+ '%' :0}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="生产消息平均耗时">{item.produce_duration_average?item.produce_duration_average.toFixed(2):0}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="消费消息耗时">{item.consume_duration_average?item.consume_duration_average.toFixed(2):0}</Descriptions.Item>
|
|
|
+ <Descriptions.Item label="平均消息大小">{item.message_size_average?item.message_size_average.toFixed(2):0}</Descriptions.Item>
|
|
|
+ </Descriptions>
|
|
|
+ })}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ )
|
|
|
+ }
|
|
|
+ {
|
|
|
+ this.props.shape == 'cloud' && (
|
|
|
+ <div className="node-details-content" >
|
|
|
+ <div style={{marginTop:'30px'}}>
|
|
|
+ <div style={{width:'100%'}}>
|
|
|
+ <h4>已知应用</h4>
|
|
|
+ <Table dataSource={this.state.cloudTable}
|
|
|
+ columns={cloudColums}
|
|
|
+
|
|
|
+ rowKey={'app_alias'}
|
|
|
+ expandedRowRender={(record) => expandedRowRender(record)}
|
|
|
+ size='small' bordered pagination={false}>
|
|
|
+ </Table>
|
|
|
+ </div>
|
|
|
+ <div style={{marginTop:'40px'}}>
|
|
|
+ <h4>未知应用</h4>
|
|
|
+ <div style={{paddingLeft: '50px'}}>
|
|
|
+ <Table dataSource={this.state.cloudTable2}
|
|
|
+ columns={cloudColums2}
|
|
|
+ rowKey={`name`} size='small' bordered pagination={false}>
|
|
|
+ </Table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+ </div>)
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </Drawer>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ //获取基础信息
|
|
|
+ getNodeBasic(){
|
|
|
+ axios({
|
|
|
+ url: `${this.state.baseUrl}/api/v1/apps_score/${this.state.queryParams.app_alias}/svr`,
|
|
|
+ method: "get",
|
|
|
+ headers: { 'Authorization': getToken },
|
|
|
+ params: {
|
|
|
+ source_service:this.props.id,
|
|
|
+ start_time:this.state.queryParams.start_time,
|
|
|
+ end_time:this.state.queryParams.end_time
|
|
|
+ }
|
|
|
+ }).then(res => {
|
|
|
+ if(res && res.data.code == 200){
|
|
|
+ const newObj= ((res ||{}).data || {}).data || {}
|
|
|
+ this.setState({
|
|
|
+ nodeData:{...newObj}
|
|
|
+ },()=>{
|
|
|
+ if(JSON.stringify(this.state.nodeData)!="{}"){
|
|
|
+ this.state.nodeData.apdex>=0.94?this.setState({bgColor:setNodeColor('G')})
|
|
|
+ :(this.state.nodeData.apdex>=0.85&&this.state.nodeData.apdex<0.94)?this.setState({bgColor:setNodeColor('B')})
|
|
|
+ :(this.state.nodeData.apdex>=0.7&&this.state.nodeData.apdex<0.85)?this.setState({bgColor:setNodeColor('DI')})
|
|
|
+ :(this.state.nodeData.apdex>=0.5&&this.state.nodeData.apdex<0.7)?this.setState({bgColor:setNodeColor('Y')})
|
|
|
+ :this.setState({bgColor:setNodeColor('R')})
|
|
|
+ }else{
|
|
|
+ this.setState({bgColor:setNodeColor('R')})
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ //获取散点图
|
|
|
+ getNodeAnalyst(){
|
|
|
+ axios({
|
|
|
+ url: `${this.state.baseUrl}/api/v1/app/analyst/${this.state.queryParams.app_alias}/svr`,
|
|
|
+ method: "get",
|
|
|
+ headers: { 'Authorization': getToken },
|
|
|
+ params: {
|
|
|
+ source_service:this.props.id,
|
|
|
+ start_time:this.state.queryParams.start_time,
|
|
|
+ end_time:this.state.queryParams.end_time,
|
|
|
+ percentile:this.state.queryParams.percentile
|
|
|
+ }
|
|
|
+ }).then(res => {
|
|
|
+ if(res && res.data.code == 200){
|
|
|
+ const obj = ((res || {}).data ||{}).data || {}
|
|
|
+ this.setState({
|
|
|
+ AnalystData:{...obj}
|
|
|
+ },()=>{
|
|
|
+ if(JSON.stringify(this.state.AnalystData)!="{}"){
|
|
|
+ this.initChart(this.state.AnalystData)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 散点图渲染
|
|
|
+ initChart(tmpData) {
|
|
|
+ let chart = echarts.getInstanceByDom(document.getElementById("main"));
|
|
|
+ if (chart == null) {
|
|
|
+ chart = echarts.init(document.getElementById("main"));
|
|
|
+ }else {
|
|
|
+ chart.dispose();
|
|
|
+ chart = echarts.init(document.getElementById("main"));
|
|
|
+ }
|
|
|
+ let successData = tmpData.success && tmpData.success.map(item => new Date(item[0]).toLocaleString()) || []
|
|
|
+ let failedData = tmpData.failed && tmpData.failed.map(item => new Date(item[0]).toLocaleString()) || []
|
|
|
+ let option = {
|
|
|
+ title: {
|
|
|
+ text: '',
|
|
|
+ subtext: '',
|
|
|
+ textStyle:{
|
|
|
+ fontSize:14
|
|
|
+ },
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ top:'8%',
|
|
|
+ left: '3%',
|
|
|
+ right: '7%',
|
|
|
+ bottom: '14%',
|
|
|
+ containLabel: true
|
|
|
+ },
|
|
|
+ tooltip: {
|
|
|
+ showDelay: 0,
|
|
|
+ formatter: function (params) {
|
|
|
+ let newParams = moment(params.data[0]).format('YYYY-MM-DD HH:mm:ss');
|
|
|
+ let time = newParams+"<br/>"+ params.data[1]+"ms"
|
|
|
+ return time;
|
|
|
+ },
|
|
|
+ axisPointer: {
|
|
|
+ show: true,
|
|
|
+ type: 'cross',
|
|
|
+ lineStyle: {
|
|
|
+ type: 'dashed',
|
|
|
+ width: 1
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ toolbox: {
|
|
|
+ show:true,
|
|
|
+ showTitle: true,
|
|
|
+ feature: {
|
|
|
+ rect: {
|
|
|
+ show: true,
|
|
|
+ title: 'Trace选择'
|
|
|
+ },
|
|
|
+ brush: {
|
|
|
+ type: ["rect"], // 开启矩形选择
|
|
|
+ show: true,//是否显示 这里我们直接true
|
|
|
+ iconStyle: {
|
|
|
+ opacity: 0,//通过opacity设置为0隐藏图标
|
|
|
+ },
|
|
|
+ }
|
|
|
+ },
|
|
|
+ left:"40%", //组件离容器左侧的距离,'left', 'center', 'right','20%'
|
|
|
+ top:"-3%", //组件离容器上侧的距离,'top', 'middle', 'bottom','20%'
|
|
|
+ right:"auto", //组件离容器右侧的距离,'20%'
|
|
|
+ bottom:"auto",
|
|
|
+ },
|
|
|
+ brush: {
|
|
|
+ toolbox: ['rect'],
|
|
|
+ xAxisIndex: 0,
|
|
|
+ throttleType:'debounce',
|
|
|
+ throttleDelay:600
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ data: ['成功', '失败'],
|
|
|
+ left: 'center',
|
|
|
+ bottom: 0,
|
|
|
+ itemGap: 100,
|
|
|
+ textStyle: {//文字颜色
|
|
|
+ fontSize: 12,
|
|
|
+ padding:[0,3],//文字与图形之间的左右间距
|
|
|
+ rich:{
|
|
|
+ labelName:{
|
|
|
+ fontSize:14,
|
|
|
+ color:'#333',
|
|
|
+ fontWeight:500
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ formatter: function (params) {
|
|
|
+ // 获取legend显示内容
|
|
|
+ let data = tmpData;
|
|
|
+ let sl,fl;
|
|
|
+ if(data.success!=null){
|
|
|
+ sl = tmpData.success.length;
|
|
|
+ }else{
|
|
|
+ sl = 0
|
|
|
+ }
|
|
|
+ if( data.failed!=null){
|
|
|
+ fl = tmpData.failed.length;
|
|
|
+ }else{
|
|
|
+ fl = 0
|
|
|
+ }
|
|
|
+ var target;
|
|
|
+ if(params == '成功'){
|
|
|
+ target = sl;
|
|
|
+ }else if(params == '失败'){
|
|
|
+ target = fl;
|
|
|
+ }
|
|
|
+ return target != undefined?params +' '+`{labelName|${target}}`:params
|
|
|
+ },
|
|
|
+ },
|
|
|
+ xAxis: [
|
|
|
+ {
|
|
|
+
|
|
|
+ type: 'time',
|
|
|
+ data: [
|
|
|
+ ...successData,
|
|
|
+ ...failedData
|
|
|
+ ],
|
|
|
+ gridIndex:0,
|
|
|
+ axisLabel: {
|
|
|
+ show:true,
|
|
|
+ rotate: 45, // 旋转标签,适用于标签较长的情况
|
|
|
+ interval: 'auto',
|
|
|
+ margin: 10, // 增加刻度标签间隔
|
|
|
+ textStyle: {
|
|
|
+ fontSize: 10,
|
|
|
+ textAlign:'center'
|
|
|
+ },
|
|
|
+ formatter: function(params) {
|
|
|
+ let newParams = moment(params).format('HH:mm:ss');
|
|
|
+ let time = newParams
|
|
|
+ return time;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ show: true
|
|
|
+ },
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ yAxis: [
|
|
|
+ {
|
|
|
+ type: 'value',
|
|
|
+ // scale: true,
|
|
|
+ gridIndex:0,
|
|
|
+ axisLabel: {
|
|
|
+ formatter: '{value}'
|
|
|
+ },
|
|
|
+ axisLine:{
|
|
|
+ show:true
|
|
|
+ },
|
|
|
+ axisTick:{
|
|
|
+ show:true
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ show: true
|
|
|
+ },
|
|
|
+ data:[0,2500,5000,7500,10000],
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '成功',
|
|
|
+ type: 'scatter',
|
|
|
+ emphasis: {
|
|
|
+ focus: 'series'
|
|
|
+ },
|
|
|
+ //设置散点图样式
|
|
|
+ itemStyle:{
|
|
|
+ color:'#13ce66'
|
|
|
+ },
|
|
|
+ symbolSize:10,//设置散点的大小
|
|
|
+ data:tmpData.success,
|
|
|
+ markArea: {
|
|
|
+ silent: true,
|
|
|
+ itemStyle: {
|
|
|
+ color: 'transparent',
|
|
|
+ borderWidth: 0,
|
|
|
+ borderType: 'dashed'
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: '失败',
|
|
|
+ type: 'scatter',
|
|
|
+ emphasis: {
|
|
|
+ focus: 'series'
|
|
|
+ },
|
|
|
+ itemStyle:{
|
|
|
+ color:'#ff4949'
|
|
|
+ },
|
|
|
+ // prettier-ignore
|
|
|
+ data:tmpData.failed,
|
|
|
+ // data:[],
|
|
|
+ markArea: {
|
|
|
+ silent: true,
|
|
|
+ itemStyle: {
|
|
|
+ color: 'transparent',
|
|
|
+ borderWidth: 0,
|
|
|
+ borderType: 'dashed'
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ chart.setOption(option,true)
|
|
|
+ // 默认开启框选
|
|
|
+ chart.dispatchAction({
|
|
|
+ type: 'takeGlobalCursor',
|
|
|
+ key: 'brush',
|
|
|
+ brushOption: {
|
|
|
+ brushType: 'rect' // 指定选框类型
|
|
|
+ }
|
|
|
+ })
|
|
|
+ chart.off("brushSelected");
|
|
|
+ //框选选择数据
|
|
|
+ chart.on('brushSelected', (params) => {
|
|
|
+
|
|
|
+ var brushComponent = params.batch[0];
|
|
|
+
|
|
|
+ let successIndexList=[];
|
|
|
+ let failIndexList =[];
|
|
|
+ let successList=[];
|
|
|
+ let failList=[];
|
|
|
+ if(brushComponent.selected.length>1){
|
|
|
+ successIndexList = brushComponent.selected[0].dataIndex
|
|
|
+ failIndexList = brushComponent.selected[1].dataIndex
|
|
|
+ }else{
|
|
|
+ if(brushComponent.selected[0].seriesName !=undefined){
|
|
|
+ if(brushComponent.selected[0].seriesName =="失败"){
|
|
|
+ failIndexList = brushComponent.selected[0].dataIndex
|
|
|
+ }else{
|
|
|
+ successIndexList = brushComponent.selected[0].dataIndex
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if(successIndexList.length>0){
|
|
|
+ for(let i = 0;i<tmpData.success.length;i++){
|
|
|
+ for(let j=0;j<successIndexList.length;j++){
|
|
|
+ if(successIndexList[j] == i){
|
|
|
+ successList.push(tmpData.success[i])
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if(failIndexList.length>0){
|
|
|
+ for(let k=0;k<tmpData.failed.length;k++){
|
|
|
+ for(let l=0;l<failIndexList.length;l++){
|
|
|
+ if(failIndexList[l] == k){
|
|
|
+ failList.push(tmpData.failed[k])
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let arr =successList.concat(failList);
|
|
|
+ let dataRange={};
|
|
|
+ if(arr.length>0){
|
|
|
+ let timeArr =[];
|
|
|
+ let valueArr=[];
|
|
|
+ for(let m=0;m<arr.length;m++){
|
|
|
+ timeArr.push(Math.round(Date.parse(arr[m][0])/1000));
|
|
|
+ valueArr.push(arr[m][1]);
|
|
|
+ }
|
|
|
+
|
|
|
+ let minTime = Math.min(...timeArr);
|
|
|
+ let maxTime = Math.max(...timeArr);
|
|
|
+
|
|
|
+ let minValue = Math.min(...valueArr);
|
|
|
+ let maxValue = Math.max(...valueArr)
|
|
|
+
|
|
|
+ if(minTime == maxTime){
|
|
|
+ minTime = minTime-1;
|
|
|
+ maxTime = maxTime+1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(minValue == maxValue){
|
|
|
+ minValue = minValue-1;
|
|
|
+ maxValue = maxValue+1;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ //看是否有成功节点
|
|
|
+ if(successIndexList.length>0){
|
|
|
+ dataRange = {
|
|
|
+ start_time:minTime,
|
|
|
+ end_time:maxTime,
|
|
|
+ min_duration:minValue,
|
|
|
+ max_duration:maxValue,
|
|
|
+ failed:false,
|
|
|
+ app_alias:this.state.queryParams.app_alias,
|
|
|
+ service_name:this.props.id,
|
|
|
+ }
|
|
|
+ }else{
|
|
|
+ dataRange = {
|
|
|
+ start_time:minTime,
|
|
|
+ end_time:maxTime,
|
|
|
+ min_duration:minValue,
|
|
|
+ max_duration:maxValue,
|
|
|
+ failed:true,
|
|
|
+ app_alias:this.state.queryParams.app_alias,
|
|
|
+ service_name:this.props.id,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ let timeAndDuration = JSON.stringify(dataRange);
|
|
|
+
|
|
|
+ // let href = this.$router.resolve({
|
|
|
+ // path:'/latency/index',
|
|
|
+ // query:{
|
|
|
+ // data:timeAndDuration
|
|
|
+ // }
|
|
|
+ // })
|
|
|
+ // window.open(window.location.origin+"/"+href.href,"_blank")
|
|
|
+
|
|
|
+
|
|
|
+ let href = `${this.state.traceUrl}/#/latency/index?start_time=${dataRange.start_time}&end_time=${dataRange.end_time}&min_duration=${dataRange.min_duration}&max_duration=${dataRange.max_duration}&failed=${dataRange.failed}&app_alias=${this.state.queryParams.app_alias}&service_name=${this.props.id}`
|
|
|
+ window.open(href,"_blank")
|
|
|
+ this.timeoutId = setTimeout(()=>{
|
|
|
+ chart.dispatchAction({
|
|
|
+ type: 'brush',//选择action行为
|
|
|
+ areas:[]//areas表示选框的集合,此时为空即可。
|
|
|
+ });
|
|
|
+ },500)
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ });
|
|
|
+
|
|
|
+ window.addEventListener("resize",function (){
|
|
|
+ chart.resize();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ //获取折线图
|
|
|
+ // http://127.0.0.1:8000/api/v1/service/liveness?service_name={service_name}
|
|
|
+ getNodeLiveness(){
|
|
|
+ axios({
|
|
|
+ url: `${this.state.baseUrl}/api/v1/service/liveness`,
|
|
|
+ method: "get",
|
|
|
+ headers: { 'Authorization': getToken },
|
|
|
+ params: {
|
|
|
+ service_name:this.props.id,
|
|
|
+ start_time:this.state.queryParams.start_time,
|
|
|
+ end_time:this.state.queryParams.end_time,
|
|
|
+ }
|
|
|
+ }).then(res => {
|
|
|
+ if(res && res.data.code == 200){
|
|
|
+ const list = ((res || {}).data || {}).data || []
|
|
|
+ this.setState({
|
|
|
+ livenessData:[...list]
|
|
|
+ },()=>{
|
|
|
+ if(this.state.livenessData.length>0){
|
|
|
+ this.initLiveness(this.state.livenessData);
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ //渲染折线图
|
|
|
+ initLiveness(data){
|
|
|
+ let liveChart = echarts.getInstanceByDom(document.getElementById("box"));
|
|
|
+ if (liveChart == null) {
|
|
|
+ liveChart = echarts.init(document.getElementById("box"));
|
|
|
+ }else {
|
|
|
+ liveChart.dispose();
|
|
|
+ liveChart = echarts.init(document.getElementById("box"));
|
|
|
+ }
|
|
|
+ let option = {
|
|
|
+ grid: {
|
|
|
+ top:'5%',
|
|
|
+ left: '1%',
|
|
|
+ right: '1%',
|
|
|
+ bottom: '5%',
|
|
|
+ containLabel: true
|
|
|
+ },
|
|
|
+ tooltip: {
|
|
|
+ showDelay: 0,
|
|
|
+ formatter: function (params) {
|
|
|
+ let newParams = moment(params.data[0]).format('YYYY-MM-DD HH:mm:ss');
|
|
|
+ let time = newParams+"<br/>"+ params.data[1]+"ms"
|
|
|
+ return time;
|
|
|
+ },
|
|
|
+ axisPointer: {
|
|
|
+ show: true,
|
|
|
+ type: 'cross',
|
|
|
+ lineStyle: {
|
|
|
+ type: 'dashed',
|
|
|
+ width: 1
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'time',
|
|
|
+ boundaryGap: false,
|
|
|
+ // splitNumber: 3,
|
|
|
+ axisLabel: {
|
|
|
+ show:true,
|
|
|
+ rotate: 45, // 旋转标签,适用于标签较长的情况
|
|
|
+ interval: 'auto',
|
|
|
+ margin: 10, // 增加刻度标签间隔
|
|
|
+ textStyle: {
|
|
|
+ fontSize: 10,
|
|
|
+ textAlign:'center'
|
|
|
+ },
|
|
|
+ formatter: function(params) {
|
|
|
+ let newParams = moment(params).format('HH:mm:ss');
|
|
|
+ let time = newParams
|
|
|
+ return time;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ show: true
|
|
|
+ },
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value',
|
|
|
+ boundaryGap: [0, '30%']
|
|
|
+ },
|
|
|
+ visualMap: {
|
|
|
+ type: 'piecewise',
|
|
|
+ show: false,
|
|
|
+ dimension: 0,
|
|
|
+ seriesIndex: 0,
|
|
|
+ pieces: [
|
|
|
+ {
|
|
|
+ gt: 1,
|
|
|
+ lt: 3,
|
|
|
+ color: 'rgba(0, 0, 180, 0.4)'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ gt: 5,
|
|
|
+ lt: 7,
|
|
|
+ color: 'rgba(0, 0, 180, 0.4)'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ type: 'line',
|
|
|
+ smooth: 0.6,
|
|
|
+ symbol: 'none',
|
|
|
+ lineStyle: {
|
|
|
+ color: '#5470C6',
|
|
|
+ width: 1
|
|
|
+ },
|
|
|
+ markLine: {
|
|
|
+ symbol: ['none', 'none'],
|
|
|
+ label: { show: false },
|
|
|
+ data: [{ xAxis: 1 }, { xAxis: 3 }, { xAxis: 5 }, { xAxis: 7 }]
|
|
|
+ },
|
|
|
+ areaStyle: {},
|
|
|
+ data: data
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+ liveChart.setOption(option,true)
|
|
|
+ }
|
|
|
+
|
|
|
+ //获取异常trace列表 /api/v1/service/spans
|
|
|
+ getServiceSpans(){
|
|
|
+ this.setState({ loading: true });
|
|
|
+ this.setState({
|
|
|
+ traceData:[],
|
|
|
+ pagination:{total:0}
|
|
|
+ },()=>{
|
|
|
+ })
|
|
|
+ axios({
|
|
|
+ url: `${this.state.baseUrl}/api/v1/service/spans`,
|
|
|
+ method: "get",
|
|
|
+ headers: { 'Authorization': getToken },
|
|
|
+ params: {
|
|
|
+ service_name:this.props.id,
|
|
|
+ only_exception:this.state.traceQuery.only_exception, // 仅显示异常trace相关
|
|
|
+ only_database:this.state.traceQuery.only_database,
|
|
|
+ pageIndex:this.state.pagination.pageIndex,
|
|
|
+ pageSize:this.state.pagination.pageSize,
|
|
|
+ start_time:this.state.queryParams.start_time,
|
|
|
+ end_time:this.state.queryParams.end_time,
|
|
|
+ app_alias:this.state.queryParams.app_alias,
|
|
|
+ sort_field:'Timestamp',
|
|
|
+ sort_type: this.state.queryParams.sort_type
|
|
|
+ }
|
|
|
+ }).then(res => {
|
|
|
+
|
|
|
+ if(res && res.data.code == 200){
|
|
|
+ const list = (((res || {}).data || {}).data || {}).list || []
|
|
|
+ const total = (((res || {}).data || {}).data || {}).count || 0
|
|
|
+ this.setState({
|
|
|
+ traceData:[...list],
|
|
|
+ pagination:{...this.state.pagination,total:total}
|
|
|
+ },()=>{
|
|
|
+ })
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ //获取table 数据
|
|
|
+ getTableData(){
|
|
|
+ this.setState({ loading: true });
|
|
|
+ this.setState({
|
|
|
+ tableData:[],
|
|
|
+ pagination2:{total:0}
|
|
|
+ },()=>{
|
|
|
+ })
|
|
|
+ axios({
|
|
|
+ url: `${this.state.coreBaseUrl}/v1/system-component/reqlist`,
|
|
|
+ method: "get",
|
|
|
+ headers: { 'Authorization': getToken },
|
|
|
+ params: {
|
|
|
+ page_num:this.state.pagination2.page_num,
|
|
|
+ page_size:this.state.pagination2.page_size,
|
|
|
+ start_time:this.state.queryParams.start_time,
|
|
|
+ end_time:this.state.queryParams.end_time,
|
|
|
+ app_alias:this.state.queryParams.app_alias,
|
|
|
+ component: this.props.id//
|
|
|
+ }
|
|
|
+ }).then(res => {
|
|
|
+
|
|
|
+ if(res && res.data.code == 200){
|
|
|
+ const list = (((res || {}).data || {}).data || {}).list || []
|
|
|
+ const total = (((res || {}).data || {}).data || {}).total || 0
|
|
|
+ const page_num = (((res || {}).data || {}).data || {}).page_num || 1
|
|
|
+ const page_size = (((res || {}).data || {}).data || {}).page_size || 10
|
|
|
+ this.setState({
|
|
|
+ tableData:[...list],
|
|
|
+ pagination2:{page_num:page_num,page_size:page_size,total:total}
|
|
|
+ },()=>{
|
|
|
+ })
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ getBarData(){// 获取柱状图数据
|
|
|
+ this.setState({ loading: true });
|
|
|
+ this.setState({
|
|
|
+ tableData:[],
|
|
|
+ pagination2:{total:0}
|
|
|
+ },()=>{
|
|
|
+ })
|
|
|
+ axios({
|
|
|
+ url: `${this.state.coreBaseUrl}/v1/system-component/stats`,
|
|
|
+ method: "get",
|
|
|
+ headers: { 'Authorization': getToken },
|
|
|
+ params: {
|
|
|
+ start_time:this.state.queryParams.start_time,
|
|
|
+ end_time:this.state.queryParams.end_time,
|
|
|
+ app_alias:this.state.queryParams.app_alias,
|
|
|
+ component: this.props.id //
|
|
|
+ }
|
|
|
+ }).then(res => {
|
|
|
+ if(res && res.data.code == 200){
|
|
|
+ const list = res?.data?.data?.database_stats?.request_bar || []
|
|
|
+ this.setState({
|
|
|
+ barData:list
|
|
|
+ },()=>{
|
|
|
+ if(this.state.barData.length>0){
|
|
|
+ setTimeout(()=>{
|
|
|
+ this.initLineBar(this.state.barData);
|
|
|
+ },0)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // 渲染调用统计 折线/柱形图
|
|
|
+ initLineBar(data){
|
|
|
+ let liveChart = echarts.getInstanceByDom(document.getElementById("chartContent"));
|
|
|
+ let xAxisArr = data.map(item => item.start_time) || []
|
|
|
+ let dataArr = data.map(item => item.total) || []
|
|
|
+
|
|
|
+ if (liveChart == null) {
|
|
|
+ liveChart = echarts.init(document.getElementById("chartContent"));
|
|
|
+ } else {
|
|
|
+ liveChart.dispose();
|
|
|
+ liveChart = echarts.init(document.getElementById("chartContent"));
|
|
|
+ }
|
|
|
+ let option = {
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ data: xAxisArr,
|
|
|
+ axisLabel: {
|
|
|
+ show:true,
|
|
|
+ rotate: 45, // 旋转标签,适用于标签较长的情况
|
|
|
+ interval: 'auto',
|
|
|
+ margin: 10, // 增加刻度标签间隔
|
|
|
+ textStyle: {
|
|
|
+ fontSize: 10,
|
|
|
+ textAlign:'center'
|
|
|
+ },
|
|
|
+ formatter: function(params) {
|
|
|
+ let newParams = moment(params).format('HH:mm:ss');
|
|
|
+ let time = newParams
|
|
|
+ return time;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ },
|
|
|
+ tooltip: {
|
|
|
+ showDelay: 0,
|
|
|
+ axisPointer: {
|
|
|
+ show: true,
|
|
|
+ type: 'cross',
|
|
|
+ lineStyle: {
|
|
|
+ type: 'dashed',
|
|
|
+ width: 1
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ type: 'value'
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ data: dataArr,
|
|
|
+ type: 'bar'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+ liveChart.setOption(option,true)
|
|
|
+ }
|
|
|
+ handleTableChange=(pagination,filters, sorter, extra)=>{
|
|
|
+ const {current} = pagination
|
|
|
+ let sort = ''
|
|
|
+ if(sorter && sorter.order == 'ascend'){
|
|
|
+ sort = 'ASC'
|
|
|
+ } else if(sorter && sorter.order == 'descend'){
|
|
|
+ sort = 'DESC'
|
|
|
+ }
|
|
|
+
|
|
|
+ this.setState({
|
|
|
+ pagination: {...this.state.pagination,pageIndex:current,current:current},
|
|
|
+ queryParams:{...this.state.queryParams,sort_type:sort}
|
|
|
+ },()=>{
|
|
|
+ this.getServiceSpans();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ handleTableChange2=(pagination,filters, sorter, extra)=>{
|
|
|
+ const {current} = pagination
|
|
|
+ this.setState({
|
|
|
+ pagination2: {...this.state.pagination2,page_num:current}
|
|
|
+ },()=>{
|
|
|
+ this.getTableData();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ //仅异常
|
|
|
+ onChangeError = e =>{
|
|
|
+ const only_exception = e.target.checked ? 1:0;
|
|
|
+ const pageIndex = 1
|
|
|
+ const current = 1
|
|
|
+ this.setState({
|
|
|
+ traceQuery:{...this.state.traceQuery,only_exception:only_exception},
|
|
|
+ pagination: {...this.state.pagination,pageIndex:pageIndex,current:current},
|
|
|
+ },()=>{
|
|
|
+ this.getServiceSpans();
|
|
|
+ });
|
|
|
+
|
|
|
+ }
|
|
|
+ //仅sql
|
|
|
+ onChangeSql = e =>{
|
|
|
+ const only_database = e.target.checked ? 1:0;
|
|
|
+ const pageIndex = 1
|
|
|
+ const current = 1
|
|
|
+ this.setState({
|
|
|
+ traceQuery:{...this.state.traceQuery,only_database:only_database},
|
|
|
+ pagination: {...this.state.pagination,pageIndex:pageIndex,current:current},
|
|
|
+ },()=>{
|
|
|
+ this.getServiceSpans(this.props.source,this.props.target);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ //单选按钮
|
|
|
+ onChange = e => {
|
|
|
+ const percentile = e.target.value
|
|
|
+ this.setState({
|
|
|
+ queryParams:{...this.state.queryParams,percentile:percentile},
|
|
|
+ AnalystData:[]
|
|
|
+ },()=>{
|
|
|
+ this.getNodeAnalyst();
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ renderTable(table) {
|
|
|
+ const { nodeMatches = makeMap() } = this.props;
|
|
|
+
|
|
|
+ if (isGenericTable(table)) {
|
|
|
+ return (
|
|
|
+ <NodeDetailsGenericTable
|
|
|
+ rows={table.rows}
|
|
|
+ columns={table.columns}
|
|
|
+ matches={nodeMatches.get('tables')}
|
|
|
+ />
|
|
|
+ );
|
|
|
+ } if (isPropertyList(table)) {
|
|
|
+ return (
|
|
|
+ <NodeDetailsPropertyList
|
|
|
+ rows={table.rows}
|
|
|
+ controls={table.controls}
|
|
|
+ matches={nodeMatches.get('property-lists')}
|
|
|
+ />
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ log(`Undefined type '${table.type}' for table ${table.id}`);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ componentDidUpdate() {
|
|
|
+ this.updateTitle();
|
|
|
+ }
|
|
|
+
|
|
|
+ updateTitle() {
|
|
|
+ setDocumentTitle(this.props.details && this.props.details.label);
|
|
|
+ }
|
|
|
+
|
|
|
+ //dottedcylinder 类型 -获取基础信息
|
|
|
+ getDottBasicsData(){
|
|
|
+ this.setState({ loading: true });
|
|
|
+ this.setState({
|
|
|
+ messaging_stats:{
|
|
|
+ topic_stats:[]
|
|
|
+ }
|
|
|
+ },()=>{
|
|
|
+ })
|
|
|
+ axios({
|
|
|
+ url: `${this.state.coreBaseUrl}/v1/system-component/stats`,
|
|
|
+ method: "get",
|
|
|
+ headers: { 'Authorization': getToken },
|
|
|
+ params: {
|
|
|
+ start_time:this.state.queryParams.start_time,
|
|
|
+ end_time:this.state.queryParams.end_time,
|
|
|
+ app_alias:this.state.queryParams.app_alias,
|
|
|
+ component: this.props.id//
|
|
|
+ }
|
|
|
+ }).then(res => {
|
|
|
+
|
|
|
+ if(res && res.data.code == 200){
|
|
|
+ const messaging_stats = (((res || {}).data || {}).data || {}).messaging_stats || {}
|
|
|
+ this.setState({
|
|
|
+ messaging_stats:messaging_stats
|
|
|
+ },()=>{
|
|
|
+ })
|
|
|
+
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // cloud 类型-获取嵌套table
|
|
|
+ getCloudTableData(){// 获取柱状图数据
|
|
|
+ this.setState({ loading: true });
|
|
|
+ this.setState({
|
|
|
+ cloudTable:[],
|
|
|
+ cloudTable2:[],
|
|
|
+ },()=>{
|
|
|
+ })
|
|
|
+ axios({
|
|
|
+ url: `${this.state.coreBaseUrl}/v1/service/related-apps`,
|
|
|
+ method: "get",
|
|
|
+ headers: { 'Authorization': getToken },
|
|
|
+ params: {
|
|
|
+ start_time:this.state.queryParams.start_time,
|
|
|
+ end_time:this.state.queryParams.end_time,
|
|
|
+ app_alias:this.state.queryParams.app_alias,
|
|
|
+ type: this.props.id //
|
|
|
+ }
|
|
|
+ }).then(res => {
|
|
|
+ if(res && res.data.code == 200){
|
|
|
+ const list = (((res || {}).data ||{}).data || {}).app_list || []
|
|
|
+ const list2 = (((res ||{}).data ||{}).data ||{}).client_list || []
|
|
|
+
|
|
|
+ this.setState({
|
|
|
+ cloudTable:[...list],
|
|
|
+ cloudTable2: [...list2]
|
|
|
+ },()=>{
|
|
|
+ })
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+NodeDetails.propTypes = {
|
|
|
+ renderNodeDetailsExtras: PropTypes.func,
|
|
|
+};
|
|
|
+
|
|
|
+NodeDetails.defaultProps = {
|
|
|
+ renderNodeDetailsExtras: noop,
|
|
|
+};
|
|
|
+
|
|
|
+function mapStateToProps(state, ownProps) {
|
|
|
+ const currentTopologyId = state.get('currentTopologyId');
|
|
|
+ return {
|
|
|
+ nodeMatches: state.getIn(['searchNodeMatches', currentTopologyId, ownProps.id]),
|
|
|
+ nodes: state.get('nodes'),
|
|
|
+ selectedNodeId: state.get('selectedNodeId'),
|
|
|
+ transitioning: state.get('pausedAt') !== ownProps.timestamp,
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+export default connect(
|
|
|
+ mapStateToProps,
|
|
|
+ { clickCloseDetails, clickShowTopologyForNode }
|
|
|
+)(NodeDetails);
|