NodeDetailDrawer.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. <template>
  2. <!-- 点击节点后出现的 右侧抽屉 -->
  3. <div class="nodeDrawer">
  4. <el-drawer :visible.sync="isShow" direction="rtl" size="60%" append-to-body @close="drawerClose">
  5. <h4 slot="title" style="font-size:16px">节点相关信息</h4>
  6. <div class="node-details">
  7. <div class="node-details-content">
  8. <div class="node-details-flex">
  9. <div class="node-details-content-section node-details-warper" :style="computedStyle">
  10. <div style="display:flex;justify-content: space-between;">
  11. <div class="node-details-content-section-header">节点信息</div>
  12. <div>
  13. <el-button size="mini" type="primary" plain @click="editNodeName()">修改节点别名</el-button>
  14. </div>
  15. </div>
  16. <div style="margin-top:25px">
  17. <div class="node-details-info-field" style="position:relative;display:flex;">
  18. <div class="node-details-info-field-label w50" style="width:20%">节点名称:</div>
  19. <div class="node-details-info-field-value" style="width:70%">
  20. {{ detailData.name }}
  21. </div>
  22. </div>
  23. <div class="node-details-info-field">
  24. <div class="node-details-info-field-label w50" style="width:20%">所属服务:</div>
  25. <!-- 目前后端没返回服务别名,中文名的字段 -->
  26. <div
  27. class="node-details-info-field-value w50"
  28. style="width:70%;color:#1890ff;cursor: pointer;"
  29. @click="gotoPathService('/service/serviceDetail/index',detailData.service_name)"
  30. >
  31. {{ detailData.service_name }}
  32. </div>
  33. </div>
  34. <div class="node-details-info-field">
  35. <div class="node-details-info-field-label w50" style="width:20%">Span名称:</div>
  36. <div class="node-details-info-field-value w50" style="width:70%">{{ detailData.span_name }}
  37. </div>
  38. </div>
  39. <div class="node-details-info-field">
  40. <div class="node-details-info-field-label w50" style="width:20%">Span类型:</div>
  41. <div class="node-details-info-field-value w50" style="width:70%">{{ detailData.span_kind }}
  42. </div>
  43. </div>
  44. <div class="node-details-info-field">
  45. <div class="node-details-info-field-label w50" style="width:20%">成功率:</div>
  46. <div class="node-details-info-field-value w50" style="width:70%">{{ ((detailData.rate || 0
  47. ) * 100).toFixed(2) + '%' }}</div>
  48. </div>
  49. </div>
  50. </div>
  51. <div v-if="detailData.span_type == &quot;http server&quot;" class="node-details-content-section node-details-warper">
  52. <div class="node-details-content-section-header">接口信息</div>
  53. <div class="node-details-info-field">
  54. <div class="node-details-info-field-label w50" style="width:20%">请求量:</div>
  55. <div class="node-details-info-field-value w50" style="width:70%">{{ nodeStats.total }}</div>
  56. </div>
  57. <div class="node-details-info-field">
  58. <div class="node-details-info-field-label w50" style="width:20%">错误率:</div>
  59. <div class="node-details-info-field-value w50" style="width:70%">{{ (nodeStats.error_rate || 0 ).toFixed(4) + '%' }}</div>
  60. </div>
  61. <div class="node-details-info-field">
  62. <div class="node-details-info-field-label w50" style="width:20%">P50:</div>
  63. <div class="node-details-info-field-value w50" style="width:70%">{{ (nodeStats.p50 ||
  64. 0).toFixed(2) + 'ms' }}</div>
  65. </div>
  66. <div class="node-details-info-field">
  67. <div class="node-details-info-field-label w50" style="width:20%">P90:</div>
  68. <div class="node-details-info-field-value w50" style="width:70%">{{ (nodeStats.p90 ||
  69. 0).toFixed(2) + 'ms' }}</div>
  70. </div>
  71. <div class="node-details-info-field">
  72. <div class="node-details-info-field-label w50" style="width:20%">P99:</div>
  73. <div class="node-details-info-field-value w50" style="width:70%">{{ (nodeStats.p99 ||
  74. 0).toFixed(2) + 'ms' }}</div>
  75. </div>
  76. <div>
  77. <div class="node-details-info-field">
  78. <div class="node-details-info-field-label w50" style="width:20%">接口名:</div>
  79. <div class="node-details-info-field-value w50" style="width:70%;">{{ nodeStats.name }}</div>
  80. </div>
  81. <div class="node-details-info-field">
  82. <div class="node-details-info-field-label w50" style="width:20%">接口地址:</div>
  83. <div class="node-details-info-field-value w50" style="width:70%">{{ nodeStats.value }}</div>
  84. </div>
  85. </div>
  86. </div>
  87. </div>
  88. <div class="node-details-content-section">
  89. <div class="node-details-content-section-header">Span列表</div>
  90. <div>
  91. <el-table v-loading="nodeloading" :data="nodeSpansList" @row-click="handleSpanRowClick" @sort-change="sortChangeTable">
  92. <el-table-column label="链路标识" prop="trace_id" :show-overflow-tooltip="true" />
  93. <el-table-column label="Method" prop="method" :show-overflow-tooltip="true" align="center" />
  94. <el-table-column label="Code" prop="code" :show-overflow-tooltip="true" align="center">
  95. <template slot-scope="scope">
  96. <span>
  97. <el-tag
  98. v-if="scope.row.code < 400"
  99. class="colorwhite"
  100. style="color:white"
  101. type="success"
  102. effect="dark"
  103. >{{ scope.row.code }}</el-tag>
  104. <el-tag v-else color="#F53F3F" style="color:white; border-color: rgb(245, 63, 63);" type="danger">{{ scope.row.code }}</el-tag>
  105. </span>
  106. </template>
  107. </el-table-column>
  108. <el-table-column
  109. label="Duration(ms)"
  110. sortable="custom"
  111. prop="duration"
  112. :show-overflow-tooltip="true"
  113. align="center"
  114. >
  115. <template slot-scope="scope">
  116. <el-tag v-if="scope.row.duration >= 2000" type="danger">{{ parseInt(scope.row.duration || 0)
  117. }}</el-tag>
  118. <el-tag v-if="scope.row.duration < 2000" type="info">{{ parseInt(scope.row.duration || 0) }}</el-tag>
  119. </template>
  120. </el-table-column>
  121. </el-table>
  122. <pagination
  123. v-show="nodeCount > 0"
  124. small
  125. :total="nodeCount"
  126. :page.sync="querySpans.pageIndex"
  127. :limit.sync="querySpans.pageSize"
  128. @pagination="getNodeSpans"
  129. />
  130. </div>
  131. </div>
  132. </div>
  133. </div>
  134. </el-drawer>
  135. <!-- 修改节点别名 对话框 -->
  136. <el-dialog
  137. v-if="nodeOpen"
  138. append-to-body
  139. title="修改节点别名"
  140. :visible.sync="nodeOpen"
  141. width="700px"
  142. :center="true"
  143. :close-on-click-modal="false"
  144. >
  145. <el-form ref="queryName" :model="queryName" label-width="95px">
  146. <el-form-item label="节点别名" prop="name">
  147. <el-input v-model="queryName.name" placeholder="请输入节点别名" />
  148. </el-form-item>
  149. </el-form>
  150. <div slot="footer" class="dialog-footer">
  151. <el-button type="primary" @click="saveNodeName">确 定</el-button>
  152. <el-button @click="cancelNode">取 消</el-button>
  153. </div>
  154. </el-dialog>
  155. <LinkDetailsDrawer
  156. v-if="isShowLinkDrawer"
  157. :is-opendrawer="isShowLinkDrawer"
  158. :tract-i-d="tractID"
  159. :span-i-d="spanID"
  160. @handleClose="closeLinkDrawer"
  161. />
  162. </div>
  163. </template>
  164. <script>
  165. import { bizNodeSpans, bizNodeName, bizNodeStats } from '@/api/mapping'
  166. import storage from '@/utils/storage'
  167. import LinkDetailsDrawer from './LinkDetailsDrawer.vue'
  168. export default {
  169. components: {
  170. LinkDetailsDrawer
  171. },
  172. props: {
  173. detailData: {
  174. type: Object,
  175. default: () => {
  176. return {}
  177. }
  178. },
  179. nodeDrawerShow: {
  180. type: Boolean,
  181. default: false
  182. },
  183. spanId: {
  184. type: Number,
  185. default: 0
  186. },
  187. queryName: {
  188. type: Object,
  189. default: () => {
  190. return {}
  191. }
  192. },
  193. spanType: {
  194. type: String,
  195. default: ''
  196. }
  197. },
  198. data() {
  199. return {
  200. nodeCount: 0,
  201. nodeSpansList: [],
  202. querySpans: {
  203. id: 0,
  204. // hash:'',//和id二选一
  205. start_time: '',
  206. end_time: '',
  207. pageIndex: 1,
  208. pageSize: 10,
  209. sort_field: 'Duration',
  210. sort_type: 'DESC'
  211. },
  212. queryStats: {
  213. id: this.spanId,
  214. start_time: 0,
  215. end_time: 0
  216. },
  217. nodeloading: false,
  218. appItem: {},
  219. nodeOpen: false,
  220. nodeStats: {},
  221. isShow: this.nodeDrawerShow,
  222. isShowLinkDrawer: false,
  223. tractID: '',
  224. spanID: ''
  225. }
  226. },
  227. computed: {
  228. computedStyle() {
  229. return {
  230. width: this.spanType == 'http server' ? '49%' : '100%'
  231. }
  232. }
  233. },
  234. watch: {
  235. '$store.state.time.globalTimes': {
  236. handler(newValue, oldValue) {
  237. if (newValue) {
  238. this.querySpans.start_time = newValue.startTime
  239. this.querySpans.end_time = newValue.endTime
  240. this.querySpans.pageIndex = 1
  241. this.queryStats.start_time = newValue.startTime
  242. this.queryStats.end_time = newValue.endTime
  243. }
  244. },
  245. deep: true
  246. }
  247. },
  248. created() {
  249. this.appItem = storage.get('appsItem')
  250. if (JSON.stringify(this.appItem) != '{}') {
  251. const start_time = this.$store.state.time.globalTimes.startTime
  252. const end_time = this.$store.state.time.globalTimes.endTime
  253. this.querySpans.start_time = start_time
  254. this.querySpans.end_time = end_time
  255. this.queryStats.start_time = start_time
  256. this.queryStats.end_time = end_time
  257. this.getNodeSpans()
  258. if (this.spanType == 'http server') {
  259. this.getNodeStats()
  260. }
  261. }
  262. },
  263. methods: {
  264. getNodeSpans() {
  265. this.nodeloading = true
  266. this.querySpans.id = this.spanId
  267. bizNodeSpans(this.querySpans).then(res => {
  268. if (res.code === 200) {
  269. this.nodeloading = false
  270. this.nodeSpansList = res.data.list
  271. this.nodeCount = res.data.count
  272. }
  273. })
  274. },
  275. handleSpanRowClick(row, column, event) {
  276. this.isShowLinkDrawer = true
  277. this.spanID = row.span_id
  278. this.tractID = row.trace_id
  279. },
  280. closeLinkDrawer() {
  281. this.isShowLinkDrawer = false
  282. },
  283. drawerClose() {
  284. this.$emit('drawerClose', false)
  285. },
  286. editNodeName() {
  287. this.nodeOpen = true
  288. },
  289. cancelNode() {
  290. this.nodeOpen = false
  291. },
  292. // 修改节点别名
  293. saveNodeName() {
  294. bizNodeName(this.queryName).then(res => {
  295. if (res.code === 200) {
  296. this.msgSuccess(res.msg)
  297. this.nodeOpen = false
  298. this.drawerClose()
  299. }
  300. })
  301. },
  302. getNodeStats() {
  303. bizNodeStats(this.queryStats).then(res => {
  304. if (res && res.code == 200) {
  305. this.nodeStats = res.data
  306. }
  307. })
  308. },
  309. gotoPathService(path, service_name) {
  310. const href = this.$router.resolve({
  311. path: path,
  312. query: {
  313. service_name: service_name
  314. }
  315. })
  316. window.open(window.location.origin + '/' + href.href, '_blank')
  317. },
  318. sortChangeTable(val) {
  319. // desc(倒序)和asc(正序)
  320. if (val.order === 'descending') {
  321. this.querySpans.sort_type = 'DESC'
  322. } else if (val.order === 'ascending') {
  323. this.querySpans.sort_type = 'ASC'
  324. }
  325. this.getNodeSpans()
  326. }
  327. }
  328. }
  329. </script>
  330. <style lang="scss" scoped>
  331. .node-details {
  332. .node-details-content {
  333. overflow-y: scroll;
  334. font-family: proxima-nova, Helvetica, Arial, sans-serif;
  335. }
  336. .node-details-content-section {
  337. padding: 20px;
  338. border-radius: 12px;
  339. background: white;
  340. height: fit-content;
  341. margin-bottom: 20px;
  342. }
  343. .node-details-flex{
  344. display:flex;
  345. align-items: center;
  346. justify-content: space-between;
  347. background: #F0F2F5 !important;
  348. }
  349. .node-details-warper{
  350. width: 49%;
  351. max-height:270px;
  352. min-height: 244px;
  353. box-sizing: border-box;
  354. overflow: overlay;
  355. }
  356. .node-details-content-section-header {
  357. font-size: 14px;
  358. color: #000;
  359. margin-bottom: 20px;
  360. }
  361. .node-details-info-field {
  362. display: flex;
  363. -webkit-box-align: baseline;
  364. box-align: baseline;
  365. align-items: baseline;
  366. margin-bottom: 8px;
  367. }
  368. .node-details-info-field-label {
  369. text-align: right;
  370. color: #000;
  371. padding: 0px 0.5em 0px 0px;
  372. white-space: nowrap;
  373. font-size: 14px;
  374. overflow: hidden;
  375. text-overflow: ellipsis;
  376. white-space: nowrap;
  377. }
  378. .node-details-info-field-value {
  379. font-size: 14px;
  380. min-width: 0px;
  381. color: rgb(61, 61, 92);
  382. word-wrap: break-word;
  383. // overflow: hidden;
  384. // text-overflow: ellipsis;
  385. // white-space: nowrap;
  386. }
  387. .truncate {
  388. overflow: hidden;
  389. text-overflow: ellipsis;
  390. white-space: nowrap;
  391. }
  392. .w50 {
  393. width: 50%;
  394. }
  395. }
  396. ::v-deep .el-drawer__body {
  397. background: #F0F2F5;
  398. }
  399. ::v-deep .el-drawer__header {
  400. text-align: center;
  401. color: white;
  402. font-size: 24px;
  403. font-weight: bold;
  404. height: 60px;
  405. background: #4E5969;
  406. padding: 20px;
  407. margin-bottom: 0;
  408. }
  409. ::v-deep .el-table__row{
  410. cursor: pointer;
  411. }
  412. </style>