node-details.js 59 KB

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