node-details.js 56 KB

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