node-details.js 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824
  1. import debug from 'debug';
  2. import React from 'react';
  3. import classNames from 'classnames';
  4. import PropTypes from 'prop-types';
  5. import { connect } from 'react-redux';
  6. import { Map as makeMap } from 'immutable';
  7. import { noop } from 'lodash';
  8. import { clickCloseDetails, clickShowTopologyForNode } from '../actions/request-actions';
  9. import { brightenColor, getNeutralColor, getNodeColorDark,getStatusColor,setNodeColor } from '../utils/color-utils';
  10. import { isGenericTable, isPropertyList } from '../utils/node-details-utils';
  11. import { resetDocumentTitle, setDocumentTitle } from '../utils/title-utils';
  12. import Overlay from './overlay';
  13. import MatchedText from './matched-text';
  14. import NodeDetailsControls from './node-details/node-details-controls';
  15. import NodeDetailsGenericTable from './node-details/node-details-generic-table';
  16. import NodeDetailsPropertyList from './node-details/node-details-property-list';
  17. import NodeDetailsHealth from './node-details/node-details-health';
  18. import NodeDetailsRelatives from './node-details/node-details-relatives';
  19. import NodeDetailsTable from './node-details/node-details-table';
  20. import Warning from './warning';
  21. import * as echarts from 'echarts'
  22. import axios from 'axios'
  23. import moment from 'moment'
  24. import { Table,Radio,Checkbox,Button, Drawer, Descriptions } from "antd";
  25. import "antd/dist/antd.css";
  26. import '../../styles/nodeDetail.less'
  27. import getToken from '../utils/get-token'
  28. const log = debug('scope:node-details');
  29. function getTruncationText(count) {
  30. return 'This section was too long to be handled efficiently and has been truncated'
  31. + ` (${count} extra entries not included). We are working to remove this limitation.`;
  32. }
  33. class NodeDetails extends React.Component {
  34. constructor(props, context) {
  35. super(props, context);
  36. this.state = {
  37. // baseUrl:'http://observe-server.cestong.com.cn', //本地调试使用
  38. // traceUrl:'http://observe-front.cestong.com.cn', //本地调试使用
  39. // coreBaseUrl: 'http://observe-front.cestong.com.cn/core', //本地调试使用
  40. coreBaseUrl:'',//上线时打开
  41. baseUrl:'/re', //上线时打开
  42. traceUrl:'',//上线时打开
  43. nodeData:{}, //节点基础信息
  44. AnalystData:{},//散点图
  45. queryParams:{
  46. start_time:Math.round((new Date().getTime())/1000 - (5*60)),
  47. end_time:Math.round(new Date().getTime()/1000),
  48. app_alias:'opentelemetry-demo',
  49. service_name:'frontend',
  50. percentile:0.5,
  51. sort_field:'Timestamp',
  52. sort_type: 'DESC'
  53. },
  54. traceData:[],
  55. livenessData:[],
  56. barData:[],
  57. tableData: [],
  58. bgColor:setNodeColor('R'),
  59. pagination: {
  60. pageIndex:1,
  61. pageSize:10,
  62. total:0,
  63. current:1,
  64. },
  65. pagination2: {
  66. page_num:1,
  67. page_size:10,
  68. total:0,
  69. current:1,
  70. },
  71. traceQuery:{
  72. only_exception:0,
  73. only_database:0,
  74. },
  75. timeoutId: null,
  76. messaging_stats: {
  77. topic_stats:[]
  78. },
  79. cloudTable: [],
  80. cloudTable2: [],
  81. cloudPagination: {
  82. page_num:1,
  83. page_size:10,
  84. total:0,
  85. current:1,
  86. }
  87. }
  88. }
  89. handleClickClose = (ev) => {
  90. ev.preventDefault();
  91. this.props.clickCloseDetails(this.props.nodeId);
  92. }
  93. handleShowTopologyForNode = (ev) => {
  94. ev.preventDefault();
  95. this.props.clickShowTopologyForNode(this.props.topologyId, this.props.nodeId);
  96. }
  97. handleBeforeUnload = () => {
  98. // 在这里执行你需要的清理工作
  99. this.props.clickCloseDetails(this.props.nodeId);
  100. };
  101. componentDidMount() {
  102. let _this = this
  103. // //上线时打开开始
  104. const traceURL = `http://${parent.location.hostname}`
  105. const coreBaseURL = `http://${parent.location.hostname}/core`
  106. this.setState({
  107. traceUrl:traceURL,
  108. coreBaseUrl:coreBaseURL
  109. },()=>{
  110. })
  111. //上线时打开结束
  112. // 接受父组件参数
  113. // window.top == window true 自己本身没有被嵌套
  114. if(window.top !== window){// false 被嵌套
  115. const data = JSON.parse(parent.localStorage.topoGlobalTimes)
  116. const appsItem = JSON.parse(parent.localStorage.appsItem)
  117. const queryParams = _this.state.queryParams
  118. queryParams.start_time = data.startTime
  119. queryParams.end_time = data.endTime
  120. queryParams.app_alias = appsItem.alias
  121. _this.setState({
  122. queryParams: queryParams
  123. },()=>{
  124. _this.getMethodByshape(_this)
  125. });
  126. }
  127. console.log(88888888888,'componentDidMount')
  128. this.props.clickCloseDetails(this.props.nodeId);
  129. window.addEventListener('message', function(event) {
  130. // 处理接收到的消息
  131. const data = event.data
  132. console.log('globalTimesChange------data.data=====', data.data)
  133. const queryParams = _this.state.queryParams
  134. if (data.eventType == 'globalTimesChange') {
  135. queryParams.start_time = data.data.startTime
  136. queryParams.end_time = data.data.endTime
  137. _this.setState({
  138. queryParams: queryParams
  139. },()=>{
  140. _this.getMethodByshape(_this)
  141. });
  142. }
  143. }, false);
  144. window.addEventListener('storage', function(event) {
  145. if ( event.key == 'topoGlobalTimes') {
  146. // 这里处理父项目 localStorage 值变化的情况
  147. const data = JSON.parse(event.newValue)
  148. console.log('JSON.parse(------', data)
  149. const queryParams = _this.state.queryParams
  150. queryParams.start_time = data.startTime
  151. queryParams.end_time = data.endTime
  152. _this.setState({
  153. queryParams: queryParams
  154. },()=>{
  155. setTimeout(()=>{
  156. _this.getMethodByshape(_this)
  157. },0)
  158. })
  159. }
  160. });
  161. }
  162. // setQueryParams(){
  163. // var strr = parent.location.href; //上线做为iframe嵌套时使用
  164. // let param = this.parseQueryString(strr); //全链路需要的参数多,因此解析成对象形式
  165. // const queryParams = this.state.queryParams;
  166. // const newAppAlias = param.app_alias
  167. // queryParams.app_alias = newAppAlias
  168. // this.setState({
  169. // queryParams: queryParams
  170. // },()=>{
  171. // this.getMethodByshape(this)
  172. // });
  173. // }
  174. getMethodByshape(_this){
  175. if (_this.props.shape == 'cylinder'){
  176. _this.getTableData(); //table
  177. setTimeout(()=>{
  178. _this.getBarData(); // 柱形图
  179. }, 0)
  180. }
  181. if (_this.props.shape == 'dottedcylinder'){
  182. _this.getDottBasicsData() // dottedcylinder 基础信息
  183. }
  184. if (_this.props.shape == 'circle'){
  185. _this.getNodeBasic();
  186. setTimeout(()=>{
  187. _this.getNodeAnalyst();//获取散点图
  188. _this.getNodeLiveness(); // 折线图
  189. },0)
  190. _this.getServiceSpans();
  191. }
  192. if (_this.props.shape == 'cloud'){
  193. _this.getCloudTableData()
  194. }
  195. }
  196. //解析URL
  197. parseQueryString(url){
  198. var json = {};
  199. var arr = url.substr(url.indexOf('?') + 1).split('&');
  200. arr.forEach(item=>{
  201. var tmp = item.split('=');
  202. json[tmp[0]] = tmp[1];
  203. });
  204. return json;
  205. }
  206. // ui test
  207. componentWillMount(){
  208. window.addEventListener('beforeunload',this.handleBeforeUnload)
  209. }
  210. componentWillUnmount() {
  211. console.log(2222222222222,'componentWillUnmount')
  212. window.removeEventListener('beforeunload', this.handleBeforeUnload);
  213. clearTimeout(this.timeoutId);
  214. resetDocumentTitle();
  215. }
  216. renderTools() {
  217. const showSwitchTopology = this.props.nodeId !== this.props.selectedNodeId;
  218. const topologyTitle = `View ${this.props.label} in ${this.props.topologyId}`;
  219. return (
  220. <div className="node-details-tools-wrapper">
  221. <div className="node-details-tools">
  222. {showSwitchTopology
  223. && (
  224. <i
  225. title={topologyTitle}
  226. className="fa fa-long-arrow-alt-left"
  227. onClick={this.handleShowTopologyForNode}>
  228. <span>
  229. Show in
  230. {/* <span>{this.props.topologyId.replace(/-/g, ' ')}</span> */}
  231. </span>
  232. </i>
  233. )
  234. }
  235. <i
  236. title="Close details"
  237. className="fa fa-times close-details"
  238. onClick={this.handleClickClose}
  239. />
  240. </div>
  241. </div>
  242. );
  243. }
  244. renderLoading() {
  245. const node = this.props.nodes.get(this.props.nodeId);
  246. const label = node ? node.get('label') : this.props.label;
  247. // NOTE: If we start the fa-spin animation before the node details panel has been
  248. // mounted, the spinner is displayed blurred the whole time in Chrome (possibly
  249. // caused by a bug having to do with animating the details panel).
  250. const spinnerClassName = classNames('fa fa-circle-notch', { 'fa-spin': this.props.mounted });
  251. const nodeColor = (node
  252. ? getNodeColorDark(node.get('rank'), label, node.get('pseudo'))
  253. : getNeutralColor());
  254. const tools = this.renderTools();
  255. const styles = {
  256. header: {
  257. backgroundColor: nodeColor
  258. }
  259. };
  260. return (
  261. <div className="node-details">
  262. {tools}
  263. <div className="node-details-header" style={styles.header}>
  264. <div className="node-details-header-wrapper">
  265. <h2 className="node-details-header-label truncate">
  266. {label}
  267. </h2>
  268. <div className="node-details-relatives truncate">
  269. Loading...
  270. </div>
  271. </div>
  272. </div>
  273. <div className="node-details-content" style="padding:0 12px">
  274. <div className="node-details-content-loading">
  275. <span className={spinnerClassName} />
  276. </div>
  277. </div>
  278. </div>
  279. );
  280. }
  281. renderNotAvailable() {
  282. const tools = this.renderTools();
  283. return (
  284. <div className="node-details">
  285. {tools}
  286. <div className="node-details-header node-details-header-notavailable">
  287. <div className="node-details-header-wrapper">
  288. <h2 className="node-details-header-label">
  289. {this.props.label}
  290. </h2>
  291. <div className="node-details-relatives truncate">
  292. n/a
  293. </div>
  294. </div>
  295. </div>
  296. <div className="node-details-content">
  297. <p className="node-details-content-info">
  298. <strong>{this.props.label}</strong>
  299. {' '}
  300. not found!
  301. </p>
  302. </div>
  303. <Overlay faded={this.props.transitioning} />
  304. </div>
  305. );
  306. }
  307. render() {
  308. // if (this.props.notFound) {
  309. // return this.renderNotAvailable();
  310. // }
  311. // if (this.props.details) {
  312. // return this.renderDetails();
  313. // }
  314. // return this.renderLoading();
  315. return this.renderNodeDetails();
  316. }
  317. renderDetails() {
  318. const {
  319. details, nodeControlStatus, nodeMatches = makeMap(), topologyId
  320. } = this.props;
  321. const showControls = details.controls && details.controls.length > 0;
  322. const nodeColor = getNodeColorDark(details.rank, details.label, details.pseudo);
  323. // const nodeColor= setNodeColor(details.color)
  324. const {error, pending} = nodeControlStatus ? nodeControlStatus.toJS() : {};
  325. const tools = this.renderTools();
  326. const styles = {
  327. controls: {
  328. backgroundColor: brightenColor(nodeColor)
  329. },
  330. header: {
  331. backgroundColor: nodeColor
  332. }
  333. };
  334. return (
  335. <div className="tour-step-anchor node-details">
  336. {tools}
  337. <div className="node-details-header" style={styles.header}>
  338. <div className="node-details-header-wrapper">
  339. <h2 className="node-details-header-label truncate" title={details.label}>
  340. <MatchedText text={details.label} match={nodeMatches.get('label')} />
  341. </h2>
  342. <div className="node-details-header-relatives">
  343. {details.parents && (
  344. <NodeDetailsRelatives
  345. matches={nodeMatches.get('parents')}
  346. relatives={details.parents} />
  347. )}
  348. </div>
  349. </div>
  350. </div>
  351. {showControls
  352. && (
  353. <div className="tour-step-anchor node-details-controls-wrapper" style={styles.controls}>
  354. <NodeDetailsControls
  355. nodeId={this.props.nodeId}
  356. controls={details.controls}
  357. pending={pending}
  358. error={error} />
  359. </div>
  360. )
  361. }
  362. <div className="node-details-content">
  363. {details.metrics
  364. && (
  365. <div className="node-details-content-section">
  366. <div className="node-details-content-section-header">Status</div>
  367. <NodeDetailsHealth
  368. metrics={details.metrics}
  369. topologyId={topologyId}
  370. />
  371. </div>
  372. )
  373. }
  374. {details.metadata
  375. && (
  376. <div className="node-details-content-section">
  377. <div className="node-details-content-section-header">Info</div>
  378. <NodeDetailsInfo rows={details.metadata} matches={nodeMatches.get('metadata')} />
  379. </div>
  380. )
  381. }
  382. {details.connections && details.connections.filter(cs => cs.connections.length > 0)
  383. .map(connections => (
  384. <div className="node-details-content-section" key={connections.id}>
  385. <NodeDetailsTable
  386. {...connections}
  387. nodes={connections.connections}
  388. nodeIdKey="nodeId"
  389. />
  390. </div>
  391. ))}
  392. {details.children && details.children.map(children => (
  393. <div className="node-details-content-section" key={children.topologyId}>
  394. <NodeDetailsTable {...children} />
  395. </div>
  396. ))}
  397. {details.tables && details.tables.length > 0 && details.tables.map((table) => {
  398. if (table.rows.length > 0) {
  399. return (
  400. <div className="node-details-content-section" key={table.id}>
  401. <div className="node-details-content-section-header">
  402. {table.label && table.label.length > 0 && table.label}
  403. {table.truncationCount > 0
  404. && (
  405. <span
  406. className="node-details-content-section-header-warning">
  407. <Warning text={getTruncationText(table.truncationCount)} />
  408. </span>
  409. )
  410. }
  411. </div>
  412. {this.renderTable(table)}
  413. </div>
  414. );
  415. }
  416. return null;
  417. })}
  418. {this.props.renderNodeDetailsExtras({ details, topologyId })}
  419. </div>
  420. <Overlay faded={this.props.transitioning} />
  421. </div>
  422. );
  423. }
  424. renderNodeDetails(){
  425. const {
  426. details, nodeControlStatus, nodeMatches = makeMap(), topologyId
  427. } = this.props;
  428. const node = this.props.nodes.get(this.props.nodeId);
  429. const label = node ? node.get('label') : this.props.label;
  430. // const nodeColor = getNodeColorDark(details.rank, details.label, details.pseudo);
  431. // const nodeColor= setNodeColor(details.color)
  432. const {error, pending} = nodeControlStatus ? nodeControlStatus.toJS() : {};
  433. const tools = this.renderTools();
  434. const styles = {
  435. controls: {
  436. // backgroundColor: brightenColor(nodeColor)
  437. },
  438. header: {
  439. backgroundColor:this.state.bgColor
  440. }
  441. };
  442. const columns = [
  443. {
  444. title: 'TraceID',
  445. dataIndex: 'trace_id',
  446. key: 'trace_id',
  447. width:'20%',
  448. ellipsis:true,
  449. align:'center',
  450. // scopedSlots:{customRender:'trace_id'},
  451. 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>
  452. },
  453. {
  454. title: '方法',
  455. dataIndex: 'method',
  456. key: 'method',
  457. width:'15%',
  458. ellipsis:true,
  459. align:'center'
  460. },
  461. {
  462. title: '状态码',
  463. dataIndex: 'code',
  464. key: 'code',
  465. width:'15%',
  466. ellipsis:true,
  467. align:'center'
  468. },
  469. {
  470. title: '请求时长(ms)',
  471. dataIndex: 'duration',
  472. key: 'duration',
  473. width:'30%',
  474. ellipsis:true,
  475. align:'center',
  476. render:(text) => <span>{(text && Number(text.toFixed(2))) || 0 }</span>
  477. },
  478. {
  479. title: '日期',
  480. dataIndex: 'datetime',
  481. key: 'datetime',
  482. width:'30%',
  483. ellipsis:true,
  484. align:'center',
  485. defaultSortOrder: 'descend',
  486. sorter:true
  487. },
  488. ]
  489. const colums2 = [
  490. {
  491. title: '服务名',
  492. dataIndex: 'service_name',
  493. key: 'service_name',
  494. ellipsis:true,
  495. align:'center',
  496. render: (text, record) => (
  497. <span>{record.service_name_cn? record.service_name_cn: record.service_name}</span>
  498. )
  499. },
  500. {
  501. title: '执行语句',
  502. dataIndex: 'query',
  503. key: 'query',
  504. width:'40%',
  505. ellipsis:true,
  506. align:'center',
  507. render: (text, record) => (
  508. <textarea value={record.query} disabled rows="2" />
  509. )
  510. },
  511. {
  512. title: '慢查询次数',
  513. dataIndex: 'slow_num',
  514. key: 'slow_num',
  515. ellipsis:true,
  516. align:'center'
  517. },
  518. {
  519. title: '错误数量',
  520. dataIndex: 'error_num',
  521. key: 'error_num',
  522. ellipsis:true,
  523. align:'center'
  524. }
  525. ]
  526. const cloudColums = [
  527. {
  528. title: '应用名称',
  529. dataIndex: 'app_name',
  530. align:'center',
  531. width: 200,
  532. key: 'app_name'
  533. },
  534. {
  535. title: '请求总数',
  536. width: 100,
  537. dataIndex: 'request_total',
  538. align:'center',
  539. key: 'request_total',
  540. },
  541. {
  542. title: '错误数',
  543. dataIndex: 'error_num',
  544. align:'center',
  545. width: 130,
  546. key: 'error_num',
  547. },
  548. {
  549. title: '平均延迟',
  550. dataIndex: 'duration_average',
  551. align:'center',
  552. key: 'duration_average',
  553. width: 130,
  554. render: (text, record) => (
  555. <span>{(record.duration_average && Number(record.duration_average).toFixed(2)) || 0}</span>
  556. )
  557. },
  558. {
  559. title: '中位延迟',
  560. dataIndex: 'duration_median',
  561. align:'center',
  562. width: 140,
  563. key: 'duration_median',
  564. render: (text, record) => (
  565. <span>{(record.duration_median && Number(record.duration_median).toFixed(2)) || 0 }</span>
  566. )
  567. }
  568. ];
  569. const cloudColums2 = [
  570. {
  571. title: '应用名称',
  572. align:'center',
  573. dataIndex: 'name',
  574. width: 210,
  575. key: 'name'
  576. },
  577. {
  578. title: '请求总数',
  579. align:'center',
  580. width: 100,
  581. dataIndex: 'request_total',
  582. key: 'request_total',
  583. },
  584. {
  585. title: '错误数',
  586. align:'center',
  587. width: 140,
  588. dataIndex: 'error_num',
  589. key: 'error_num',
  590. },
  591. {
  592. title: '平均延迟',
  593. dataIndex: 'duration_average',
  594. align:'center',
  595. width: 130,
  596. key: 'duration_average',
  597. render: (text, record) => (
  598. <span>{(record.duration_average && Number(record.duration_average).toFixed(2)) || 0}</span>
  599. )
  600. },
  601. {
  602. title: '中位延迟',
  603. dataIndex: 'duration_median',
  604. align:'center',
  605. width: 140,
  606. key: 'duration_median',
  607. render: (text, record) => (
  608. <span>{(record.duration_median && Number(record.duration_median).toFixed(2)) || 0 }</span>
  609. )
  610. }
  611. ];
  612. const expandedRowRender = (record) => {
  613. const columns = [
  614. {
  615. title: '服务名',
  616. dataIndex: 'service_name_cn',
  617. align:'center',
  618. width: 180,
  619. key: 'service_name_cn',
  620. },
  621. {
  622. title: '请求总数',
  623. dataIndex: 'request_total',
  624. align:'center',
  625. width: 114,
  626. key: 'request_total',
  627. },
  628. {
  629. title: '错误数',
  630. dataIndex: 'error_num',
  631. align:'center',
  632. width: 120,
  633. key: 'error_num',
  634. },
  635. {
  636. title: '平均延迟',
  637. dataIndex: 'duration_average',
  638. key: 'duration_average',
  639. align:'center',
  640. width: 130,
  641. render: (text, record) => (
  642. <span>{(record.duration_average && Number(record.duration_average).toFixed(2)) ||0 }</span>
  643. )
  644. },
  645. {
  646. title: '中位延迟',
  647. dataIndex: 'duration_median',
  648. key: 'duration_median',
  649. align:'center',
  650. width: 130,
  651. render: (text, record) => (
  652. <span>{(record.duration_median && Number(record.duration_median).toFixed(2)) || 0 }</span>
  653. )
  654. }
  655. ];
  656. return <Table columns={columns} rowKey={'service_name'} dataSource={record.service_list} pagination={false} size='small' />;
  657. };
  658. return (
  659. <Drawer placement="right"
  660. onClose={this.handleClickClose} visible={true}
  661. destroyOnClose={true}
  662. width="65%">
  663. <div className="tour-step-anchor node-details">
  664. {tools}
  665. <div className="node-details-header" style={styles.header}>
  666. <div className="node-details-header-wrapper">
  667. <h2 className="node-details-header-label truncate">
  668. {label}
  669. </h2>
  670. <div className="node-details-header-relatives">
  671. {this.state.nodeData.subtitle}
  672. </div>
  673. </div>
  674. </div>
  675. {/* {showControls
  676. && (
  677. <div className="tour-step-anchor node-details-controls-wrapper" style={styles.controls}>
  678. <NodeDetailsControls
  679. nodeId={this.props.nodeId}
  680. controls={details.controls}
  681. pending={pending}
  682. error={error} />
  683. </div>
  684. )
  685. } */}
  686. {
  687. this.props.shape == 'cylinder'&&
  688. <div className="node-details-content" >
  689. <div style={{marginTop:'16px'}}>
  690. <div className="node-details-content-section-header" style={{marginBottom:0}}>执行次数</div>
  691. <div id="chartContent" style={{width: '100%', height: '300px', borderRadius: '12px'}}></div>
  692. <div style={{paddingBottom:'20px'}}>
  693. <Table dataSource={this.state.tableData} columns={colums2} rowKey={(record, index) => `${record.service_name}${index}`} pagination={this.state.pagination2} onChange={this.handleTableChange2} size='small' >
  694. </Table>
  695. </div>
  696. </div>
  697. </div>
  698. }
  699. { this.props.shape == 'circle'&&
  700. (<div className="node-details-content">
  701. <div>
  702. <div className="node-details-content-section">
  703. <div className="node-details-content-section-header">基本信息</div>
  704. <div className='node-details-info'>
  705. <div style={{textAlign:'right'}}>
  706. <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=${this.props.id}&isShowBackBtn=false`}>服务详情</a></Button>
  707. </div>
  708. </div>
  709. </div>
  710. {/* https://zhongdian.feishu.cn/docx/HkXSdrGa2ou84zxPvvycGX5Fn3b 中让去掉的 */}
  711. {/* <div className="node-details-content-section">
  712. <div className="node-details-content-section-header">状态</div>
  713. <div>
  714. <div className='node-details-info-field'>
  715. <div className='node-details-info-field-label truncate w50' style={{width:"50%"}}>可用性</div>
  716. <div className='node-details-info-field-value truncate w50' style={{width:"50%"}}>
  717. {this.state.nodeData.apdex?Math.floor(this.state.nodeData.apdex*100):0}
  718. </div>
  719. </div>
  720. <div className='node-details-info-field'>
  721. <div className='node-details-info-field-label truncate w50' style={{width:"50%"}}>成功率</div>
  722. <div className='node-details-info-field-value truncate w50' style={{width:"50%"}}>{this.state.nodeData.arc__success?Number(this.state.nodeData.arc__success*100).toFixed(2):0}%</div>
  723. </div>
  724. <div className='node-details-info-field'>
  725. <div className='node-details-info-field-label truncate w50' style={{width:"50%"}}>失败率</div>
  726. <div className='node-details-info-field-value truncate w50' style={{width:"50%"}}>{this.state.nodeData.arc__faild?Number(this.state.nodeData.arc__faild*100).toFixed(2):0}%</div>
  727. </div>
  728. <div className='node-details-info-field'>
  729. <div className='node-details-info-field-label truncate w50' style={{width:"50%"}}>接收数量</div>
  730. <div className='node-details-info-field-value truncate w50' style={{width:"50%"}}>{this.state.nodeData.receive?this.state.nodeData.receive:0}</div>
  731. </div>
  732. <div className='node-details-info-field'>
  733. <div className='node-details-info-field-label truncate w50' style={{width:"50%"}}>发送数量</div>
  734. <div className='node-details-info-field-value truncate w50' style={{width:"50%"}}>{this.state.nodeData.send?this.state.nodeData.send:0}</div>
  735. </div>
  736. </div>
  737. </div> */}
  738. <div className="node-details-content-section">
  739. <div className="node-details-content-section-header">调用次数</div>
  740. <div className='node-details-info'>
  741. {
  742. <div>
  743. <div id='box' className="echartsbox"></div>
  744. </div>
  745. }
  746. </div>
  747. </div>
  748. <div className="node-details-content-section">
  749. <div className="node-details-content-section-header">延迟比例</div>
  750. <div className='node-details-info' style={{marginTop: "-15px",position:'relative'}}>
  751. {
  752. <div>
  753. <div id='main' className="echartsbox" style={{height:'240px'}}></div>
  754. </div>
  755. }
  756. <div className='LatencySelect'>
  757. <Radio.Group onChange={this.onChange} value={this.state.queryParams.percentile} size="small">
  758. <Radio value={0.5}>50分位</Radio>
  759. {/* <Radio value={0.95}>p.95</Radio> */}
  760. <Radio value={0.99}>99分位</Radio>
  761. </Radio.Group>
  762. </div>
  763. </div>
  764. </div>
  765. <div className='node-details-content-section'>
  766. <div className="node-details-content-section-header">链路列表</div>
  767. <div className='node-serch'>
  768. <Checkbox onChange={this.onChangeError}>仅异常</Checkbox>
  769. <Checkbox onChange={this.onChangeSql}>仅SQL</Checkbox>
  770. </div>
  771. <Table dataSource={this.state.traceData} columns={columns} rowKey={(record, index) => `${record.span_id}${index}`} pagination={this.state.pagination} onChange={this.handleTableChange} size='small'>
  772. {/* <span slot='trace_id' slot-scope='text,record'>
  773. <template>
  774. <div>
  775. <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>
  776. </div>
  777. </template>
  778. </span> */}
  779. </Table>
  780. </div>
  781. </div>
  782. </div>)
  783. }
  784. {
  785. this.props.shape == 'dottedcylinder' && (
  786. <div className='ant-descriptions-header'>
  787. <Descriptions title="" >
  788. {/* <Descriptions.Item label="系统名称">{this.state.messaging_stats.name}</Descriptions.Item> */}
  789. <Descriptions.Item label="生产消息数量">{this.state.messaging_stats.produce_num || 0}</Descriptions.Item>
  790. <Descriptions.Item label="消费消息数量">{this.state.messaging_stats.consume_num || 0}</Descriptions.Item>
  791. <Descriptions.Item label="生产消息错误率">{this.state.messaging_stats.produce_error_rate?Number(this.state.messaging_stats.produce_error_rate*100).toFixed(2):0}</Descriptions.Item>
  792. <Descriptions.Item label="消费消息错误率">{this.state.messaging_stats.consume_error_rate?Number(this.state.messaging_stats.consume_error_rate*100).toFixed(2):0}</Descriptions.Item>
  793. <Descriptions.Item label="生产消息平均耗时">{this.state.messaging_stats.produce_duration_average?Number(this.state.messaging_stats.produce_duration_average).toFixed(2):0}</Descriptions.Item>
  794. <Descriptions.Item label="消费消息耗时">{this.state.messaging_stats.consume_duration_average?Number(this.state.messaging_stats.consume_duration_average).toFixed(2):0}</Descriptions.Item>
  795. <Descriptions.Item label="平均消息大小">{this.state.messaging_stats.message_size_average?Number(this.state.messaging_stats.message_size_average).toFixed(2):0}</Descriptions.Item>
  796. <Descriptions.Item label="主题数量">{this.state.messaging_stats.topic_num || 0}</Descriptions.Item>
  797. </Descriptions>
  798. <div className="bisic-title">主题统计信息</div>
  799. <div>
  800. {this.state.messaging_stats && this.state.messaging_stats.topic_stats && this.state.messaging_stats.topic_stats.length > 0 && this.state.messaging_stats.topic_stats.map((item, index)=>{
  801. return <Descriptions key={index+item.name} >
  802. <Descriptions.Item label="主题名称" >{item.name || '--'}</Descriptions.Item>
  803. <Descriptions.Item label="生产消息数量">{item.produce_num || 0}</Descriptions.Item>
  804. <Descriptions.Item label="消费消息数量">{item.consume_num || 0}</Descriptions.Item>
  805. <Descriptions.Item label="生产消息错误率">{item.produce_error_rate ? Number(item.produce_error_rate*100).toFixed(2)+'%' :0}</Descriptions.Item>
  806. <Descriptions.Item label="消费消息错误率">{item.consume_error_rate?Number(item.consume_error_rate*100).toFixed(2)+ '%' :0}</Descriptions.Item>
  807. <Descriptions.Item label="生产消息平均耗时">{item.produce_duration_average?Number(item.produce_duration_average).toFixed(2):0}</Descriptions.Item>
  808. <Descriptions.Item label="消费消息耗时">{item.consume_duration_average?Number(item.consume_duration_average).toFixed(2):0}</Descriptions.Item>
  809. <Descriptions.Item label="平均消息大小">{item.message_size_average?Number(item.message_size_average).toFixed(2):0}</Descriptions.Item>
  810. </Descriptions>
  811. })}
  812. </div>
  813. </div>
  814. )
  815. }
  816. {
  817. this.props.shape == 'cloud' && (
  818. <div className="node-details-content" >
  819. <div style={{marginTop:'30px'}}>
  820. <div style={{width:'100%'}}>
  821. <h4>已知应用</h4>
  822. <Table dataSource={this.state.cloudTable}
  823. columns={cloudColums}
  824. rowKey={'app_alias'}
  825. expandedRowRender={(record) => expandedRowRender(record)}
  826. size='small' bordered pagination={false}>
  827. </Table>
  828. </div>
  829. <div style={{marginTop:'40px'}}>
  830. <h4>未知应用</h4>
  831. <div style={{paddingLeft: '80px'}}>
  832. <Table dataSource={this.state.cloudTable2}
  833. columns={cloudColums2}
  834. rowKey={`name`} size='small' bordered pagination={false}>
  835. </Table>
  836. </div>
  837. </div>
  838. </div>
  839. </div>)
  840. }
  841. </div>
  842. </Drawer>
  843. );
  844. }
  845. //获取基础信息
  846. getNodeBasic(){
  847. axios({
  848. url: `${this.state.baseUrl}/api/v1/apps_score/${this.state.queryParams.app_alias}/svr`,
  849. method: "get",
  850. headers: { 'Authorization': getToken },
  851. params: {
  852. source_service:this.props.id,
  853. start_time:this.state.queryParams.start_time,
  854. end_time:this.state.queryParams.end_time
  855. }
  856. }).then(res => {
  857. if(res && res.data.code == 200){
  858. const newObj= ((res ||{}).data || {}).data || {}
  859. this.setState({
  860. nodeData:{...newObj}
  861. },()=>{
  862. if(JSON.stringify(this.state.nodeData)!="{}"){
  863. this.state.nodeData.apdex>=0.94?this.setState({bgColor:setNodeColor('G')})
  864. :(this.state.nodeData.apdex>=0.85&&this.state.nodeData.apdex<0.94)?this.setState({bgColor:setNodeColor('B')})
  865. :(this.state.nodeData.apdex>=0.7&&this.state.nodeData.apdex<0.85)?this.setState({bgColor:setNodeColor('DI')})
  866. :(this.state.nodeData.apdex>=0.5&&this.state.nodeData.apdex<0.7)?this.setState({bgColor:setNodeColor('Y')})
  867. :this.setState({bgColor:setNodeColor('R')})
  868. }else{
  869. this.setState({bgColor:setNodeColor('R')})
  870. }
  871. })
  872. }
  873. });
  874. }
  875. //获取散点图--延迟比例
  876. getNodeAnalyst(){
  877. let chart = echarts.getInstanceByDom(document.getElementById("main"));
  878. if (chart == null) {
  879. chart = echarts.init(document.getElementById("main"));
  880. }else {
  881. chart.dispose();
  882. chart = echarts.init(document.getElementById("main"));
  883. }
  884. // 显示loading动画
  885. chart.showLoading(
  886. {
  887. text: ''
  888. }
  889. );
  890. axios({
  891. url: `${this.state.baseUrl}/api/v1/app/analyst/${this.state.queryParams.app_alias}/svr`,
  892. method: "get",
  893. headers: { 'Authorization': getToken },
  894. params: {
  895. source_service:this.props.id,
  896. start_time:this.state.queryParams.start_time,
  897. end_time:this.state.queryParams.end_time,
  898. percentile:this.state.queryParams.percentile
  899. }
  900. }).then(res => {
  901. chart.hideLoading();
  902. if(res && res.data.code == 200){
  903. const obj = ((res || {}).data ||{}).data || {}
  904. this.setState({
  905. AnalystData:{...obj}
  906. },()=>{
  907. if(JSON.stringify(this.state.AnalystData)!="{}"){
  908. this.initChart(this.state.AnalystData, chart)
  909. }
  910. })
  911. }
  912. });
  913. }
  914. // 散点图渲染
  915. initChart(tmpData, chart) {
  916. let option = {}
  917. if (tmpData?.success || tmpData?.failed){
  918. let successData = tmpData.success && tmpData.success.map(item => new Date(item[0]).toLocaleString()) || []
  919. let failedData = tmpData.failed && tmpData.failed.map(item => new Date(item[0]).toLocaleString()) || []
  920. option = {
  921. title: {
  922. text: '',
  923. subtext: '',
  924. textStyle:{
  925. fontSize:14
  926. },
  927. },
  928. grid: {
  929. top:'8%',
  930. left: '3%',
  931. right: '7%',
  932. bottom: '14%',
  933. containLabel: true
  934. },
  935. tooltip: {
  936. showDelay: 0,
  937. formatter: function (params) {
  938. let newParams = moment(params.data[0]).format('YYYY-MM-DD HH:mm:ss');
  939. let time = newParams+"<br/>"+ '延迟:' + params.data[1]+"ms"
  940. return time;
  941. }
  942. },
  943. toolbox: {
  944. show:true,
  945. showTitle: true,
  946. feature: {
  947. rect: {
  948. show: true,
  949. title: 'Trace选择'
  950. },
  951. brush: {
  952. type: ["rect"], // 开启矩形选择
  953. show: true,//是否显示 这里我们直接true
  954. iconStyle: {
  955. opacity: 0,//通过opacity设置为0隐藏图标
  956. },
  957. }
  958. },
  959. left:"40%", //组件离容器左侧的距离,'left', 'center', 'right','20%'
  960. top:"-3%", //组件离容器上侧的距离,'top', 'middle', 'bottom','20%'
  961. right:"auto", //组件离容器右侧的距离,'20%'
  962. bottom:"auto",
  963. },
  964. brush: {
  965. toolbox: ['rect'],
  966. brushLink: 'all', // 设置brush时同时选中x轴和y轴的数据
  967. throttleType:'debounce',
  968. throttleDelay:600
  969. },
  970. legend: {
  971. data: ['成功', '失败'],
  972. left: 'center',
  973. bottom: 0,
  974. itemGap: 100,
  975. textStyle: {//文字颜色
  976. fontSize: 12,
  977. padding:[0,3],//文字与图形之间的左右间距
  978. rich:{
  979. labelName:{
  980. fontSize:14,
  981. color:'#333',
  982. fontWeight:500
  983. }
  984. }
  985. },
  986. formatter: function (params) {
  987. // 获取legend显示内容
  988. let data = tmpData;
  989. let sl,fl;
  990. if(data.success!=null){
  991. sl = tmpData.success.length;
  992. }else{
  993. sl = 0
  994. }
  995. if( data.failed!=null){
  996. fl = tmpData.failed.length;
  997. }else{
  998. fl = 0
  999. }
  1000. var target;
  1001. if(params == '成功'){
  1002. target = sl;
  1003. }else if(params == '失败'){
  1004. target = fl;
  1005. }
  1006. return target != undefined?params +' '+`{labelName|${target}}`:params
  1007. },
  1008. },
  1009. xAxis: [
  1010. {
  1011. type: 'time',
  1012. data: [
  1013. ...successData,
  1014. ...failedData
  1015. ],
  1016. gridIndex:0,
  1017. axisLabel: {
  1018. show:true,
  1019. rotate: 45, // 旋转标签,适用于标签较长的情况
  1020. interval: 'auto',
  1021. margin: 10, // 增加刻度标签间隔
  1022. textStyle: {
  1023. fontSize: 10,
  1024. textAlign:'center'
  1025. },
  1026. formatter: function(params) {
  1027. let newParams = moment(params).format('HH:mm:ss');
  1028. let time = newParams
  1029. return time;
  1030. }
  1031. },
  1032. splitLine: {
  1033. show: true
  1034. },
  1035. }
  1036. ],
  1037. yAxis: [
  1038. {
  1039. type: 'value',
  1040. // scale: true,
  1041. gridIndex:0,
  1042. boundaryGap: [0, '30%'],
  1043. axisLabel: {
  1044. formatter: '{value}'
  1045. },
  1046. axisLine:{
  1047. show:true
  1048. },
  1049. axisTick:{
  1050. show:true
  1051. },
  1052. splitLine: {
  1053. show: true
  1054. },
  1055. data:[0,2500,5000,7500,10000],
  1056. }
  1057. ],
  1058. series: [
  1059. {
  1060. name: '成功',
  1061. type: 'scatter',
  1062. emphasis: {
  1063. focus: 'series'
  1064. },
  1065. //设置散点图样式
  1066. itemStyle:{
  1067. color:'#13ce66'
  1068. },
  1069. symbolSize:10,//设置散点的大小
  1070. data:tmpData.success,
  1071. markArea: {
  1072. silent: true,
  1073. itemStyle: {
  1074. color: 'transparent',
  1075. borderWidth: 0,
  1076. borderType: 'dashed'
  1077. },
  1078. },
  1079. },
  1080. {
  1081. name: '失败',
  1082. type: 'scatter',
  1083. emphasis: {
  1084. focus: 'series'
  1085. },
  1086. itemStyle:{
  1087. color:'#ff4949'
  1088. },
  1089. // prettier-ignore
  1090. data:tmpData.failed,
  1091. // data:[],
  1092. markArea: {
  1093. silent: true,
  1094. itemStyle: {
  1095. color: 'transparent',
  1096. borderWidth: 0,
  1097. borderType: 'dashed'
  1098. },
  1099. },
  1100. }
  1101. ]
  1102. }
  1103. } else {
  1104. option = {
  1105. title: {
  1106. text: '暂无数据',
  1107. x: 'center',
  1108. y: 'center',
  1109. textStyle: {
  1110. color: '#999',
  1111. fontSize: 14
  1112. }
  1113. }
  1114. }
  1115. }
  1116. chart.setOption(option,true)
  1117. // 默认开启框选
  1118. chart.dispatchAction({
  1119. type: 'takeGlobalCursor',
  1120. key: 'brush',
  1121. brushOption: {
  1122. brushType: 'rect' // 指定选框类型
  1123. }
  1124. })
  1125. chart.off("brushSelected");
  1126. //框选选择数据
  1127. chart.on('brushSelected', (params) => {
  1128. var brushComponent = params.batch[0];
  1129. let successIndexList=[];
  1130. let failIndexList =[];
  1131. let successList=[];
  1132. let failList=[];
  1133. if(brushComponent.selected.length>1){
  1134. successIndexList = brushComponent.selected[0].dataIndex
  1135. failIndexList = brushComponent.selected[1].dataIndex
  1136. }else{
  1137. if(brushComponent.selected[0].seriesName !=undefined){
  1138. if(brushComponent.selected[0].seriesName =="失败"){
  1139. failIndexList = brushComponent.selected[0].dataIndex
  1140. }else{
  1141. successIndexList = brushComponent.selected[0].dataIndex
  1142. }
  1143. }
  1144. }
  1145. if(successIndexList.length>0){
  1146. for(let i = 0;i<tmpData.success.length;i++){
  1147. for(let j=0;j<successIndexList.length;j++){
  1148. if(successIndexList[j] == i){
  1149. successList.push(tmpData.success[i])
  1150. }
  1151. }
  1152. }
  1153. }
  1154. if(failIndexList.length>0){
  1155. for(let k=0;k<tmpData.failed.length;k++){
  1156. for(let l=0;l<failIndexList.length;l++){
  1157. if(failIndexList[l] == k){
  1158. failList.push(tmpData.failed[k])
  1159. }
  1160. }
  1161. }
  1162. }
  1163. let arr =successList.concat(failList);
  1164. let dataRange={};
  1165. if(arr.length>0){
  1166. let timeArr =[];
  1167. let valueArr=[];
  1168. for(let m=0;m<arr.length;m++){
  1169. timeArr.push(Math.round(Date.parse(arr[m][0])/1000));
  1170. valueArr.push(arr[m][1]);
  1171. }
  1172. let minTime = Math.min(...timeArr);
  1173. let maxTime = Math.max(...timeArr);
  1174. let minValue = Math.min(...valueArr);
  1175. let maxValue = Math.max(...valueArr)
  1176. if(minTime == maxTime){
  1177. minTime = minTime-1;
  1178. maxTime = maxTime+1;
  1179. }
  1180. if(minValue == maxValue){
  1181. minValue = minValue-1;
  1182. maxValue = maxValue+1;
  1183. }
  1184. //看是否有成功节点
  1185. if(successIndexList.length>0){
  1186. dataRange = {
  1187. start_time:minTime,
  1188. end_time:maxTime,
  1189. min_duration:minValue,
  1190. max_duration:maxValue,
  1191. failed:false,
  1192. app_alias:this.state.queryParams.app_alias,
  1193. service_name:this.props.id,
  1194. }
  1195. }else{
  1196. dataRange = {
  1197. start_time:minTime,
  1198. end_time:maxTime,
  1199. min_duration:minValue,
  1200. max_duration:maxValue,
  1201. failed:true,
  1202. app_alias:this.state.queryParams.app_alias,
  1203. service_name:this.props.id,
  1204. }
  1205. }
  1206. let timeAndDuration = JSON.stringify(dataRange);
  1207. // let href = this.$router.resolve({
  1208. // path:'/latency/index',
  1209. // query:{
  1210. // data:timeAndDuration
  1211. // }
  1212. // })
  1213. // window.open(window.location.origin+"/"+href.href,"_blank")
  1214. 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}`
  1215. window.open(href,"_blank")
  1216. this.timeoutId = setTimeout(()=>{
  1217. chart.dispatchAction({
  1218. type: 'brush',//选择action行为
  1219. areas:[]//areas表示选框的集合,此时为空即可。
  1220. });
  1221. },500)
  1222. }
  1223. });
  1224. window.addEventListener("resize",function (){
  1225. chart.resize();
  1226. });
  1227. }
  1228. //获取折线图
  1229. getNodeLiveness(){
  1230. let liveChart = echarts.getInstanceByDom(document.getElementById("box"));
  1231. if (liveChart == null) {
  1232. liveChart = echarts.init(document.getElementById("box"));
  1233. }else {
  1234. liveChart.dispose();
  1235. liveChart = echarts.init(document.getElementById("box"));
  1236. }
  1237. liveChart.showLoading(
  1238. {
  1239. text: ''
  1240. }
  1241. );
  1242. axios({
  1243. url: `${this.state.baseUrl}/api/v1/service/liveness`,
  1244. method: "get",
  1245. headers: { 'Authorization': getToken },
  1246. params: {
  1247. service_name:this.props.id,
  1248. start_time:this.state.queryParams.start_time,
  1249. end_time:this.state.queryParams.end_time,
  1250. }
  1251. }).then(res => {
  1252. liveChart.hideLoading();
  1253. if(res && res.data.code == 200){
  1254. const list = ((res || {}).data || {}).data || []
  1255. this.setState({
  1256. livenessData:[...list]
  1257. },()=>{
  1258. this.initLiveness(this.state.livenessData, liveChart);
  1259. })
  1260. }
  1261. });
  1262. }
  1263. //渲染折线图
  1264. initLiveness(data, liveChart){
  1265. let option = {}
  1266. if (data.length > 0 ){
  1267. const xAxisData = []
  1268. const lineArr1 = []
  1269. const lineArr2 = []
  1270. data.forEach((item)=>{
  1271. if (item){
  1272. xAxisData.push(item[0])
  1273. lineArr1.push(item[1])
  1274. const num = item[2] && item[2].toFixed(2)
  1275. lineArr2.push(num)
  1276. }
  1277. })
  1278. option = {
  1279. grid: {
  1280. top:'15%',
  1281. left: '3%',
  1282. right: '7%',
  1283. bottom: '14%',
  1284. containLabel: true
  1285. },
  1286. tooltip: {
  1287. trigger: 'axis',
  1288. showDelay: 0,
  1289. formatter: function (params) {
  1290. if (params && params.length === 2) {
  1291. let newParams = moment(params[0].name).format('YYYY-MM-DD HH:mm:ss');
  1292. let time = newParams+"<br/>"+ params[0].seriesName +':'+ params[0].value + '<br/>' + params[1].seriesName +':'+ params[1].value
  1293. return time;
  1294. }
  1295. return ''
  1296. }
  1297. },
  1298. xAxis: {
  1299. data: xAxisData,
  1300. type: 'category',
  1301. boundaryGap: false,
  1302. axisLabel: {
  1303. show:true,
  1304. rotate: 45, // 旋转标签,适用于标签较长的情况
  1305. interval: 10,
  1306. margin: 10, // 增加刻度标签间隔
  1307. textStyle: {
  1308. fontSize: 10,
  1309. textAlign:'center'
  1310. },
  1311. formatter: function(params) {
  1312. let newParams = moment(params).format('HH:mm:ss');
  1313. let time = newParams
  1314. return time;
  1315. }
  1316. },
  1317. splitLine: {
  1318. show: true
  1319. },
  1320. },
  1321. yAxis: [
  1322. {
  1323. boundaryGap: [0, '20%'],
  1324. type: 'value',
  1325. show:true,
  1326. name: '调用次数',
  1327. position: 'left',
  1328. alignTicks: true,
  1329. axisLine: {
  1330. show: true
  1331. },
  1332. axisLabel: {
  1333. formatter: '{value}'
  1334. }
  1335. },
  1336. {
  1337. boundaryGap: [0, '20%'],
  1338. type: 'value',
  1339. show:true,
  1340. offset: 0, // 防止与第一个Y轴重叠
  1341. // splitNumber: 5,
  1342. nameGap: 20, // 设置 Y 轴名称和轴线之间的间距
  1343. name: '中位延迟',
  1344. position: 'right',
  1345. alignTicks: true,
  1346. axisLine: {
  1347. show: true
  1348. },
  1349. axisLabel: {
  1350. formatter: '{value} ms'
  1351. }
  1352. },
  1353. ],
  1354. legend: {
  1355. data: ['调用次数', '中位延迟'],
  1356. bottom: 0,
  1357. itemGap: 100,
  1358. },
  1359. series: [
  1360. {
  1361. name: '调用次数',
  1362. type: 'line',
  1363. smooth: true,
  1364. yAxisIndex: 0, // 使用第一条 Y 轴
  1365. data: lineArr1
  1366. },
  1367. {
  1368. name: '中位延迟',
  1369. type: 'line',
  1370. smooth: true,
  1371. yAxisIndex: 1, // 使用第二条 Y 轴
  1372. data: lineArr2
  1373. }
  1374. ]
  1375. };
  1376. } else {
  1377. option = {
  1378. title: {
  1379. text: '暂无数据',
  1380. x: 'center',
  1381. y: 'center',
  1382. textStyle: {
  1383. color: '#999',
  1384. fontSize: 14
  1385. }
  1386. }
  1387. }
  1388. }
  1389. liveChart.setOption(option,true)
  1390. }
  1391. //获取异常trace列表 /api/v1/service/spans
  1392. getServiceSpans(){
  1393. this.setState({ loading: true });
  1394. this.setState({
  1395. traceData:[],
  1396. pagination:{total:0}
  1397. },()=>{
  1398. })
  1399. axios({
  1400. url: `${this.state.baseUrl}/api/v1/service/spans`,
  1401. method: "get",
  1402. headers: { 'Authorization': getToken },
  1403. params: {
  1404. service_name:this.props.id,
  1405. only_exception:this.state.traceQuery.only_exception, // 仅显示异常trace相关
  1406. only_database:this.state.traceQuery.only_database,
  1407. pageIndex:this.state.pagination.pageIndex,
  1408. pageSize:this.state.pagination.pageSize,
  1409. start_time:this.state.queryParams.start_time,
  1410. end_time:this.state.queryParams.end_time,
  1411. app_alias:this.state.queryParams.app_alias,
  1412. sort_field:'Timestamp',
  1413. sort_type: this.state.queryParams.sort_type
  1414. }
  1415. }).then(res => {
  1416. if(res && res.data.code == 200){
  1417. const list = (((res || {}).data || {}).data || {}).list || []
  1418. const total = Number((((res || {}).data || {}).data || {}).count || 0)
  1419. this.setState({
  1420. traceData:[...list],
  1421. pagination:{...this.state.pagination,total:total}
  1422. },()=>{
  1423. })
  1424. }
  1425. });
  1426. }
  1427. //获取table 数据
  1428. getTableData(){
  1429. this.setState({ loading: true });
  1430. this.setState({
  1431. tableData:[],
  1432. pagination2:{total:0}
  1433. },()=>{
  1434. })
  1435. axios({
  1436. url: `${this.state.coreBaseUrl}/v1/system-component/reqlist`,
  1437. method: "get",
  1438. headers: { 'Authorization': getToken },
  1439. params: {
  1440. page_num:this.state.pagination2.page_num,
  1441. page_size:this.state.pagination2.page_size,
  1442. start_time:this.state.queryParams.start_time,
  1443. end_time:this.state.queryParams.end_time,
  1444. app_alias:this.state.queryParams.app_alias,
  1445. component: this.props.id//
  1446. }
  1447. }).then(res => {
  1448. if(res && res.data.code == 200){
  1449. const list = (((res || {}).data || {}).data || {}).list || []
  1450. const total = Number((((res || {}).data || {}).data || {}).total || 0)
  1451. const page_num = (((res || {}).data || {}).data || {}).page_num || 1
  1452. const page_size = (((res || {}).data || {}).data || {}).page_size || 10
  1453. this.setState({
  1454. tableData:[...list],
  1455. pagination2:{page_num:page_num,page_size:page_size,total:total}
  1456. },()=>{
  1457. })
  1458. }
  1459. });
  1460. }
  1461. getBarData(){// 获取柱状图数据
  1462. let liveChart = echarts.getInstanceByDom(document.getElementById("chartContent"));
  1463. if (liveChart == null) {
  1464. liveChart = echarts.init(document.getElementById("chartContent"));
  1465. } else {
  1466. liveChart.dispose();
  1467. liveChart = echarts.init(document.getElementById("chartContent"));
  1468. }
  1469. this.setState({
  1470. tableData:[],
  1471. pagination2:{total:0}
  1472. },()=>{
  1473. })
  1474. liveChart.showLoading(
  1475. {
  1476. text: ''
  1477. }
  1478. );
  1479. axios({
  1480. url: `${this.state.coreBaseUrl}/v1/system-component/stats`,
  1481. method: "get",
  1482. headers: { 'Authorization': getToken },
  1483. params: {
  1484. start_time:this.state.queryParams.start_time,
  1485. end_time:this.state.queryParams.end_time,
  1486. app_alias:this.state.queryParams.app_alias,
  1487. component: this.props.id //
  1488. }
  1489. }).then(res => {
  1490. liveChart.hideLoading();
  1491. if(res && res.data.code == 200){
  1492. const list = res?.data?.data?.database_stats?.request_bar || []
  1493. this.setState({
  1494. barData:list
  1495. },()=>{
  1496. if(this.state.barData.length>0){
  1497. setTimeout(()=>{
  1498. this.initLineBar(this.state.barData, liveChart);
  1499. },0)
  1500. }
  1501. })
  1502. }
  1503. });
  1504. }
  1505. // 渲染调用统计 折线/柱形图
  1506. initLineBar(data, liveChart){
  1507. let xAxisArr = data.map(item => item.start_time) || []
  1508. let dataArr = data.map(item => item.total) || []
  1509. let option = {}
  1510. if (data && data.length > 0) {
  1511. option = {
  1512. xAxis: {
  1513. type: 'category',
  1514. data: xAxisArr,
  1515. axisLabel: {
  1516. show:true,
  1517. rotate: 45, // 旋转标签,适用于标签较长的情况
  1518. interval: 'auto',
  1519. margin: 10, // 增加刻度标签间隔
  1520. textStyle: {
  1521. fontSize: 10,
  1522. textAlign:'center'
  1523. },
  1524. formatter: function(params) {
  1525. let newParams = moment(params).format('HH:mm:ss');
  1526. let time = newParams
  1527. return time;
  1528. }
  1529. },
  1530. },
  1531. tooltip: {
  1532. showDelay: 0,
  1533. axisPointer: {
  1534. show: true,
  1535. type: 'cross',
  1536. lineStyle: {
  1537. type: 'dashed',
  1538. width: 1
  1539. }
  1540. }
  1541. },
  1542. yAxis: {
  1543. type: 'value'
  1544. },
  1545. series: [
  1546. {
  1547. data: dataArr,
  1548. type: 'bar'
  1549. }
  1550. ]
  1551. };
  1552. } else {
  1553. option = {
  1554. title: {
  1555. text: '暂无数据',
  1556. x: 'center',
  1557. y: 'center',
  1558. textStyle: {
  1559. color: '#999',
  1560. fontSize: 14
  1561. }
  1562. }
  1563. }
  1564. }
  1565. liveChart.setOption(option,true)
  1566. }
  1567. handleTableChange=(pagination,filters, sorter, extra)=>{
  1568. const {current} = pagination
  1569. let sort = ''
  1570. if(sorter && sorter.order == 'ascend'){
  1571. sort = 'ASC'
  1572. } else if(sorter && sorter.order == 'descend'){
  1573. sort = 'DESC'
  1574. }
  1575. this.setState({
  1576. pagination: {...this.state.pagination,pageIndex:current,current:current},
  1577. queryParams:{...this.state.queryParams,sort_type:sort}
  1578. },()=>{
  1579. this.getServiceSpans();
  1580. });
  1581. }
  1582. handleTableChange2=(pagination,filters, sorter, extra)=>{
  1583. const {current} = pagination
  1584. this.setState({
  1585. pagination2: {...this.state.pagination2,page_num:current}
  1586. },()=>{
  1587. this.getTableData();
  1588. });
  1589. }
  1590. //仅异常
  1591. onChangeError = e =>{
  1592. const only_exception = e.target.checked ? 1:0;
  1593. const pageIndex = 1
  1594. const current = 1
  1595. this.setState({
  1596. traceQuery:{...this.state.traceQuery,only_exception:only_exception},
  1597. pagination: {...this.state.pagination,pageIndex:pageIndex,current:current},
  1598. },()=>{
  1599. this.getServiceSpans();
  1600. });
  1601. }
  1602. //仅sql
  1603. onChangeSql = e =>{
  1604. const only_database = e.target.checked ? 1:0;
  1605. const pageIndex = 1
  1606. const current = 1
  1607. this.setState({
  1608. traceQuery:{...this.state.traceQuery,only_database:only_database},
  1609. pagination: {...this.state.pagination,pageIndex:pageIndex,current:current},
  1610. },()=>{
  1611. this.getServiceSpans(this.props.source,this.props.target);
  1612. });
  1613. }
  1614. //单选按钮
  1615. onChange = e => {
  1616. const percentile = e.target.value
  1617. this.setState({
  1618. queryParams:{...this.state.queryParams,percentile:percentile},
  1619. AnalystData:[]
  1620. },()=>{
  1621. this.getNodeAnalyst();
  1622. });
  1623. };
  1624. renderTable(table) {
  1625. const { nodeMatches = makeMap() } = this.props;
  1626. if (isGenericTable(table)) {
  1627. return (
  1628. <NodeDetailsGenericTable
  1629. rows={table.rows}
  1630. columns={table.columns}
  1631. matches={nodeMatches.get('tables')}
  1632. />
  1633. );
  1634. } if (isPropertyList(table)) {
  1635. return (
  1636. <NodeDetailsPropertyList
  1637. rows={table.rows}
  1638. controls={table.controls}
  1639. matches={nodeMatches.get('property-lists')}
  1640. />
  1641. );
  1642. }
  1643. log(`Undefined type '${table.type}' for table ${table.id}`);
  1644. return null;
  1645. }
  1646. componentDidUpdate() {
  1647. this.updateTitle();
  1648. }
  1649. updateTitle() {
  1650. setDocumentTitle(this.props.details && this.props.details.label);
  1651. }
  1652. //dottedcylinder 类型 -获取基础信息
  1653. getDottBasicsData(){
  1654. this.setState({ loading: true });
  1655. this.setState({
  1656. messaging_stats:{
  1657. topic_stats:[]
  1658. }
  1659. },()=>{
  1660. })
  1661. axios({
  1662. url: `${this.state.coreBaseUrl}/v1/system-component/stats`,
  1663. method: "get",
  1664. headers: { 'Authorization': getToken },
  1665. params: {
  1666. start_time:this.state.queryParams.start_time,
  1667. end_time:this.state.queryParams.end_time,
  1668. app_alias:this.state.queryParams.app_alias,
  1669. component: this.props.id//
  1670. }
  1671. }).then(res => {
  1672. if(res && res.data.code == 200){
  1673. const messaging_stats = (((res || {}).data || {}).data || {}).messaging_stats || {}
  1674. this.setState({
  1675. messaging_stats:messaging_stats
  1676. },()=>{
  1677. })
  1678. }
  1679. });
  1680. }
  1681. // cloud 类型-获取嵌套table
  1682. getCloudTableData(){// 获取柱状图数据
  1683. this.setState({ loading: true });
  1684. this.setState({
  1685. cloudTable:[],
  1686. cloudTable2:[],
  1687. },()=>{
  1688. })
  1689. axios({
  1690. url: `${this.state.coreBaseUrl}/v1/service/related-apps`,
  1691. method: "get",
  1692. headers: { 'Authorization': getToken },
  1693. params: {
  1694. start_time: this.state.queryParams.start_time, //1727366400
  1695. end_time:this.state.queryParams.end_time, // 1727415703
  1696. app_alias:this.state.queryParams.app_alias,
  1697. type: this.props.id //
  1698. }
  1699. }).then(res => {
  1700. if(res && res.data.code == 200){
  1701. const list = (((res || {}).data ||{}).data || {}).app_list || []
  1702. const list2 = (((res ||{}).data ||{}).data ||{}).client_list || []
  1703. this.setState({
  1704. cloudTable:[...list],
  1705. cloudTable2: [...list2]
  1706. },()=>{
  1707. })
  1708. }
  1709. });
  1710. }
  1711. }
  1712. NodeDetails.propTypes = {
  1713. renderNodeDetailsExtras: PropTypes.func,
  1714. };
  1715. NodeDetails.defaultProps = {
  1716. renderNodeDetailsExtras: noop,
  1717. };
  1718. function mapStateToProps(state, ownProps) {
  1719. const currentTopologyId = state.get('currentTopologyId');
  1720. return {
  1721. nodeMatches: state.getIn(['searchNodeMatches', currentTopologyId, ownProps.id]),
  1722. nodes: state.get('nodes'),
  1723. selectedNodeId: state.get('selectedNodeId'),
  1724. transitioning: state.get('pausedAt') !== ownProps.timestamp,
  1725. };
  1726. }
  1727. export default connect(
  1728. mapStateToProps,
  1729. { clickCloseDetails, clickShowTopologyForNode }
  1730. )(NodeDetails);