index.vue 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902
  1. <template>
  2. <BasicLayout>
  3. <template #wrapper>
  4. <el-card class="box-card" style="flex:1;">
  5. <div >
  6. <el-page-header @back="back">
  7. <div slot="content">
  8. <span style="font-size:18px;margin-right:16px;">{{ detailTitle }}</span>
  9. <el-button
  10. size="small"
  11. type="primary"
  12. @click.native.stop="handleUpdateName()"
  13. >修改业务别名</el-button>
  14. </div>
  15. </el-page-header>
  16. </div>
  17. <div >
  18. <div class="flexWrap">
  19. <div class="flex-left">
  20. <div class="ob-switch-box">
  21. <el-switch
  22. v-model="switchValue"
  23. active-color="#165DFF"
  24. inactive-color="#C9CDD4"
  25. @change="handelSwitch"
  26. />
  27. </div>
  28. <div v-if="switchValue">
  29. <div v-if="JSON.stringify(graphData)!=&quot;{}&quot;" v-loading="loading">
  30. <div style="font-size:14px;font-weight:bold;text-align:right">
  31. <svg-icon :icon-class="isFull?'exit-fullscreen':'fullscreen'" @click.native.prevent="clickFull" />
  32. </div>
  33. <Topo ref="topo" :graph-data="graphData" topo-id="container" @changeState="clickRowHandleNode" />
  34. </div>
  35. </div>
  36. <div v-else style="position:relative;height: 100%;">
  37. <div v-if="isLoading" v-loading="isLoading" class="loading-overlay"></div>
  38. <div v-else>
  39. <NodeG6Charts v-if="sweetGraphData && Object.keys(sweetGraphData).length > 0" :chart-content-id="'serviceDrawerContainer'" :graph-data="sweetGraphData" />
  40. </div>
  41. </div>
  42. </div>
  43. <div class="flex-right">
  44. <div id="scaleMain" style="width:100%;height:240px;margin-bottom:36px;margin-top:16px" />
  45. <div style="text-align: right; padding-right:10px;">
  46. <el-checkbox v-model="checked" style="margin-bottom:10px" @change="handelCheckChange(checked)">Duration > 2500ms</el-checkbox>
  47. <el-checkbox v-model="onlyException" style="margin-bottom:10px" @change="handelChange()">仅异常</el-checkbox>
  48. </div>
  49. <el-table
  50. v-loading="tableLoading"
  51. :data="rowData"
  52. @sort-change="sortChangeTable"
  53. >
  54. <el-table-column header-align="center" label="TraceID" prop="trace_id" align="center" width="240">
  55. <template slot-scope="scope">
  56. <span @click="goto(scope.row)">{{ scope.row.trace_id }}</span>
  57. </template>
  58. </el-table-column>
  59. <el-table-column header-align="center" label="SpanName" prop="span_name" align="center" :show-overflow-tooltip="true" />
  60. <el-table-column header-align="center" label="DateTime" sortable="custom" prop="datetime" align="center" :show-overflow-tooltip="true" />
  61. <el-table-column header-align="center" label="Duration(ms)" sortable="custom" prop="duration" width="140" align="center">
  62. <template slot-scope="scope">
  63. <el-tag v-if="scope.row.duration>=2000" type="danger">{{ scope.row.duration }}</el-tag>
  64. <el-tag v-if="scope.row.duration<2000" type="info">{{ scope.row.duration }}</el-tag>
  65. </template>
  66. </el-table-column>
  67. </el-table>
  68. <pagination
  69. v-show="total>0"
  70. :total="total"
  71. :page.sync="childQueryParams.pageIndex"
  72. :limit.sync="childQueryParams.pageSize"
  73. @pagination="handelgetBizDetail"
  74. />
  75. </div>
  76. </div>
  77. <!-- 添加或修改应用配置对话框 -->
  78. <el-dialog v-if="open" :title="title" :visible.sync="open" width="700px" :center="true" :close-on-click-modal="false">
  79. <el-form ref="form" :model="form" label-width="95px">
  80. <el-form-item label="中文别名" prop="name">
  81. <el-input v-model="form.name" placeholder="请输入中文别名" />
  82. </el-form-item>
  83. </el-form>
  84. <div slot="footer" class="dialog-footer">
  85. <el-button type="primary" @click="submitForm">确 定</el-button>
  86. <el-button @click="cancel">取 消</el-button>
  87. </div>
  88. </el-dialog>
  89. <el-dialog
  90. :visible.sync="Visible"
  91. :fullscreen="true"
  92. center
  93. :show-close="false"
  94. >
  95. <Topo :graph-data="graphData" topo-id="container1" @changeState="clickRowHandleNode" />
  96. </el-dialog>
  97. </div>
  98. <el-drawer
  99. :visible.sync="drawer"
  100. direction="rtl"
  101. size="50%"
  102. @close="drawerClose"
  103. >
  104. <h4 slot="title" style="font-size:16px">{{ detailData.name }}</h4>
  105. <div class="node-details">
  106. <div class="node-details-content">
  107. <div class="node-details-content-section">
  108. <div style="display:flex;justify-content: space-between;">
  109. <div class="node-details-content-section-header"><a href="" _blank>节点信息</a></div>
  110. <div>
  111. <el-button size="mini" type="primary" plain @click="editNodeName()">修改节点别名</el-button>
  112. </div>
  113. </div>
  114. <div>
  115. <div class="node-details-info-field" style="position:relative;display:flex;">
  116. <div class="node-details-info-field-label truncate w50" style="width:50%">节点名称:</div>
  117. <div class="node-details-info-field-value truncate" style="width:50%">
  118. {{ detailData.name }}
  119. </div>
  120. </div>
  121. <div class="node-details-info-field">
  122. <div class="node-details-info-field-label truncate w50" style="width:50%">所属服务:</div>
  123. <!-- 目前后端没返回服务别名,中文名的字段 -->
  124. <div class="node-details-info-field-value truncate w50" style="width:50%;color:#1890ff;cursor: pointer;" @click="gotoPathService(&quot;/service/serviceDetail/index&quot;,detailData.service_name)">
  125. {{ detailData.service_name }}
  126. </div>
  127. </div>
  128. <div class="node-details-info-field">
  129. <div class="node-details-info-field-label truncate w50" style="width:50%">Span名称:</div>
  130. <div class="node-details-info-field-value truncate w50" style="width:50%">{{ detailData.span_name }}</div>
  131. </div>
  132. <div class="node-details-info-field">
  133. <div class="node-details-info-field-label truncate w50" style="width:50%">Span类型:</div>
  134. <div class="node-details-info-field-value truncate w50" style="width:50%">{{ detailData.span_kind }}</div>
  135. </div>
  136. <div class="node-details-info-field">
  137. <div class="node-details-info-field-label truncate w50" style="width:50%">成功率:</div>
  138. <div class="node-details-info-field-value truncate w50" style="width:50%">{{ ((detailData.rate || 0)*100).toFixed(2)+'%' }}</div>
  139. </div>
  140. </div>
  141. </div>
  142. <div v-if="detailData.span_type == &quot;http server&quot;" class="node-details-content-section">
  143. <div class="node-details-content-section-header">接口信息</div>
  144. <div class="node-details-info-field">
  145. <div class="node-details-info-field-label truncate w50" style="width:50%">请求量:</div>
  146. <div class="node-details-info-field-value truncate w50" style="width:50%">{{ nodeStats.total }}</div>
  147. </div>
  148. <div class="node-details-info-field">
  149. <div class="node-details-info-field-label truncate w50" style="width:50%">错误率:</div>
  150. <div class="node-details-info-field-value truncate w50" style="width:50%">{{ ((nodeStats.error_rate || 0) *100 ).toFixed(4) + '%' }}</div>
  151. </div>
  152. <div class="node-details-info-field">
  153. <div class="node-details-info-field-label truncate w50" style="width:50%">P50:</div>
  154. <div class="node-details-info-field-value truncate w50" style="width:50%">{{ (nodeStats.p50 || 0 ).toFixed(2) +'ms' }}</div>
  155. </div>
  156. <div class="node-details-info-field">
  157. <div class="node-details-info-field-label truncate w50" style="width:50%">P90:</div>
  158. <div class="node-details-info-field-value truncate w50" style="width:50%">{{ (nodeStats.p90 || 0).toFixed(2) +'ms' }}</div>
  159. </div>
  160. <div class="node-details-info-field">
  161. <div class="node-details-info-field-label truncate w50" style="width:50%">P99:</div>
  162. <div class="node-details-info-field-value truncate w50" style="width:50%">{{ (nodeStats.p99 || 0).toFixed(2) +'ms' }}</div>
  163. </div>
  164. <div>
  165. <div class="node-details-info-field" style="cursor: pointer;" @click="gotoPath(&quot;/service/InterfaceDetail/index&quot;,nodeStats)">
  166. <div class="node-details-info-field-label truncate w50" style="width:50%">接口名:</div>
  167. <div class="node-details-info-field-value truncate w50" style="width:50%;color:#1890ff;cursor: pointer;">{{ nodeStats.name }}</div>
  168. </div>
  169. <div class="node-details-info-field">
  170. <div class="node-details-info-field-label truncate w50" style="width:50%">接口地址:</div>
  171. <div class="node-details-info-field-value truncate w50" style="width:50%">{{ nodeStats.value }}</div>
  172. </div>
  173. </div>
  174. </div>
  175. <div class="node-details-content-section">
  176. <div class="node-details-content-section-header">Span列表</div>
  177. <div>
  178. <el-table
  179. v-loading="nodeloading"
  180. :data="nodeSpansList"
  181. @row-click="handleRowClick"
  182. >
  183. <el-table-column label="链路标识" prop="trace_id" :show-overflow-tooltip="true" />
  184. <el-table-column label="Method" prop="method" :show-overflow-tooltip="true" />
  185. <el-table-column label="Code" prop="code" :show-overflow-tooltip="true" />
  186. <el-table-column label="Duration(ms)" sortable="custom" prop="duration" :show-overflow-tooltip="true" />
  187. </el-table>
  188. <pagination
  189. v-show="nodeCount>0"
  190. small
  191. :total="nodeCount"
  192. :page.sync="querySpans.pageIndex"
  193. :limit.sync="querySpans.pageSize"
  194. @pagination="getNodeSpans"
  195. />
  196. </div>
  197. </div>
  198. </div>
  199. </div>
  200. </el-drawer>
  201. <el-dialog v-if="nodeOpen" title="修改节点别名" :visible.sync="nodeOpen" width="700px" :center="true" :close-on-click-modal="false">
  202. <el-form ref="queryName" :model="queryName" label-width="95px">
  203. <el-form-item label="节点别名" prop="name">
  204. <el-input v-model="queryName.name" placeholder="请输入节点别名" />
  205. </el-form-item>
  206. </el-form>
  207. <div slot="footer" class="dialog-footer">
  208. <el-button type="primary" @click="saveNodeName">确 定</el-button>
  209. <el-button @click="cancelNode">取 消</el-button>
  210. </div>
  211. </el-dialog>
  212. </el-card>
  213. </template>
  214. </BasicLayout>
  215. </template>
  216. <script>
  217. import storage from '@/utils/storage'
  218. import { getBizDetail, listBizGraph, updateBiz, listBiz, bizNodeStats, bizNodeName, bizNodeSpans, getSweetList, addUrlMapping, listBizStats } from '@/api/mapping'
  219. import Topo from './components/Topo'
  220. import resize from '../mixins/resize'
  221. import moment from 'moment'
  222. import NodeG6Charts from '../../service/InterfaceDetail/components/NodeG6Charts.vue'
  223. export default {
  224. components: {
  225. Topo,
  226. NodeG6Charts
  227. },
  228. mixins: [resize],
  229. data() {
  230. return {
  231. nodeOpen: false,
  232. drawer: false,
  233. isFull: false,
  234. Visible: false,
  235. loading: false,
  236. open: false,
  237. form: {},
  238. // 表单校验
  239. rules: {
  240. name: [
  241. { required: true, message: '接口名称不能为空', trigger: 'blur' }
  242. ]
  243. },
  244. title: '修改业务别名',
  245. // 查询参数
  246. queryParams: {
  247. // start_time:Math.round((new Date().getTime())/1000 - (5*60)),
  248. // end_time:Math.round(new Date().getTime()/1000),
  249. app_id: '',
  250. start_time: '',
  251. end_time: '',
  252. pageIndex: 1,
  253. pageSize: 10
  254. },
  255. detailTitle: '',
  256. tableLoading: false,
  257. rowData: [],
  258. total: 0,
  259. childQueryParams: {
  260. // start_time:Math.round((new Date().getTime())/1000 - (5*60)),
  261. // end_time:Math.round(new Date().getTime()/1000),
  262. start_time: 0,
  263. end_time: 0,
  264. biz_id: 1,
  265. pageIndex: 1,
  266. pageSize: 10,
  267. sort_type: 'DESC',
  268. sort_field: 'Timestamp',
  269. min_duration: 0,
  270. only_exception: false
  271. },
  272. BizStatsQuery: {
  273. biz_id: 0,
  274. start_time: '',
  275. end_time: ''
  276. },
  277. graphData: {},
  278. detailObj: {},
  279. timer: null,
  280. detailData: {},
  281. queryStats: {
  282. id: 0,
  283. // hash:'',//和id二选一
  284. start_time: 0,
  285. end_time: 0
  286. },
  287. nodeStats: {},
  288. queryName: {
  289. id: 0,
  290. // hash:'',//和id二选一
  291. name: ''
  292. },
  293. querySpans: {
  294. id: 0,
  295. // hash:'',//和id二选一
  296. start_time: '',
  297. end_time: '',
  298. pageIndex: 1,
  299. pageSize: 10
  300. },
  301. nodeSpansList: [],
  302. nodeCount: 0,
  303. nodeloading: false,
  304. checked: false,
  305. onlyException: false,
  306. switchValue: false,
  307. isLoading: false,
  308. sweetquanParams: {
  309. biz_id: 0,
  310. start_time: '',
  311. end_time: ''
  312. },
  313. sweetGraphData:{},
  314. appItem: {},
  315. chartQuantiles: {
  316. biz_id: null,
  317. start_time: '',
  318. end_time: ''
  319. }
  320. }
  321. },
  322. watch: {
  323. '$store.state.time.globalTimes': {
  324. handler(newValue, oldValue) {
  325. if (newValue) {
  326. this.childQueryParams.start_time = newValue.startTime
  327. this.childQueryParams.end_time = newValue.endTime
  328. this.queryParams.start_time = newValue.startTime
  329. this.queryParams.end_time = newValue.endTime
  330. this.queryParams.pageIndex = 1
  331. this.querySpans.start_time = newValue.startTime
  332. this.querySpans.end_time = newValue.endTime
  333. this.queryStats.start_time = newValue.startTime
  334. this.queryStats.end_time = newValue.endTime
  335. this.sweetquanParams.start_time = newValue.startTime
  336. this.sweetquanParams.end_time = newValue.endTime
  337. this.chartQuantiles.start_time = newValue.startTime
  338. this.chartQuantiles.end_time = newValue.endTime
  339. this.querySpans.pageIndex = 1
  340. this.getList() // 获取前一个页面-解析列表的table 数据
  341. this.handelgetBizDetail()// 获取table 表格
  342. //
  343. if (this.switchValue) {
  344. this.getListBizGraph(this.$route.query.id)// 获取紧凑树接口
  345. } else {
  346. this.getSweetListFn() // 获取甜甜圈接口
  347. }
  348. if (newValue.timeOut) {
  349. if (newValue.timeOut == 1) {
  350. clearInterval(this.timer)
  351. } else {
  352. clearInterval(this.timer)
  353. this.Refresh(newValue.timeOut)
  354. }
  355. }
  356. }
  357. },
  358. // immediate: true,
  359. deep: true
  360. }
  361. },
  362. created() {
  363. const start_time = this.$store.state.time.globalTimes.startTime
  364. const end_time = this.$store.state.time.globalTimes.endTime
  365. this.childQueryParams.start_time = start_time
  366. this.childQueryParams.end_time = end_time
  367. this.queryParams.start_time = start_time
  368. this.queryParams.end_time = end_time
  369. this.querySpans.start_time = start_time
  370. this.querySpans.end_time = end_time
  371. this.queryStats.start_time = start_time
  372. this.queryStats.end_time = end_time
  373. this.sweetquanParams.start_time = start_time
  374. this.sweetquanParams.end_time = end_time
  375. this.chartQuantiles.start_time = start_time
  376. this.chartQuantiles.end_time = end_time
  377. this.appItem = storage.get('appsItem')
  378. this.detailObj = storage.get('detailObj')
  379. if (this.$route.query.id != undefined) {
  380. this.childQueryParams.biz_id = this.$route.query.id
  381. this.chartQuantiles.biz_id = this.$route.query.id
  382. this.detailTitle = this.$route.query.name
  383. this.queryParams.app_id = this.appItem.id
  384. this.sweetquanParams.biz_id = this.$route.query.id
  385. this.handelgetBizDetail()
  386. this.getList()
  387. if (this.switchValue) {
  388. this.getListBizGraph(this.$route.query.id)
  389. } else {
  390. this.getSweetListFn()
  391. }
  392. }
  393. },
  394. mounted() {
  395. // this.$nextTick(() => {
  396. // if (document.getElementById('scaleMain')){
  397. // this.drawEchartsScale(this.detailObj)
  398. // }
  399. // })
  400. },
  401. beforeDestroy() {
  402. clearInterval(this.timer)
  403. this.graphData = {}
  404. },
  405. methods: {
  406. handelSwitch() {
  407. if (this.switchValue) {
  408. this.getListBizGraph(this.sweetquanParams.biz_id)
  409. } else {
  410. this.getSweetListFn()
  411. }
  412. },
  413. async getSweetListFn() {
  414. this.isLoading = true
  415. this.sweetGraphData = {}
  416. const res = await getSweetList(this.sweetquanParams)
  417. if (res && res.code === 200) {
  418. this.sweetGraphData = res.data
  419. } else {
  420. this.sweetGraphData = {}
  421. }
  422. this.isLoading = false
  423. },
  424. gotoPathService(path, service_name) {
  425. const href = this.$router.resolve({
  426. path: path,
  427. query: {
  428. service_name: service_name
  429. }
  430. })
  431. window.open(window.location.origin + '/' + href.href, '_blank')
  432. },
  433. gotoPath(path, row) {
  434. storage.set('row', row)
  435. const href = this.$router.resolve({
  436. path: path,
  437. query: {
  438. name: row.name,
  439. kind: row.value,
  440. method: row.http_method,
  441. service_name: row.service_name
  442. }
  443. })
  444. window.open(window.location.origin + '/' + href.href, '_blank')
  445. },
  446. handleRowClick(row, column, event) {
  447. const datetime = Date.parse(row.datetime) / 1000
  448. const href = this.$router.resolve({
  449. path: '/latency/index',
  450. query: {
  451. id: row.trace_id,
  452. span_id: row.span_id,
  453. datetime: datetime
  454. }
  455. })
  456. window.open(window.location.origin + '/' + href.href, '_blank')
  457. },
  458. clickRowHandleNode(item) {
  459. this.detailData = {}
  460. this.detailData = item
  461. this.queryStats.id = Number(item.unique_id)
  462. this.queryName.id = Number(item.unique_id)
  463. this.queryName.name = item.name
  464. this.querySpans.id = Number(item.unique_id)
  465. if (item.span_type == 'http server') {
  466. this.getNodeStats()
  467. }
  468. this.getNodeSpans()
  469. this.drawer = true
  470. },
  471. getNodeStats() {
  472. bizNodeStats(this.queryStats).then(res => {
  473. if (res&& res.code == 200) {
  474. this.nodeStats = res.data
  475. }
  476. })
  477. },
  478. saveNodeName() {
  479. bizNodeName(this.queryName).then(res => {
  480. if (res.code == 200) {
  481. this.msgSuccess(res.msg)
  482. this.nodeOpen = false
  483. this.drawer = false
  484. this.graphData = {}
  485. // window.location.reload();
  486. this.handelgetBizDetail()
  487. this.getListBizGraph(this.$route.query.id)
  488. }
  489. })
  490. },
  491. cancelNode() {
  492. this.nodeOpen = false
  493. },
  494. getNodeSpans() {
  495. this.nodeloading = true
  496. bizNodeSpans(this.querySpans).then(res => {
  497. if (res.code == 200) {
  498. this.nodeloading = false
  499. this.nodeSpansList = res.data.list
  500. this.nodeCount = res.data.count
  501. }
  502. })
  503. },
  504. editNodeName() {
  505. this.nodeOpen = true
  506. },
  507. drawerClose() {
  508. this.drawer = false
  509. },
  510. clickFull() {
  511. this.Visible = true
  512. // this.$refs['topo'].initGraph();
  513. },
  514. handleUpdateName() {
  515. this.open = true
  516. this.form = this.detailObj
  517. delete this.form.quantiles
  518. },
  519. /** 提交按钮 */
  520. submitForm: function() {
  521. this.$refs['form'].validate(valid => {
  522. if (valid) {
  523. if (this.form.id !== undefined || this.form.id != 0) {
  524. updateBiz(this.form, this.form.id).then(response => {
  525. if (response.code === 200) {
  526. this.msgSuccess(response.msg)
  527. this.open = false
  528. this.drawer = false
  529. this.$router.push({
  530. path: '/business-analysis/analysis/index'
  531. })
  532. } else {
  533. this.msgError(response.msg)
  534. }
  535. })
  536. } else {
  537. addUrlMapping(this.form).then(response => {
  538. if (response.code === 200) {
  539. this.msgSuccess(response.msg)
  540. this.open = false
  541. this.queryParams.pageIndex = 1
  542. this.serveceMapList = []
  543. this.tempList = []
  544. this.getList()
  545. } else {
  546. this.msgError(response.msg)
  547. }
  548. })
  549. }
  550. }
  551. })
  552. },
  553. // 取消按钮
  554. cancel() {
  555. this.open = false
  556. this.reset()
  557. },
  558. getList() { // 获取前一个页面-解析列表的table 数据
  559. listBiz(this.queryParams).then(res => {
  560. if (res.code == 200) {
  561. for (let i = 0; i < res.data.list.length; i++) {
  562. if (res.data.list[i].id == this.$route.query.id) {
  563. this.detailTitle = res.data.list[i].name
  564. this.detailObj = res.data.list[i]
  565. listBizStats(this.chartQuantiles).then(response => {
  566. if (response.code == 200) {
  567. if (response.data.biz_id != undefined) {
  568. if (this.detailObj.id == response.data.biz_id) {
  569. this.detailObj = Object.assign({}, this.detailObj, response.data)
  570. storage.set('detailObj', this.detailObj)
  571. setTimeout(()=>{
  572. this.drawEchartsScale(this.detailObj)
  573. },100)
  574. }
  575. }
  576. }
  577. })
  578. }
  579. }
  580. }
  581. })
  582. },
  583. // 表单重置
  584. reset() {
  585. this.form = {
  586. id: undefined,
  587. name: undefined,
  588. url: undefined,
  589. type: undefined,
  590. module: undefined,
  591. summary: undefined,
  592. favor: undefined
  593. }
  594. this.resetForm('form')
  595. },
  596. Refresh(timeOut) {
  597. this.timer = setInterval(() => {
  598. this.handelgetBizDetail()
  599. this.getListBizGraph(this.$route.query.id)
  600. }, timeOut)
  601. },
  602. back() {
  603. this.$router.push({
  604. path: '/business-analysis/analysis/index'
  605. })
  606. },
  607. handelgetBizDetail() {// 获取table 表格
  608. this.tableLoading = true
  609. getBizDetail(this.childQueryParams).then(res => {
  610. if (res.code == 200) {
  611. this.tableLoading = false
  612. this.rowData = res.data.list
  613. this.total = res.data.count
  614. }
  615. })
  616. },
  617. getListBizGraph(id) {
  618. this.loading = true
  619. listBizGraph({ biz_id: id }).then((res) => {
  620. this.graphData = {}
  621. if (res.code == 200) {
  622. const arr = res.data
  623. if (arr.length > 0) {
  624. const List = this.handelListBizData(arr)
  625. this.graphData = List[0]
  626. this.loading = false
  627. } else {
  628. this.graphData = {}
  629. }
  630. }
  631. })
  632. },
  633. handelListBizData(data) {
  634. data.forEach((v, k) => {
  635. // v.collapsed = false;
  636. v.id = v.id.toString()
  637. v.name = v.name
  638. v.label = v.stats.duration != undefined ? (v.stats.duration).toString() : ''
  639. v.currency = 'ms'
  640. v.rate = v.stats.success_rate
  641. v.status = v.stats.success_rate >= 0.6 ? 'B' : v.stats.success_rate == 0 ? 'DI' : 'R'
  642. v.variableValue = v.stats.success_rate
  643. v.variableUp = v.stats.success_rate_up
  644. if (v.children != undefined && v.children.length > 0) {
  645. v.children = this.handelListBizData(v.children)
  646. }
  647. })
  648. return data
  649. },
  650. drawEchartsScale(row) {
  651. const _this = this
  652. let myChartScale = this.$echarts5.getInstanceByDom(document.getElementById('scaleMain'));
  653. if (myChartScale == undefined) {
  654. myChartScale = this.$echarts5.init(document.getElementById('scaleMain'));
  655. }
  656. // 绘制趋势echarts
  657. const option = {
  658. tooltip: {
  659. trigger: 'axis',
  660. confine: false,
  661. appendToBody: true,
  662. show: true,
  663. formatter: function(params) {
  664. storage.set('paramsValue', params)
  665. const axisValueLabel = params[0].axisValueLabel
  666. let str0 = ''
  667. params.forEach((item, idx) => {
  668. // str1+=`${}`
  669. str0 += `${item.marker}${item.seriesName}<span style='margin-left:30px;text-align:right;font-weight:400'>${item.data}</span>`
  670. switch (idx) {
  671. case 0:
  672. str0
  673. break
  674. case 1:
  675. str0
  676. break
  677. default:
  678. str0
  679. }
  680. str0 += idx === params.length - 1 ? '' : '<br/>'
  681. })
  682. return axisValueLabel + '<br>' + str0
  683. }
  684. },
  685. title: {
  686. text: '延迟比例',
  687. // subtext: 'ms',
  688. textStyle: {
  689. fontSize: 14
  690. }
  691. // triggerEvent: true,
  692. },
  693. grid: {
  694. // top:'5%',
  695. left: '3%',
  696. right: '6%',
  697. bottom: '1%',
  698. containLabel: true
  699. },
  700. xAxis: {
  701. type: 'category',
  702. boundaryGap: false,
  703. data: row.quantiles.time,
  704. axisLabel: {
  705. textStyle: {
  706. fontSize: 12,
  707. textAlign: 'center'
  708. },
  709. formatter: function(params) {
  710. // let newParams = moment(params).format('YYYY-MM-DD HH:mm:ss');
  711. // let newArr = newParams.split(' ')
  712. // let time = newArr[0] + "\n" + newArr[1]
  713. // return time;
  714. let newParams = ''
  715. const dateStr = params.substring(0, 10)
  716. // dateStr = dateStr.replace(/\-/g, function(match) {
  717. // return match.replace(/\-/g, '.');
  718. // });
  719. const timeStr = params.substring(11, 19)
  720. newParams = dateStr + '\n' + timeStr
  721. return newParams
  722. }
  723. }
  724. },
  725. yAxis: {
  726. type: 'value',
  727. scale: true,
  728. gridIndex: 0,
  729. axisLabel: {
  730. formatter: '{value}'
  731. },
  732. axisLine: {
  733. show: true
  734. },
  735. axisTick: {
  736. show: true
  737. },
  738. splitLine: {
  739. show: true
  740. }
  741. },
  742. series: [
  743. {
  744. name: 'P.50',
  745. type: 'line',
  746. stack: 'Total',
  747. symbol: 'none',
  748. // symbol: 'emptyCircle',//拐点
  749. data: row.quantiles.p50
  750. },
  751. {
  752. name: 'P.95',
  753. type: 'line',
  754. stack: 'Total',
  755. symbol: 'none', // 拐点
  756. // symbol: 'emptyCircle',//拐点
  757. data: row.quantiles.p90
  758. },
  759. {
  760. name: 'P.99',
  761. type: 'line',
  762. stack: 'Total',
  763. symbol: 'none', // 拐点
  764. // symbol: 'emptyCircle',//拐点
  765. data: row.quantiles.p99
  766. }
  767. ]
  768. }
  769. myChartScale.setOption(option)
  770. myChartScale.getZr().on('click', params => {
  771. const data = storage.get('paramsValue')
  772. const timestamp = moment(data[0].name).unix()
  773. _this.childQueryParams.start_time = timestamp
  774. _this.childQueryParams.end_time = timestamp
  775. _this.handelgetBizDetail()
  776. })
  777. },
  778. goto(row) {
  779. const datetime = Date.parse(row.datetime) / 1000
  780. const href = this.$router.resolve({
  781. path: '/latency/index',
  782. query: {
  783. id: row.trace_id,
  784. span_id: row.span_id,
  785. datetime: datetime
  786. }
  787. })
  788. window.open(window.location.origin + '/' + href.href, '_blank')
  789. },
  790. sortChangeTable(val) {
  791. const prop = val.prop
  792. this.childQueryParams.sort_field = prop === 'duration' ? 'Duration' : 'Timestamp'
  793. // desc(倒序)和asc(正序)
  794. if (val.order === 'descending') {
  795. this.childQueryParams.sort_type = 'DESC'
  796. } else if (val.order === 'ascending') {
  797. this.childQueryParams.sort_type = 'ASC'
  798. }
  799. this.handelgetBizDetail()
  800. },
  801. handelCheckChange() {
  802. this.childQueryParams.min_duration = this.checked ? 2500 : 0
  803. this.handelgetBizDetail()
  804. },
  805. handelChange() {
  806. this.childQueryParams.only_exception = this.onlyException ? 1 : 0
  807. this.handelgetBizDetail()
  808. }
  809. }
  810. }
  811. </script>
  812. <style lang="scss" scoped>
  813. .node-details{
  814. .node-details-content{
  815. // width:420px;
  816. padding:0 36px;
  817. overflow-y: scroll;
  818. font-family: proxima-nova, Helvetica, Arial, sans-serif;
  819. }
  820. .node-details-content-section{
  821. margin: 24px 0px;
  822. }
  823. .node-details-content-section-header{
  824. font-size: 14px;
  825. color:rgb(133,133,173);
  826. margin-bottom:20px;
  827. }
  828. .node-details-info-field{
  829. display: flex;
  830. -webkit-box-align: baseline;
  831. box-align:baseline;
  832. align-items: baseline;
  833. margin-bottom: 8px;
  834. }
  835. .node-details-info-field-label{
  836. text-align: right;
  837. // width: 30%;
  838. color: rgb(92, 92, 138);
  839. padding: 0px 0.5em 0px 0px;
  840. white-space: nowrap;
  841. font-size: 14px;
  842. overflow: hidden;
  843. text-overflow: ellipsis;
  844. white-space:nowrap;
  845. }
  846. .node-details-info-field-value{
  847. font-size: 14px;
  848. // flex: 0 0 0%;
  849. min-width: 0px;
  850. color: rgb(61, 61, 92);
  851. overflow: hidden;
  852. text-overflow: ellipsis;
  853. white-space:nowrap;
  854. }
  855. .truncate{
  856. overflow: hidden;
  857. text-overflow: ellipsis;
  858. white-space:nowrap;
  859. }
  860. .w50{
  861. width:50%;
  862. }
  863. }
  864. .ob-switch-box{
  865. text-align: right;
  866. width: 100%;
  867. margin-bottom: 10px;
  868. }
  869. ::v-deep .el-drawer__header {
  870. padding:16px;
  871. background:#1890ff;
  872. color:#fff;
  873. }
  874. ::v-deep .el-page-header__title{
  875. margin-top:3px;
  876. }
  877. </style>