45 Commits

Author SHA1 Message Date
Fine0830
1f98619a5b fix: update attached event's details (#200) 2022-12-02 15:03:29 +08:00
Fine0830
aab44626da fix: update attached event details (#199) 2022-12-01 13:06:46 +08:00
Fine0830
0ff5d4d6bb fix: update attached event‘s occurrence date (#198) 2022-12-01 10:47:30 +08:00
Fine0830
611731d6d0 fix: update attached event details (#197) 2022-11-30 16:48:49 +08:00
Fine0830
221751f034 fix: optimize metrics association (#196) 2022-11-29 18:47:53 +08:00
Fine0830
d4dde7e73b fix: optimize config UI for related trace and metrics (#195) 2022-11-29 14:24:15 +08:00
Fine0830
5be106fc4f fix: add ProcessRelation to entity types (#194) 2022-11-28 22:49:23 +08:00
Fine0830
d8f91bbdf3 feat: Implement creating tasks UI for network profiling widget (#193) 2022-11-28 17:57:23 +08:00
Fine0830
23e9742946 feat: enhance associating metrics with traces by refId (#192) 2022-11-25 17:33:51 +08:00
Fine0830
7a1c83b5fb fix: update metric processor for the readRecords and remove readSampledRecords from metrics selector (#191) 2022-11-24 18:51:21 +08:00
Fine0830
7d802d490e feat: visualize attached events on the trace widget (#190) 2022-11-24 11:19:25 +08:00
Fine0830
da1db8def6 fix: update query conditions for metrics related traces (#189) 2022-11-22 22:39:22 +08:00
Fine0830
2230d05508 fix: optimize metrics related trace (#188) 2022-11-21 14:28:37 +08:00
WD
e8d909792d chore: fix typo (#187) 2022-11-21 10:07:01 +08:00
Fine0830
dc842609ba fix: update condition logic for trace tree data (#186) 2022-11-19 16:53:15 +08:00
drgnchan
670bef1d69 fix typo (#185) 2022-11-18 11:55:52 +08:00
Fine0830
882828b04a feat: enhance tags component to search tags with the input value (#184) 2022-11-17 17:57:23 +08:00
WD
ed6fb0448b feat: solve the problem of floating loading (#183) 2022-11-17 16:07:18 +08:00
Fine0830
a0fc879eb1 feat: enhance graph legend for the single metric (#182) 2022-11-10 15:13:17 +08:00
Fine0830
b37d65eaac feat: enhance the legend of metrics graph widget with the summary table (#181) 2022-11-10 14:55:19 +08:00
heihei180
fd46211a37 add apache eventMesh logo file (#180) 2022-11-06 15:12:30 +08:00
Fine0830
ae0b8c056d fix trace profiling widget, select the first span by default (#179) 2022-11-04 20:31:46 +08:00
Fine0830
4b88d8bbb3 fix: reset tag keys list and duration condition (#178) 2022-10-31 12:05:25 +08:00
Fine0830
09051e916b feat: support labeled value on the service/instance/endpoint list widgets (#177) 2022-10-31 10:27:37 +08:00
Fine0830
e597f91448 remove unuse icon (#176) 2022-10-26 21:53:27 +08:00
Fine0830
4232161d36 fix: set selector props and update configuration panel styles (#175) 2022-10-25 16:48:49 +08:00
Fine0830
eda44db0cd feat: associate metrics with trace widget on dashboards (#174) 2022-10-25 11:36:49 +08:00
pg.yang
78f0096c00 add menu for virtual mq (#173) 2022-10-20 20:36:28 +08:00
Fine0830
5e161f17c2 feat: add readRecords to metric types (#172) 2022-10-17 21:55:14 +08:00
WD
77d189cdfb fix: added name verification to avoid creating blank dashboard name (#171) 2022-10-17 21:25:59 +08:00
Fine0830
9f57e35119 revert logs on trace widget (#170) 2022-10-14 11:28:10 +08:00
dependabot[bot]
2bf90d6a6d build(deps): bump d3-color from 3.0.1 to 3.1.0 (#166) 2022-10-01 10:33:48 +08:00
Fine0830
0f4319499a fix: query logs with the specific service ID (#165) 2022-09-30 10:25:05 +08:00
pg.yang
5bb58a00cd feat: add gateway,apisix menu (#163) 2022-09-27 10:36:15 +08:00
吴晟 Wu Sheng
d50e9fc261 Remove commented codes (#162) 2022-09-21 12:34:06 +08:00
Fine0830
b235929c77 feat: enhance menu configuration to make it easier to change (#161) 2022-09-20 16:24:42 +08:00
Fine0830
4561e2e374 fix: remove All from the endpoints selector from profiling 2022-09-19 14:05:09 +08:00
pg.yang
214b34ddfd feat: add virtual cache dashboard (#159) 2022-09-19 10:02:22 +08:00
Fine0830
26817e9f92 feat: enhance the process topology graph to support dragging nodes (#158) 2022-09-15 17:18:39 +08:00
Fine0830
9ed0121fd0 fix: update styles for an adaptive height (#157) 2022-09-13 16:31:30 +08:00
Fine0830
0d63d538c3 fix: set up a new time range after clicking the refresh button (#156) 2022-09-08 22:41:02 +08:00
Fine0830
5da441ff9a fix: polish the endpoint list graph (#155) 2022-09-08 21:46:37 +08:00
kezhenxu94
49bc349064 Keep package.json and package-lock.json in sync (#154) 2022-09-06 14:31:52 +08:00
Lv Lifeng
61a4d2f759 Add impala icon 4 impala jdbc plugin (#153) 2022-09-03 19:45:54 +08:00
云泥
0b4e738699 fix: tab active incorrectly, when click tab space (#152) 2022-09-01 15:49:15 +08:00
111 changed files with 3631 additions and 1142 deletions

View File

@@ -44,9 +44,9 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: npm install, lint, build, and test
- name: npm ci, lint, build, and test
run: |
npm install
npm ci
npm run lint
npm run build --if-present
npm run test:unit

221
package-lock.json generated
View File

@@ -510,9 +510,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.16.8",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.8.tgz",
"integrity": "sha512-i7jDUfrVBWc+7OKcBzEe5n7fbv3i2fWtxKzzCvOjnzSxMfWMigAhtfJ7qzZNGFNMsCCd67+uz553dYKWXPvCKw==",
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz",
"integrity": "sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==",
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -3433,8 +3433,16 @@
"node_modules/@types/lodash": {
"version": "4.14.179",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.179.tgz",
"integrity": "sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w==",
"dev": true
"integrity": "sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w=="
},
"node_modules/@types/lodash-es": {
"version": "4.17.6",
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.6.tgz",
"integrity": "sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==",
"peer": true,
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/mime": {
"version": "1.3.2",
@@ -4948,6 +4956,46 @@
"@vue/cli-service": "^3.0.0 || ^4.0.0-0"
}
},
"node_modules/@vue/cli-plugin-unit-jest/node_modules/@vue/compiler-sfc": {
"version": "2.7.10",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.10.tgz",
"integrity": "sha512-55Shns6WPxlYsz4WX7q9ZJBL77sKE1ZAYNYStLs6GbhIOMrNtjMvzcob6gu3cGlfpCR4bT7NXgyJ3tly2+Hx8Q==",
"dev": true,
"peer": true,
"dependencies": {
"@babel/parser": "^7.18.4",
"postcss": "^8.4.14",
"source-map": "^0.6.1"
}
},
"node_modules/@vue/cli-plugin-unit-jest/node_modules/@vue/compiler-sfc/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@vue/cli-plugin-unit-jest/node_modules/csstype": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
"integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==",
"dev": true,
"peer": true
},
"node_modules/@vue/cli-plugin-unit-jest/node_modules/vue": {
"version": "2.7.10",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.10.tgz",
"integrity": "sha512-HmFC70qarSHPXcKtW8U8fgIkF6JGvjEmDiVInTkKZP0gIlEPhlVlcJJLkdGIDiNkIeA2zJPQTWJUI4iWe+AVfg==",
"dev": true,
"peer": true,
"dependencies": {
"@vue/compiler-sfc": "2.7.10",
"csstype": "^3.1.0"
}
},
"node_modules/@vue/cli-plugin-unit-jest/node_modules/vue-jest": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/vue-jest/-/vue-jest-3.0.7.tgz",
@@ -9586,9 +9634,9 @@
"integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A=="
},
"node_modules/d3-color": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.0.1.tgz",
"integrity": "sha512-6/SlHkDOBLyQSJ1j1Ghs82OIUXpKWlR0hCsw0XrLSQhuUPuCSmLQ1QPH98vpnQxMUQM2/gfAkUEWsupVpd9JGw==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
"engines": {
"node": ">=12"
}
@@ -9967,6 +10015,13 @@
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz",
"integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig=="
},
"node_modules/de-indent": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
"integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
"dev": true,
"peer": true
},
"node_modules/deasync": {
"version": "0.1.24",
"resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.24.tgz",
@@ -18831,9 +18886,9 @@
"dev": true
},
"node_modules/nanoid": {
"version": "3.1.32",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.32.tgz",
"integrity": "sha512-F8mf7R3iT9bvThBoW4tGXhXFHCctyCiUUPrWF8WaTqa3h96d9QybkSeba43XVOOE3oiLfkVDe4bT8MeGmkrTxw==",
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@@ -20451,20 +20506,26 @@
}
},
"node_modules/postcss": {
"version": "8.4.5",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz",
"integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==",
"version": "8.4.16",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz",
"integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
}
],
"dependencies": {
"nanoid": "^3.1.30",
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.1"
"source-map-js": "^1.0.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
}
},
"node_modules/postcss-calc": {
@@ -24413,9 +24474,9 @@
}
},
"node_modules/source-map-js": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.1.tgz",
"integrity": "sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"engines": {
"node": ">=0.10.0"
}
@@ -27845,6 +27906,17 @@
"integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=",
"dev": true
},
"node_modules/vue-template-compiler": {
"version": "2.7.10",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.10.tgz",
"integrity": "sha512-QO+8R9YRq1Gudm8ZMdo/lImZLJVUIAM8c07Vp84ojdDAf8HmPJc7XB556PcXV218k2AkKznsRz6xB5uOjAC4EQ==",
"dev": true,
"peer": true,
"dependencies": {
"de-indent": "^1.0.2",
"he": "^1.2.0"
}
},
"node_modules/vue-template-es2015-compiler": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz",
@@ -29975,9 +30047,9 @@
}
},
"@babel/parser": {
"version": "7.16.8",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.8.tgz",
"integrity": "sha512-i7jDUfrVBWc+7OKcBzEe5n7fbv3i2fWtxKzzCvOjnzSxMfWMigAhtfJ7qzZNGFNMsCCd67+uz553dYKWXPvCKw=="
"version": "7.19.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz",
"integrity": "sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw=="
},
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
"version": "7.16.7",
@@ -32249,8 +32321,16 @@
"@types/lodash": {
"version": "4.14.179",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.179.tgz",
"integrity": "sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w==",
"dev": true
"integrity": "sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w=="
},
"@types/lodash-es": {
"version": "4.17.6",
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.6.tgz",
"integrity": "sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==",
"peer": true,
"requires": {
"@types/lodash": "*"
}
},
"@types/mime": {
"version": "1.3.2",
@@ -33439,6 +33519,45 @@
"vue-jest": "^3.0.5"
},
"dependencies": {
"@vue/compiler-sfc": {
"version": "2.7.10",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.10.tgz",
"integrity": "sha512-55Shns6WPxlYsz4WX7q9ZJBL77sKE1ZAYNYStLs6GbhIOMrNtjMvzcob6gu3cGlfpCR4bT7NXgyJ3tly2+Hx8Q==",
"dev": true,
"peer": true,
"requires": {
"@babel/parser": "^7.18.4",
"postcss": "^8.4.14",
"source-map": "^0.6.1"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"peer": true
}
}
},
"csstype": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
"integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==",
"dev": true,
"peer": true
},
"vue": {
"version": "2.7.10",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.10.tgz",
"integrity": "sha512-HmFC70qarSHPXcKtW8U8fgIkF6JGvjEmDiVInTkKZP0gIlEPhlVlcJJLkdGIDiNkIeA2zJPQTWJUI4iWe+AVfg==",
"dev": true,
"peer": true,
"requires": {
"@vue/compiler-sfc": "2.7.10",
"csstype": "^3.1.0"
}
},
"vue-jest": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/vue-jest/-/vue-jest-3.0.7.tgz",
@@ -37173,9 +37292,9 @@
"integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A=="
},
"d3-color": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.0.1.tgz",
"integrity": "sha512-6/SlHkDOBLyQSJ1j1Ghs82OIUXpKWlR0hCsw0XrLSQhuUPuCSmLQ1QPH98vpnQxMUQM2/gfAkUEWsupVpd9JGw=="
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="
},
"d3-contour": {
"version": "3.0.1",
@@ -37456,6 +37575,13 @@
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz",
"integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig=="
},
"de-indent": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
"integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
"dev": true,
"peer": true
},
"deasync": {
"version": "0.1.24",
"resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.24.tgz",
@@ -44424,9 +44550,9 @@
"dev": true
},
"nanoid": {
"version": "3.1.32",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.32.tgz",
"integrity": "sha512-F8mf7R3iT9bvThBoW4tGXhXFHCctyCiUUPrWF8WaTqa3h96d9QybkSeba43XVOOE3oiLfkVDe4bT8MeGmkrTxw=="
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
},
"nanomatch": {
"version": "1.2.13",
@@ -45679,13 +45805,13 @@
"dev": true
},
"postcss": {
"version": "8.4.5",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.5.tgz",
"integrity": "sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==",
"version": "8.4.16",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz",
"integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==",
"requires": {
"nanoid": "^3.1.30",
"nanoid": "^3.3.4",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.1"
"source-map-js": "^1.0.2"
},
"dependencies": {
"picocolors": {
@@ -48795,9 +48921,9 @@
"dev": true
},
"source-map-js": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.1.tgz",
"integrity": "sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA=="
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
},
"source-map-resolve": {
"version": "0.5.3",
@@ -51480,6 +51606,17 @@
}
}
},
"vue-template-compiler": {
"version": "2.7.10",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.10.tgz",
"integrity": "sha512-QO+8R9YRq1Gudm8ZMdo/lImZLJVUIAM8c07Vp84ojdDAf8HmPJc7XB556PcXV218k2AkKznsRz6xB5uOjAC4EQ==",
"dev": true,
"peer": true,
"requires": {
"de-indent": "^1.0.2",
"he": "^1.2.0"
}
},
"vue-template-es2015-compiler": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz",

View File

@@ -19,7 +19,7 @@ limitations under the License. -->
#app {
color: #2c3e50;
height: 100%;
overflow: auto;
overflow: hidden;
min-width: 1024px;
}
</style>

View File

@@ -0,0 +1,15 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg t="1667899293763" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4705" width="16" height="16"><path d="M512 512m-368 0a368 368 0 1 0 736 0 368 368 0 1 0-736 0Z" p-id="4706"></path></svg>

After

Width:  |  Height:  |  Size: 1001 B

View File

@@ -0,0 +1,16 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg t="1666624449554" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2649" width="48" height="48"><path d="M381.482667 673.877333a90.389333 90.389333 0 0 1 85.226666 60.245334H853.333333v64H465.28a90.389333 90.389333 0 0 1-167.573333 0H170.666667v-64h125.610666a90.389333 90.389333 0 0 1 85.205334-60.245334z m0 64a26.346667 26.346667 0 1 0 0 52.693334 26.346667 26.346667 0 0 0 0-52.693334z m261.034666-304.938666a90.389333 90.389333 0 0 1 85.205334 60.245333H853.333333v64h-127.04a90.389333 90.389333 0 0 1-167.573333 0H170.666667v-64h386.624a90.389333 90.389333 0 0 1 85.226666-60.245333z m0 64a26.346667 26.346667 0 1 0 0 52.693333 26.346667 26.346667 0 0 0 0-52.693333zM381.482667 192a90.389333 90.389333 0 0 1 85.226666 60.224H853.333333v64H465.28a90.389333 90.389333 0 0 1-167.573333 0H170.666667v-64h125.610666A90.389333 90.389333 0 0 1 381.482667 192z m0 64a26.346667 26.346667 0 1 0 0 52.693333 26.346667 26.346667 0 0 0 0-52.693333z" p-id="2650"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

15
src/assets/icons/copy.svg Normal file
View File

@@ -0,0 +1,15 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg t="1664265269855" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4109" width="48" height="48"><path d="M866.461538 39.384615H354.461538c-43.323077 0-78.769231 35.446154-78.76923 78.769231v39.384616h472.615384c43.323077 0 78.769231 35.446154 78.769231 78.76923v551.384616h39.384615c43.323077 0 78.769231-35.446154 78.769231-78.769231V118.153846c0-43.323077-35.446154-78.769231-78.769231-78.769231z m-118.153846 275.692308c0-43.323077-35.446154-78.769231-78.76923-78.769231H157.538462c-43.323077 0-78.769231 35.446154-78.769231 78.769231v590.769231c0 43.323077 35.446154 78.769231 78.769231 78.769231h512c43.323077 0 78.769231-35.446154 78.76923-78.769231V315.076923z m-354.461538 137.846154c0 11.815385-7.876923 19.692308-19.692308 19.692308h-157.538461c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h157.538461c11.815385 0 19.692308 7.876923 19.692308 19.692308v39.384615z m157.538461 315.076923c0 11.815385-7.876923 19.692308-19.692307 19.692308H216.615385c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h315.076923c11.815385 0 19.692308 7.876923 19.692307 19.692308v39.384615z m78.769231-157.538462c0 11.815385-7.876923 19.692308-19.692308 19.692308H216.615385c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h393.846153c11.815385 0 19.692308 7.876923 19.692308 19.692308v39.384615z" p-id="4110"></path></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,15 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg t="1664244255409" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2266" width="48" height="48"><path d="M523.776 430.592l-153.088 88.576 153.088 88.576 152.576-88.576-152.576-88.576z m-165.888 108.544l0.512 177.152 153.6 87.552-1.024-176.64-153.088-88.064z m330.24 0l-153.6 87.552-1.024 176.64 153.6-87.552 1.024-176.64z m131.072 205.824l-68.096-40.96 39.936-8.704-5.632-26.112-67.072 14.848-13.824 23.04 101.376 60.928 13.312-23.04z m-142.848 7.68l68.096 40.96-39.936 8.704 5.632 26.112 67.072-14.848 13.824-23.04-101.888-60.928-12.8 23.04zM481.28 424.96h26.624V306.176H481.28v79.36l-27.648-29.696-19.456 18.432L481.28 424.96z m53.76-118.784V424.96h26.624V345.088l27.648 29.696 19.456-18.432-47.104-50.176h-26.624z m-190.464 401.92l-13.312-23.04-68.608 40.448 11.264-38.912-25.6-7.168-18.944 66.048 13.312 23.04 101.888-60.416z m-89.088 82.944l13.312 23.04 68.608-39.936-11.264 38.912 25.6 7.168 19.456-66.048-13.312-23.04-102.4 59.904z m622.08-45.056c-45.568 0-82.432 36.864-82.432 82.432 0 45.568 36.864 82.432 82.432 82.432 45.568 0 82.432-36.864 82.432-82.432 0-45.568-36.864-82.432-82.432-82.432z m0 122.88c-22.528 0-40.448-17.92-40.448-40.448s17.92-40.448 40.448-40.448 40.448 17.92 40.448 40.448-17.92 40.448-40.448 40.448zM521.728 292.864c45.568 0 82.432-36.864 82.432-82.432 0-45.568-36.864-82.432-82.432-82.432-45.568 0-82.432 36.864-82.432 82.432 0 45.568 36.864 82.432 82.432 82.432z m0-122.88c22.528 0 40.448 17.92 40.448 40.448s-17.92 40.448-40.448 40.448-40.448-17.92-40.448-40.448 18.432-40.448 40.448-40.448zM167.936 749.056c-45.568 0-82.432 36.864-82.432 82.432 0 45.568 36.864 82.432 82.432 82.432 45.568 0 82.432-36.864 82.432-82.432 0-45.568-36.864-82.432-82.432-82.432z m0 122.88c-22.528 0-40.448-17.92-40.448-40.448s17.92-40.448 40.448-40.448 40.448 17.92 40.448 40.448-18.432 40.448-40.448 40.448z" p-id="2267"></path></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -13,6 +13,5 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<title>info_outline</title>
<path d="M11.016 9v-2.016h1.969v2.016h-1.969zM12 20.016q3.281 0 5.648-2.367t2.367-5.648-2.367-5.648-5.648-2.367-5.648 2.367-2.367 5.648 2.367 5.648 5.648 2.367zM12 2.016q4.125 0 7.055 2.93t2.93 7.055-2.93 7.055-7.055 2.93-7.055-2.93-2.93-7.055 2.93-7.055 7.055-2.93zM11.016 17.016v-6h1.969v6h-1.969z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,15 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg t="1664266918236" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5378" width="48" height="48"><path d="M571.178667 643.328a144 144 0 0 1-189.098667-193.450667l77.781333 77.866667a48 48 0 1 0 67.882667-67.84l-77.824-77.909333a144 144 0 0 1 193.450667 189.141333l226.517333 207.061333a64.896 64.896 0 1 1-91.690667 91.690667l-207.018666-226.56z m51.498666 134.656a288.298667 288.298667 0 0 1-38.656 12.928v95.488c0 5.290667-4.309333 9.6-9.642666 9.6h-124.757334a9.6 9.6 0 0 1-9.6-9.6v-95.488a286.293333 286.293333 0 0 1-74.325333-30.805333l-67.541333 67.541333a9.6 9.6 0 0 1-13.568 0L196.352 739.413333a9.6 9.6 0 0 1 0-13.568l67.541333-67.541333a286.293333 286.293333 0 0 1-30.805333-74.325333H137.6A9.6 9.6 0 0 1 128 574.378667v-124.757334c0-5.290667 4.309333-9.6 9.6-9.6h95.488c6.826667-26.453333 17.28-51.370667 30.805333-74.325333L196.352 298.154667a9.6 9.6 0 0 1 0-13.568L284.586667 196.352a9.6 9.6 0 0 1 13.568 0l67.541333 67.498667a287.146667 287.146667 0 0 1 74.325333-30.848V137.6c0-5.290667 4.266667-9.6 9.6-9.6h124.8c5.248 0 9.6 4.309333 9.6 9.6v95.488c26.368 6.826667 51.328 17.28 74.282667 30.805333l67.541333-67.541333a9.6 9.6 0 0 1 13.568 0l88.234667 88.234667a9.6 9.6 0 0 1 0 13.568l-67.498667 67.541333a287.146667 287.146667 0 0 1 30.848 74.282667h95.402667c5.290667 0 9.6 4.352 9.6 9.642666v124.757334c0 5.333333-4.266667 9.6-9.6 9.6h-95.488c-4.693333 18.133333-11.178667 35.754667-19.328 52.650666a9.6 9.6 0 0 1-15.018667 2.986667l-10.112-9.173333-38.314666-34.261334-12.16-10.88a9.6 9.6 0 0 1-2.688-10.24A192.298667 192.298667 0 0 0 512 320a192 192 0 1 0 63.018667 373.333333 9.6 9.6 0 0 1 10.24 2.645334l10.837333 12.074666 35.285333 39.338667 8.149334 9.130667a9.6 9.6 0 0 1-2.901334 15.061333 283.306667 283.306667 0 0 1-13.952 6.4z" p-id="5379"></path></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -632,11 +632,6 @@ onMounted(() => {
right: 24px;
}
.calendar-next-month-btn .middle,
.calendar-prev-month-btn .middle {
margin-top: 8px;
}
.calendar-body {
position: relative;
width: 196px;

View File

@@ -15,6 +15,28 @@ limitations under the License. -->
<template>
<div class="chart" ref="chartRef" :style="`height:${height};width:${width};`">
<div v-if="!available" class="no-data">No Data</div>
<div class="menus" v-show="visMenus" ref="menus">
<div class="tools" @click="associateMetrics" v-if="associate.length">
{{ t("associateMetrics") }}
</div>
<div
class="tools"
@click="viewTrace"
v-if="relatedTrace && relatedTrace.enableRelate"
>
{{ t("viewTrace") }}
</div>
</div>
<el-drawer
v-model="showTrace"
size="100%"
:destroy-on-close="true"
:before-close="() => (showTrace = false)"
:append-to-body="true"
title="The Related Traces"
>
<Trace :data="traceOptions" />
</el-drawer>
</div>
</template>
<script lang="ts" setup>
@@ -28,15 +50,28 @@ import {
computed,
} from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { EventParams } from "@/types/app";
import { Filters, RelatedTrace } from "@/types/dashboard";
import { useECharts } from "@/hooks/useEcharts";
import { addResizeListener, removeResizeListener } from "@/utils/event";
import Trace from "@/views/dashboard/related/trace/Index.vue";
import associateProcessor from "@/hooks/useAssociateProcessor";
/*global Nullable, defineProps, defineEmits*/
const emits = defineEmits(["select"]);
const { t } = useI18n();
const chartRef = ref<Nullable<HTMLDivElement>>(null);
const menus = ref<Nullable<HTMLDivElement>>(null);
const visMenus = ref<boolean>(false);
const { setOptions, resize, getInstance } = useECharts(
chartRef as Ref<HTMLDivElement>
);
const currentParams = ref<Nullable<EventParams>>(null);
const showTrace = ref<boolean>(false);
const traceOptions = ref<{ type: string; filters?: unknown }>({
type: "Trace",
});
const props = defineProps({
height: { type: String, default: "100%" },
width: { type: String, default: "100%" },
@@ -45,15 +80,14 @@ const props = defineProps({
default: () => ({}),
},
filters: {
type: Object as PropType<{
duration: {
startTime: string;
endTime: string;
};
isRange: boolean;
dataIndex?: number;
sourceId: string;
}>,
type: Object as PropType<Filters>,
},
relatedTrace: {
type: Object as PropType<RelatedTrace>,
},
associate: {
type: Array as PropType<{ widgetId: string }[]>,
default: () => [],
},
});
const available = computed(
@@ -66,14 +100,34 @@ const available = computed(
onMounted(async () => {
await setOptions(props.option);
chartRef.value && addResizeListener(unref(chartRef), resize);
instanceEvent();
});
function instanceEvent() {
setTimeout(() => {
const instance = getInstance();
if (!instance) {
return;
}
instance.on("click", (params: unknown) => {
emits("select", params);
instance.on("click", (params: EventParams) => {
currentParams.value = params;
if (!menus.value || !chartRef.value) {
return;
}
visMenus.value = true;
const w = chartRef.value.getBoundingClientRect().width || 0;
const h = chartRef.value.getBoundingClientRect().height || 0;
if (w - params.event.offsetX > 120) {
menus.value.style.left = params.event.offsetX + "px";
} else {
menus.value.style.left = params.event.offsetX - 120 + "px";
}
if (h - params.event.offsetY < 50) {
menus.value.style.top = params.event.offsetY - 40 + "px";
} else {
menus.value.style.top = params.event.offsetY + 2 + "px";
}
});
document.addEventListener(
"click",
@@ -81,9 +135,7 @@ onMounted(async () => {
if (instance.isDisposed()) {
return;
}
instance.dispatchAction({
type: "hideTip",
});
visMenus.value = false;
instance.dispatchAction({
type: "updateAxisPointer",
currTrigger: "leave",
@@ -92,9 +144,18 @@ onMounted(async () => {
true
);
}, 1000);
});
}
function updateOptions() {
function associateMetrics() {
emits("select", currentParams.value);
const { dataIndex, seriesIndex } = currentParams.value || {
dataIndex: 0,
seriesIndex: 0,
};
updateOptions({ dataIndex, seriesIndex });
}
function updateOptions(params?: { dataIndex: number; seriesIndex: number }) {
const instance = getInstance();
if (!instance) {
return;
@@ -103,60 +164,32 @@ function updateOptions() {
return;
}
if (props.filters.isRange) {
const { eventAssociate } = associateProcessor(props);
const options = eventAssociate();
setOptions(options || props.option);
} else {
instance.dispatchAction({
type: "showTip",
dataIndex: props.filters.dataIndex,
seriesIndex: 0,
type: "updateAxisPointer",
dataIndex: params ? params.dataIndex : props.filters.dataIndex,
seriesIndex: params ? params.seriesIndex : 0,
});
const ids = props.option.series.map((_: unknown, index: number) => index);
instance.dispatchAction({
type: "highlight",
dataIndex: params ? params.dataIndex : props.filters.dataIndex,
seriesIndex: ids,
});
}
}
function eventAssociate() {
if (!props.filters) {
return;
}
if (!props.filters.duration) {
return props.option;
}
if (!props.option.series[0]) {
return;
}
const list = props.option.series[0].data.map(
(d: (number | string)[]) => d[0]
);
if (!list.includes(props.filters.duration.endTime)) {
return;
}
const markArea = {
silent: true,
itemStyle: {
opacity: 0.3,
},
data: [
[
{
xAxis: props.filters.duration.startTime,
},
{
xAxis: props.filters.duration.endTime,
},
],
],
function viewTrace() {
const item = associateProcessor(props).traceFilters(currentParams.value);
traceOptions.value = {
...traceOptions.value,
filters: item,
};
const series = (window as any).structuredClone(props.option.series);
for (const [key, temp] of series.entries()) {
if (key === 0) {
temp.markArea = markArea;
}
}
const options = {
...props.option,
series,
};
return options;
showTrace.value = true;
visMenus.value = true;
}
watch(
@@ -170,6 +203,7 @@ watch(
}
let options;
if (props.filters && props.filters.isRange) {
const { eventAssociate } = associateProcessor(props);
options = eventAssociate();
}
setOptions(options || props.option);
@@ -200,5 +234,30 @@ onBeforeUnmount(() => {
.chart {
overflow: hidden;
flex: 1;
}
.menus {
position: absolute;
display: block;
white-space: nowrap;
z-index: 9999999;
box-shadow: #ddd 1px 2px 10px;
transition: all cubic-bezier(0.075, 0.82, 0.165, 1) linear;
background-color: rgb(255, 255, 255);
border-radius: 4px;
color: rgb(51, 51, 51);
padding: 5px;
}
.tools {
padding: 5px;
color: #999;
cursor: pointer;
&:hover {
color: #409eff;
background-color: #eee;
}
}
</style>

View File

@@ -24,8 +24,8 @@ import { ref } from "vue";
import type { PropType } from "vue";
interface Option {
label: string;
value: string;
label: string | number;
value: string | number;
}
/*global defineProps, defineEmits */

View File

@@ -18,7 +18,6 @@ limitations under the License. -->
v-model="selected"
:placeholder="placeholder"
@change="changeSelected"
filterable
:multiple="multiple"
:disabled="disabled"
:style="{ borderRadius }"
@@ -26,6 +25,7 @@ limitations under the License. -->
:remote="isRemote"
:reserve-keyword="isRemote"
:remote-method="remoteMethod"
:filterable="filterable"
>
<el-option
v-for="item in options"
@@ -66,6 +66,7 @@ const props = defineProps({
disabled: { type: Boolean, default: false },
clearable: { type: Boolean, default: false },
isRemote: { type: Boolean, default: false },
filterable: { type: Boolean, default: true },
});
const selected = ref<string[] | string>(props.value);

View File

@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export enum TimeType {
MINUTE_TIME = "MINUTE",
HOUR_TIME = "HOUR",
@@ -25,33 +26,3 @@ export const Languages = [
{ label: "Chinese", value: "zh" },
{ label: "Spanish", value: "es" },
];
export const RoutesMap: { [key: string]: string } = {
GeneralServices: "GENERAL",
GeneralServicesActiveTabIndex: "GENERAL",
VirtualDatabase: "VIRTUAL_DATABASE",
VirtualDatabaseActiveTabIndex: "VIRTUAL_DATABASE",
MeshServices: "MESH",
MeshServicesActiveTabIndex: "MESH",
ControlPanel: "MESH_CP",
ControlPanelActiveTabIndex: "MESH_CP",
DataPanel: "MESH_DP",
DataPanelActiveTabIndex: "MESH_DP",
Linux: "OS_LINUX",
SkyWalkingServer: "SO11Y_OAP",
SkyWalkingServerActiveTabIndex: "SO11Y_OAP",
SatelliteActiveTabIndex: "SO11Y_SATELLITE",
Satellite: "SO11Y_SATELLITE",
Functions: "FAAS",
FunctionsActiveTabIndex: "FAAS",
Browser: "BROWSER",
BrowserActiveTabIndex: "BROWSER",
KubernetesCluster: "K8S",
KubernetesClusterActiveTabIndex: "K8S",
KubernetesService: "K8S_SERVICE",
KubernetesServiceActiveTabIndex: "K8S_SERVICE",
MySQL: "MYSQL",
MySQLActiveTabIndex: "MYSQL",
PostgreSQL: "POSTGRESQL",
PostgreSQLActiveTabIndex: "POSTGRESQL",
};

View File

@@ -69,6 +69,25 @@ export const TraceSpans = {
value
}
}
attachedEvents {
startTime {
seconds
nanos
}
event
endTime {
seconds
nanos
}
tags {
key
value
}
summary {
key
value
}
}
}
}
`,

View File

@@ -32,6 +32,6 @@ export const queryInstances = `query queryInstances(${Instances.variable}) {${In
export const queryLayers = `query listLayer {${Layers.query}}`;
export const queryService = `query queryService(${getService.variable}) {${getService.query}}`;
export const queryInstance = `query queryInstance(${getInstance.variable}) {${getInstance.query}}`;
export const queryEndpoint = `query queryInstance(${getEndpoint.variable}) {${getEndpoint.query}}`;
export const queryEndpoint = `query queryEndpoint(${getEndpoint.variable}) {${getEndpoint.query}}`;
export const queryProcesses = `query queryProcesses(${Processes.variable}) {${Processes.query}}`;
export const queryProcess = `query queryProcess(${getProcess.variable}) {${getProcess.query}}`;

View File

@@ -21,6 +21,7 @@ export enum MetricQueryTypes {
ReadLabeledMetricsValues = "readLabeledMetricsValues",
READHEATMAP = "readHeatMap",
ReadSampledRecords = "readSampledRecords",
ReadRecords = "readRecords",
}
export enum Calculations {
@@ -101,4 +102,10 @@ export const RespFields: any = {
value
refId
}`,
readRecords: `{
id
name
value
refId
}`,
};

View File

@@ -0,0 +1,138 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useAppStoreWithOut } from "@/store/modules/app";
import dateFormatStep from "@/utils/dateFormat";
import getLocalTime from "@/utils/localtime";
import { EventParams } from "@/types/app";
export default function associateProcessor(props: any) {
function eventAssociate() {
if (!props.filters) {
return;
}
if (!props.filters.duration) {
return props.option;
}
if (!props.option.series[0]) {
return;
}
const list = props.option.series[0].data.map(
(d: (number | string)[]) => d[0]
);
if (!list.includes(props.filters.duration.endTime)) {
return;
}
const markArea = {
silent: true,
itemStyle: {
opacity: 0.3,
},
data: [
[
{
xAxis: props.filters.duration.startTime,
},
{
xAxis: props.filters.duration.endTime,
},
],
],
};
const series = (window as any).structuredClone(props.option.series);
for (const [key, temp] of series.entries()) {
if (key === 0) {
temp.markArea = markArea;
}
}
const options = {
...props.option,
series,
};
return options;
}
function traceFilters(currentParams: Nullable<EventParams>) {
const appStore = useAppStoreWithOut();
if (!currentParams) {
return;
}
const start = appStore.intervalUnix[currentParams.dataIndex];
const { step } = appStore.durationRow;
let duration = undefined;
if (start) {
const end = start;
duration = {
start: dateFormatStep(
getLocalTime(appStore.utc, new Date(start)),
step,
true
),
end: dateFormatStep(
getLocalTime(appStore.utc, new Date(end)),
step,
true
),
step,
};
}
const relatedTrace = props.relatedTrace || {};
const status = relatedTrace.status;
const queryOrder = relatedTrace.queryOrder;
const latency = relatedTrace.latency;
const series = props.option.series || [];
const item: any = {
duration,
queryOrder,
status,
};
if (latency) {
const latencyList = series.map(
(d: { name: string; data: number[][] }, index: number) => {
const data = [
d.data[currentParams.dataIndex][1],
series[index + 1]
? series[index + 1].data[currentParams.dataIndex][1]
: Infinity,
];
return {
label:
d.name +
"--" +
(series[index + 1] ? series[index + 1].name : "Infinity"),
value: String(index),
data,
};
}
);
item.latency = latencyList;
}
const value = series.map(
(d: { name: string; data: number[][] }, index: number) => {
return {
label: d.name,
value: String(index),
data: d.data[currentParams.dataIndex][1],
date: d.data[currentParams.dataIndex][0],
};
}
);
item.metricValue = value;
return item;
}
return { eventAssociate, traceFilters };
}

View File

@@ -0,0 +1,142 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { LegendOptions } from "@/types/dashboard";
import { isDef } from "@/utils/is";
export default function useLegendProcess(legend?: LegendOptions) {
let isRight = false;
if (legend && legend.toTheRight) {
isRight = true;
}
function showEchartsLegend(keys: string[]) {
if (legend && isDef(legend.show)) {
if (legend.asTable && legend.show) {
return false;
}
return legend.show;
}
if (keys.length === 1) {
return false;
}
if (legend && legend.asTable) {
return false;
}
return true;
}
function aggregations(
data: { [key: string]: number[] },
intervalTime: string[]
) {
const source: { [key: string]: unknown }[] = [];
const keys = Object.keys(data || {}).filter(
(i: any) => Array.isArray(data[i]) && data[i].length
);
const headers = [];
for (const [key, value] of keys.entries()) {
const arr = JSON.parse(JSON.stringify(data[value]));
const item: { [key: string]: unknown } = {
name: value,
topN: arr
.map((d: number, index: number) => {
return {
key: intervalTime[index],
value: d,
};
})
.sort(
(
a: { key: string; value: number },
b: { key: string; value: number }
) => b.value - a.value
)
.filter((_: unknown, index: number) => index < 10),
};
if (legend) {
if (legend.min) {
item.min = Math.min(...data[value]).toFixed(2);
if (key === 0) {
headers.push({ value: "min", label: "Min" });
}
}
if (legend.max) {
item.max = Math.max(...data[value]).toFixed(2);
if (key === 0) {
headers.push({ value: "max", label: "Max" });
}
}
if (legend.mean) {
const total = data[value].reduce((prev: number, next: number) => {
prev += Number(next);
return prev;
}, 0);
item.mean = (total / data[value].length).toFixed(4);
if (key === 0) {
headers.push({ value: "mean", label: "Mean" });
}
}
if (legend.total) {
item.total = data[value]
.reduce((prev: number, next: number) => {
prev += Number(next);
return prev;
}, 0)
.toFixed(2);
if (key === 0) {
headers.push({ value: "total", label: "Total" });
}
}
}
source.push(item);
}
return { source, headers };
}
function chartColors(keys: string[]) {
let color: string[] = [];
switch (keys.length) {
case 2:
color = ["#FF6A84", "#a0b1e6"];
break;
case 1:
color = ["#3f96e3"];
break;
default:
color = [
"#30A4EB",
"#45BFC0",
"#FFCC55",
"#FF6A84",
"#a0a7e6",
"#c23531",
"#2f4554",
"#61a0a8",
"#d48265",
"#91c7ae",
"#749f83",
"#ca8622",
"#bda29a",
"#6e7074",
"#546570",
"#c4ccd3",
];
break;
}
return color;
}
return { showEchartsLegend, isRight, aggregations, chartColors };
}

View File

@@ -26,14 +26,18 @@ export function useListConfig(config: any, index: string) {
config.metricConfig &&
config.metricConfig[i] &&
config.metricConfig[i].calculation;
const line =
config.metricTypes[i] === MetricQueryTypes.ReadMetricsValues &&
!types.includes(calculation);
const isLinear =
[
MetricQueryTypes.ReadMetricsValues,
MetricQueryTypes.ReadLabeledMetricsValues,
].includes(config.metricTypes[i]) && !types.includes(calculation);
const isAvg =
config.metricTypes[i] === MetricQueryTypes.ReadMetricsValues &&
types.includes(calculation);
[
MetricQueryTypes.ReadMetricsValues,
MetricQueryTypes.ReadLabeledMetricsValues,
].includes(config.metricTypes[i]) && types.includes(calculation);
return {
isLinear: line,
isLinear,
isAvg,
};
}

View File

@@ -74,62 +74,73 @@ export function useQueryProcessor(config: any) {
order: c.sortOrder || "DES",
};
} else {
if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) {
const labels = (c.labelsIndex || "")
.split(",")
.map((item: string) => item.replace(/^\s*|\s*$/g, ""));
variables.push(`$labels${index}: [String!]!`);
conditions[`labels${index}`] = labels;
}
variables.push(`$condition${index}: MetricsCondition!`);
conditions[`condition${index}`] = {
name,
entity: {
scope: dashboardStore.entity,
serviceName:
dashboardStore.entity === "All"
? undefined
: selectorStore.currentService.value,
normal:
dashboardStore.entity === "All"
? undefined
: selectorStore.currentService.normal,
serviceInstanceName: [
"ServiceInstance",
"ServiceInstanceRelation",
"ProcessRelation",
].includes(dashboardStore.entity)
? selectorStore.currentPod && selectorStore.currentPod.value
: undefined,
endpointName: dashboardStore.entity.includes("Endpoint")
? selectorStore.currentPod && selectorStore.currentPod.value
: undefined,
processName: dashboardStore.entity.includes("Process")
? selectorStore.currentProcess && selectorStore.currentProcess.value
: undefined,
destNormal: isRelation
? selectorStore.currentDestService.normal
: undefined,
destServiceName: isRelation
? selectorStore.currentDestService.value
: undefined,
destServiceInstanceName: [
"ServiceInstanceRelation",
"ProcessRelation",
].includes(dashboardStore.entity)
const entity = {
scope: config.catalog,
serviceName:
dashboardStore.entity === "All"
? undefined
: selectorStore.currentService.value,
normal:
dashboardStore.entity === "All"
? undefined
: selectorStore.currentService.normal,
serviceInstanceName: [
"ServiceInstance",
"ServiceInstanceRelation",
"ProcessRelation",
].includes(dashboardStore.entity)
? selectorStore.currentPod && selectorStore.currentPod.value
: undefined,
endpointName: dashboardStore.entity.includes("Endpoint")
? selectorStore.currentPod && selectorStore.currentPod.value
: undefined,
processName: dashboardStore.entity.includes("Process")
? selectorStore.currentProcess && selectorStore.currentProcess.value
: undefined,
destNormal: isRelation
? selectorStore.currentDestService.normal
: undefined,
destServiceName: isRelation
? selectorStore.currentDestService.value
: undefined,
destServiceInstanceName: [
"ServiceInstanceRelation",
"ProcessRelation",
].includes(dashboardStore.entity)
? selectorStore.currentDestPod && selectorStore.currentDestPod.value
: undefined,
destEndpointName:
dashboardStore.entity === "EndpointRelation"
? selectorStore.currentDestPod && selectorStore.currentDestPod.value
: undefined,
destEndpointName:
dashboardStore.entity === "EndpointRelation"
? selectorStore.currentDestPod &&
selectorStore.currentDestPod.value
: undefined,
destProcessName: dashboardStore.entity.includes("ProcessRelation")
? selectorStore.currentDestProcess &&
selectorStore.currentDestProcess.value
: undefined,
},
destProcessName: dashboardStore.entity.includes("ProcessRelation")
? selectorStore.currentDestProcess &&
selectorStore.currentDestProcess.value
: undefined,
};
if ([MetricQueryTypes.ReadRecords].includes(metricType)) {
variables.push(`$condition${index}: RecordCondition!`);
conditions[`condition${index}`] = {
name,
parentEntity: entity,
topN: c.topN || 10,
order: c.sortOrder || "DES",
};
} else {
entity.scope = dashboardStore.entity;
if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) {
const labels = (c.labelsIndex || "")
.split(",")
.map((item: string) => item.replace(/^\s*|\s*$/g, ""));
variables.push(`$labels${index}: [String!]!`);
conditions[`labels${index}`] = labels;
}
variables.push(`$condition${index}: MetricsCondition!`);
conditions[`condition${index}`] = {
name,
entity,
};
}
}
if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) {
return `${name}${index}: ${metricType}(condition: $condition${index}, labels: $labels${index}, duration: $duration)${RespFields[metricType]}`;
@@ -138,6 +149,7 @@ export function useQueryProcessor(config: any) {
}
});
const queryStr = `query queryData(${variables}) {${fragment}}`;
return {
queryStr,
conditions,
@@ -167,7 +179,7 @@ export function useSourceProcessor(
const c = (config.metricConfig && config.metricConfig[index]) || {};
if (type === MetricQueryTypes.ReadMetricsValues) {
source[m] =
source[c.label || m] =
(resp.data[keys[index]] &&
calculateExp(resp.data[keys[index]].values.values, c)) ||
[];
@@ -196,8 +208,13 @@ export function useSourceProcessor(
source[m] = aggregation(Number(Object.values(resp.data)[0]), c);
}
if (
type === MetricQueryTypes.SortMetrics ||
type === MetricQueryTypes.ReadSampledRecords
(
[
MetricQueryTypes.ReadRecords,
MetricQueryTypes.ReadSampledRecords,
MetricQueryTypes.SortMetrics,
] as string[]
).includes(type)
) {
source[m] = (Object.values(resp.data)[0] || []).map(
(d: { value: unknown; name: string }) => {
@@ -238,13 +255,19 @@ export function useSourceProcessor(
export function useQueryPodsMetrics(
pods: Array<Instance | Endpoint | Service | any>,
config: { metrics: string[]; metricTypes: string[] },
config: {
metrics: string[];
metricTypes: string[];
metricConfig: MetricConfigOpt[];
},
scope: string
) {
if (!(config.metrics && config.metrics[0])) {
const metricTypes = (config.metricTypes || []).filter((m: string) => m);
if (!metricTypes.length) {
return;
}
if (!(config.metricTypes && config.metricTypes[0])) {
const metrics = (config.metrics || []).filter((m: string) => m);
if (!metrics.length) {
return;
}
const appStore = useAppStoreWithOut();
@@ -266,14 +289,24 @@ export function useQueryPodsMetrics(
endpointName: scope === "Endpoint" ? d.label : undefined,
normal: scope === "Service" ? d.normal : currentService.normal,
};
const f = config.metrics.map((name: string, idx: number) => {
const metricType = config.metricTypes[idx] || "";
const f = metrics.map((name: string, idx: number) => {
const metricType = metricTypes[idx] || "";
variables.push(`$condition${index}${idx}: MetricsCondition!`);
conditions[`condition${index}${idx}`] = {
name,
entity: param,
};
variables.push(`$condition${index}${idx}: MetricsCondition!`);
return `${name}${index}${idx}: ${metricType}(condition: $condition${index}${idx}, duration: $duration)${RespFields[metricType]}`;
let labelStr = "";
if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) {
const c = config.metricConfig[idx] || {};
variables.push(`$labels${index}${idx}: [String!]!`);
labelStr = `labels: $labels${index}${idx}, `;
const labels = (c.labelsIndex || "")
.split(",")
.map((item: string) => item.replace(/^\s*|\s*$/g, ""));
conditions[`labels${index}${idx}`] = labels;
}
return `${name}${index}${idx}: ${metricType}(condition: $condition${index}${idx}, ${labelStr}duration: $duration)${RespFields[metricType]}`;
});
return f;
}
@@ -283,6 +316,7 @@ export function useQueryPodsMetrics(
return { queryStr, conditions };
}
export function usePodsSource(
pods: Array<Instance | Endpoint>,
resp: { errors: string; data: { [key: string]: any } },
@@ -296,12 +330,20 @@ export function usePodsSource(
ElMessage.error(resp.errors);
return {};
}
const names: string[] = [];
const metricConfigArr: MetricConfigOpt[] = [];
const metricTypesArr: string[] = [];
const data = pods.map((d: Instance | any, idx: number) => {
config.metrics.map((name: string, index: number) => {
const c: any = (config.metricConfig && config.metricConfig[index]) || {};
const key = name + idx + index;
if (config.metricTypes[index] === MetricQueryTypes.ReadMetricsValue) {
d[name] = aggregation(resp.data[key], c);
if (idx === 0) {
names.push(name);
metricConfigArr.push(c);
metricTypesArr.push(config.metricTypes[index]);
}
}
if (config.metricTypes[index] === MetricQueryTypes.ReadMetricsValues) {
d[name] = {};
@@ -317,12 +359,56 @@ export function usePodsSource(
d[name]["values"] = resp.data[key].values.values.map(
(val: { value: number }) => aggregation(val.value, c)
);
if (idx === 0) {
names.push(name);
metricConfigArr.push(c);
metricTypesArr.push(config.metricTypes[index]);
}
}
if (
config.metricTypes[index] === MetricQueryTypes.ReadLabeledMetricsValues
) {
const resVal = resp.data[key] || [];
const labels = (c.label || "")
.split(",")
.map((item: string) => item.replace(/^\s*|\s*$/g, ""));
const labelsIdx = (c.labelsIndex || "")
.split(",")
.map((item: string) => item.replace(/^\s*|\s*$/g, ""));
for (let i = 0; i < resVal.length; i++) {
const item = resVal[i];
const values = item.values.values.map((d: { value: number }) =>
aggregation(Number(d.value), c)
);
const indexNum = labelsIdx.findIndex((d: string) => d === item.label);
let key = item.label;
if (labels[indexNum] && indexNum > -1) {
key = labels[indexNum];
}
if (!d[key]) {
d[key] = {};
}
if (
[
Calculations.Average,
Calculations.ApdexAvg,
Calculations.PercentageAvg,
].includes(c.calculation)
) {
d[key]["avg"] = calculateExp(item.values.values, c);
}
d[key]["values"] = values;
if (idx === 0) {
names.push(key);
metricConfigArr.push({ ...c, index: i });
metricTypesArr.push(config.metricTypes[index]);
}
}
}
});
return d;
});
return data;
return { data, names, metricConfigArr, metricTypesArr };
}
export function useQueryTopologyMetrics(metrics: string[], ids: string[]) {
const appStore = useAppStoreWithOut();
@@ -435,6 +521,7 @@ export async function useGetMetricEntity(metric: string, metricType: any) {
[
MetricQueryTypes.ReadSampledRecords,
MetricQueryTypes.SortMetrics,
MetricQueryTypes.ReadRecords,
].includes(metricType)
) {
const res = await dashboardStore.fetchMetricList(metric);

View File

@@ -18,7 +18,7 @@ limitations under the License. -->
<div class="app-config">
<span class="red" v-show="timeRange">{{ t("timeTips") }}</span>
<TimePicker
:value="time"
:value="[appStore.durationRow.start, appStore.durationRow.end]"
position="bottom"
format="YYYY-MM-DD HH:mm"
@input="changeTimeRange"
@@ -55,17 +55,12 @@ import { useI18n } from "vue-i18n";
import timeFormat from "@/utils/timeFormat";
import { useAppStoreWithOut } from "@/store/modules/app";
import { ElMessage } from "element-plus";
import getLocalTime from "@/utils/localtime";
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const route = useRoute();
const pageName = ref<string>("");
const timeRange = ref<number>(0);
const time = ref<Date[]>([
appStore.durationRow.start,
appStore.durationRow.end,
]);
resetDuration();
getVersion();
@@ -73,15 +68,13 @@ const setConfig = (value: string) => {
pageName.value = value || "";
};
const handleReload = () => {
function handleReload() {
const gap =
appStore.duration.end.getTime() - appStore.duration.start.getTime();
const dates: Date[] = [
getLocalTime(appStore.utc, new Date(new Date().getTime() - gap)),
getLocalTime(appStore.utc, new Date()),
];
const dates: Date[] = [new Date(new Date().getTime() - gap), new Date()];
appStore.setDuration(timeFormat(dates));
};
}
function changeTimeRange(val: Date[] | any) {
timeRange.value =
val[1].getTime() - val[0].getTime() > 60 * 24 * 60 * 60 * 1000 ? 1 : 0;
@@ -114,7 +107,6 @@ function resetDuration() {
step: d.step,
});
appStore.updateUTC(d.utc);
time.value = [new Date(d.start), new Date(d.end)];
}
}
</script>

View File

@@ -33,11 +33,7 @@ limitations under the License. -->
<template v-for="(menu, index) in routes" :key="index">
<el-sub-menu :index="String(menu.name)" v-if="menu.meta.hasGroup">
<template #title>
<router-link
class="items"
:to="menu.path"
:exact="menu.meta.exact || false"
>
<router-link class="items" :to="menu.path">
<el-icon class="menu-icons" :style="{ marginRight: '12px' }">
<Icon size="lg" :iconName="menu.meta.icon" />
</el-icon>
@@ -52,11 +48,7 @@ limitations under the License. -->
:index="m.name"
:key="idx"
>
<router-link
class="items"
:to="m.path"
:exact="(m.meta && m.meta.exact) || false"
>
<router-link class="items" :to="m.path">
<span class="title">{{ m.meta && t(m.meta.title) }}</span>
</router-link>
</el-menu-item>
@@ -68,20 +60,12 @@ limitations under the License. -->
v-else
>
<el-icon class="menu-icons" :style="{ marginRight: '12px' }">
<router-link
class="items"
:to="menu.children[0].path"
:exact="menu.meta.exact"
>
<router-link class="items" :to="menu.children[0].path">
<Icon size="lg" :iconName="menu.meta.icon" />
</router-link>
</el-icon>
<template #title>
<router-link
class="items"
:to="menu.children[0].path"
:exact="menu.meta.exact"
>
<router-link class="items" :to="menu.children[0].path">
<span class="title">{{ t(menu.meta.title) }}</span>
</router-link>
</template>
@@ -123,7 +107,7 @@ if (/Android|webOS|iPhone|iPod|iPad|BlackBerry/i.test(navigator.userAgent)) {
} else {
appStore.setIsMobile(false);
}
const isCollapse = ref(appStore.isMobile ? true : false);
const isCollapse = ref(false);
const controlMenu = () => {
isCollapse.value = !isCollapse.value;
};
@@ -141,13 +125,13 @@ const filterMenus = (menus: any[]) => {
.side-bar {
background: #252a2f;
height: 100%;
min-height: 700px;
position: relative;
margin-bottom: 100px;
overflow-y: auto;
overflow-x: hidden;
}
.el-menu-vertical:not(.el-menu--collapse) {
width: 200px;
width: 220px;
font-size: 16px;
}
@@ -173,7 +157,7 @@ span.collapse {
.menu-control {
position: absolute;
top: 7px;
left: 200px;
left: 220px;
cursor: pointer;
transition: all 0.2s linear;
z-index: 99;

View File

@@ -52,17 +52,19 @@ const msg = {
instance: "Instance",
create: "Create",
loading: "Loading",
selectVisualization: "Visualize your metrics",
selectVisualization: "Visualize Metrics",
visualization: "Visualization",
graphStyles: "Graph styles",
widgetOptions: "Widget options",
standardOptions: "Standard options",
graphStyles: "Graph Styles",
widgetOptions: "Widget Options",
standardOptions: "Standard Options",
max: "Max",
min: "Min",
plus: "Plus",
mean: "Mean",
minus: "Minus",
multiply: "Multiply",
divide: "Divide",
total: "Total",
convertToMilliseconds: "Convert Unix Timestamp(milliseconds)",
convertToSeconds: "Convert Unix Timestamp(seconds)",
smooth: "Smooth",
@@ -108,6 +110,7 @@ const msg = {
showXAxis: "Show X Axis",
showYAxis: "Show Y Axis",
nameError: "The dashboard name cannot be duplicate",
nameEmptyError: "The dashboard name cannot be empty",
showGroup: "Show Group",
noRoot: "Please set a root dashboard for",
noWidget: "Please add widgets.",
@@ -123,6 +126,7 @@ const msg = {
editWarning: "You are entering edit mode",
viewWarning: "You are entering view mode",
virtualDatabase: "Virtual Database",
virtualCache: "Virtual Cache",
reloadDashboards: "Reload dashboards",
kubernetesService: "Service",
kubernetesCluster: "Cluster",
@@ -144,6 +148,7 @@ const msg = {
pause: "Pause",
begin: "Start",
associateOptions: "Association Options",
associateMetrics: "Association Metrics",
widget: "Widget",
nameTip:
"The name only supports Chinese and English, horizontal lines and underscores. The length of the name is limited to 300 characters",
@@ -152,6 +157,30 @@ const msg = {
text: "Text",
query: "Query",
postgreSQL: "PostgreSQL",
endpointTips: "The table shows up to 20 pieces of endpoints.",
apisix: "APISIX",
viewTrace: "View Related Traces",
relatedTraceOptions: "Related Trace Options",
setLatencyDuration: "Latency Related Metrics",
queryOrder: "Query By Duration",
setOrder: "Query Order",
latency: "Latency",
metricValues: "Metric Values",
queryConditions: "Query Conditions",
enableRelatedTrace: "Enable Related Trace",
maxTraceDuration: "Maximum Duration",
minTraceDuration: "Minimum Duration",
legendOptions: "Legend Options",
showLegend: "Show Legend",
asTable: "As Table",
toTheRight: "To The Right",
legendValues: "Legend Values",
minDuration: "Minimal Request Duration",
when4xx:
"Sample HTTP requests and responses with tracing when response code between 400 and 499",
when5xx:
"Sample HTTP requests and responses with tracing when response code between 500 and 599",
taskTitle: "HTTP request and response collecting rules",
seconds: "Seconds",
hourTip: "Select Hour",
minuteTip: "Select Minute",
@@ -244,7 +273,7 @@ const msg = {
entityType: "Entity Type",
maxItemNum: "Max number of Item",
unknownMetrics: "Unknown Metrics",
labels: "Labels",
labels: "Label",
aggregation: "Calculation",
unit: "Unit",
labelsIndex: "Label Subscript",
@@ -305,6 +334,7 @@ const msg = {
eventsParameters: "Event Parameters",
eventDetail: "Event Detail",
value: "Value",
key: "Key",
show: "Show",
hide: "Hide",
statistics: "Statistics",
@@ -343,5 +373,7 @@ const msg = {
conditionNotice:
"Notice: Please press Enter after inputting a key of content, exclude key of content(key=value).",
language: "Language",
gateway: "Gateway",
virtualMQ: "Virtual MQ",
};
export default msg;

View File

@@ -59,9 +59,11 @@ const msg = {
standardOptions: "Opciones estandar",
max: "Máx",
min: "Mín",
mean: "Promedio",
plus: "Más",
minus: "Menoss",
multiply: "Multiplcar",
total: "Todo",
divide: "Dividir",
convertToMilliseconds: "Convertir Unix Timestamp(milisegundos)",
convertToSeconds: "Convertir Unix Timestamp(segundos)",
@@ -110,6 +112,7 @@ const msg = {
showXAxis: "Mostrar Eje X",
showYAxis: "Mostrar Eje Y",
nameError: "El nombre del panel no puede ser duplicado",
nameEmptyError: "El nombre del panel no puede estar vacío",
showGroup: "Mostrar Grupo",
noRoot: "Por favor ponga la raíz del panel",
noWidget: "Por favor añada widgets.",
@@ -125,6 +128,7 @@ const msg = {
editWarning: "Estás entrando en modo edición",
viewWarning: "Estás entrando en modo visualización",
virtualDatabase: "Base de Datos Virtual",
virtualCache: "Caché virtual",
reloadDashboards: "Recargar Panel",
kubernetesService: "Servicio",
kubernetesCluster: "Cluster",
@@ -144,6 +148,7 @@ const msg = {
pause: "Pausa",
begin: "Inicio",
associateOptions: "Opciones de asociación",
associateMetrics: "Índice de correlación",
widget: "Dispositivo pequeño",
text: "Texto",
duplicateName: "Nombre duplicado",
@@ -152,10 +157,34 @@ const msg = {
enableAssociate: "Activar asociación",
query: "Consulta",
postgreSQL: "PostgreSQL",
endpointTips: "Aquí, la tabla muestra hasta 20 punto final.",
apisix: "APISIX",
queryOrder: "Consulta por duración",
setOrder: "Orden de consulta",
latency: "Retraso",
metricValues: "Valor métrico",
legendValues: "Valor de la leyenda",
seconds: "Segundos",
hourTip: "Seleccione Hora",
minuteTip: "Seleccione Minuto",
secondTip: "Seleccione Segundo",
viewTrace: "Ver trazas relacionadas",
relatedTraceOptions: "Opciones de seguimiento relacionadas",
setLatencyDuration: "Índice de correlación retardada",
enableRelatedTrace: "Activar trazas relacionadas",
queryConditions: "Condiciones de consulta",
maxTraceDuration: "Duración máxima",
minTraceDuration: "Duración mínima",
legendOptions: "Opciones de leyenda",
showLegend: "Mostrar leyenda",
asTable: "Como tabla",
toTheRight: "Derecha",
minDuration: "Duración mínima de la solicitud",
when4xx:
"Ejemplo de solicitud y respuesta http con seguimiento cuando el Código de respuesta está entre 400 y 499",
when5xx:
"Ejemplo de solicitud y respuesta http con seguimiento cuando el Código de respuesta está entre 500 y 599",
taskTitle: "Reglas de recolección de peticiones y respuestas HTTP",
second: "s",
yearSuffix: "Año",
monthsHead: "Ene_Feb_Mar_Abr_May_Jun_Jul_Ago_Set_Oct_Nov_Dic",
@@ -305,6 +334,7 @@ const msg = {
eventsParameters: "Parámetro del Evento",
eventDetail: "Detalle del Evento",
value: "Valor",
key: "Clave",
show: "Mostrar",
hide: "Oculatr",
statistics: "Estadísticas",
@@ -346,5 +376,7 @@ const msg = {
conditionNotice:
"Aviso: Por favor presione Intro después de introducir una clave de contenido, excluir clave de contenido(clave=valor).",
language: "Lenguaje",
gateway: "Puerta",
virtualMQ: "MQ virtual",
};
export default msg;

View File

@@ -56,10 +56,12 @@ const msg = {
standardOptions: "标准选项",
max: "最大值",
min: "最小值",
mean: "平均值",
plus: "加法",
minus: "减法",
multiply: "乘法",
divide: "除法",
total: "总计",
convertToMilliseconds: "转换Unix时间戳毫秒",
convertToSeconds: "转换Unix时间戳",
smooth: "光滑的",
@@ -106,6 +108,7 @@ const msg = {
showXAxis: "显示X轴",
showYAxis: "显示Y轴",
nameError: "仪表板名称不能重复",
nameEmptyError: "仪表板名称不能为空",
noRoot: "请设置根仪表板,为",
showGroup: "显示分组",
noWidget: "请添加组件",
@@ -121,6 +124,7 @@ const msg = {
editWarning: "你正在进入编辑模式",
viewWarning: "你正在进入预览模式",
virtualDatabase: "虚拟数据库",
virtualCache: "虚拟缓存",
reloadDashboards: "重新加载仪表盘",
kubernetesService: "服务",
kubernetesCluster: "集群",
@@ -142,6 +146,7 @@ const msg = {
pause: "暂停",
begin: "开始",
associateOptions: "关联选项",
associateMetrics: "关联指标",
widget: "部件",
enableAssociate: "启用关联",
nameTip: "该名称仅支持中文和英文、横线和下划线, 并且限制长度为300个字符",
@@ -149,6 +154,28 @@ const msg = {
text: "文本",
query: "查询",
postgreSQL: "PostgreSQL",
endpointTips: "这里最多展示20条endpoints。",
apisix: "APISIX",
viewTrace: "查看相关Trace",
relatedTraceOptions: "相关的Trace选项",
setLatencyDuration: "延迟相关指标",
queryOrder: "按持续时间查询",
setOrder: "查询顺序",
latency: "延迟",
metricValues: "指标值",
enableRelatedTrace: "启用相关Trace",
queryConditions: "查询条件",
maxTraceDuration: "最大持续时间",
minTraceDuration: "最小持续时间",
legendOptions: "图例选项",
showLegend: "显示图例",
asTable: "作为表格",
toTheRight: "在右边",
legendValues: "图例值",
minDuration: "最小请求持续时间",
when4xx: "当响应代码介于400和499之间时带有跟踪的HTTP请求和响应示例",
when5xx: "当响应代码介于500和599之间时带有跟踪的HTTP请求和响应示例",
taskTitle: "HTTP请求和响应收集规则",
seconds: "秒",
hourTip: "选择小时",
minuteTip: "选择分钟",
@@ -303,6 +330,7 @@ const msg = {
eventsParameters: "事件参数",
eventDetail: "事件详情",
value: "数值",
key: "Key",
tableHeader: "表头名称",
tableValues: "表值",
show: "展示",
@@ -343,5 +371,7 @@ const msg = {
conditionNotice:
"请输入一个内容关键词或者内容不包含的关键词(key=value)之后回车",
language: "语言",
gateway: "网关",
virtualMQ: "虚拟消息队列",
};
export default msg;

View File

@@ -25,16 +25,12 @@ export const routesAlarm: Array<RouteRecordRaw> = [
title: "alarm",
icon: "spam",
hasGroup: false,
exact: true,
},
component: Layout,
children: [
{
path: "/alerting",
name: "Alarm",
meta: {
exact: false,
},
component: () =>
import(/* webpackChunkName: "alerting" */ "@/views/Alarm.vue"),
},

View File

@@ -26,7 +26,6 @@ export const routesDashboard: Array<RouteRecordRaw> = [
title: "dashboards",
icon: "dashboard_customize",
hasGroup: true,
exact: true,
},
children: [
{
@@ -38,7 +37,6 @@ export const routesDashboard: Array<RouteRecordRaw> = [
name: "List",
meta: {
title: "dashboardList",
exact: false,
},
},
{
@@ -50,7 +48,6 @@ export const routesDashboard: Array<RouteRecordRaw> = [
name: "New",
meta: {
title: "dashboardNew",
exact: false,
},
},
{

View File

@@ -14,10 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesBrowser: Array<RouteRecordRaw> = [
export default [
{
path: "",
name: "Browser",
@@ -26,26 +24,22 @@ export const routesBrowser: Array<RouteRecordRaw> = [
icon: "language",
},
redirect: "/browser",
component: Layout,
children: [
{
path: "/browser",
name: "Browser",
meta: {
title: "browser",
exact: true,
layer: "BROWSER",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/browser/tab/:activeTabIndex",
name: "BrowserActiveTabIndex",
meta: {
notShow: true,
layer: "BROWSER",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},

View File

@@ -14,10 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesDatabase: Array<RouteRecordRaw> = [
export default [
{
path: "",
name: "Database",
@@ -27,45 +25,38 @@ export const routesDatabase: Array<RouteRecordRaw> = [
hasGroup: true,
},
redirect: "/mySQL",
component: Layout,
children: [
{
path: "/mySQL",
name: "MySQL",
meta: {
title: "mySQL",
exact: true,
layer: "MYSQL",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mySQL/tab/:activeTabIndex",
name: "MySQLActiveTabIndex",
meta: {
notShow: true,
layer: "MYSQL",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/postgreSQL",
name: "PostgreSQL",
meta: {
title: "postgreSQL",
exact: true,
layer: "POSTGRESQL",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/postgreSQL/tab/:activeTabIndex",
name: "PostgreSQLActiveTabIndex",
meta: {
notShow: true,
layer: "POSTGRESQL",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},

View File

@@ -14,34 +14,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesFunctions: Array<RouteRecordRaw> = [
export default [
{
path: "",
name: "Functions",
meta: {
title: "functions",
icon: "cloud_queue",
layer: "FAAS",
},
redirect: "/functions",
component: Layout,
children: [
{
path: "/functions",
name: "Functions",
meta: {
exact: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/functions/tab/:activeTabIndex",
name: "FunctionsActiveTabIndex",
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},

View File

@@ -0,0 +1,47 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export default [
{
path: "",
name: "Gateway",
meta: {
title: "gateway",
icon: "gateway",
hasGroup: true,
},
redirect: "/apisix",
children: [
{
path: "/apisix",
name: "APISIX",
meta: {
title: "apisix",
layer: "APISIX",
},
},
{
path: "/apisix/tab/:activeTabIndex",
name: "APISIXActiveTabIndex",
meta: {
notShow: true,
layer: "APISIX",
},
},
],
},
];

View File

@@ -14,10 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesGen: Array<RouteRecordRaw> = [
export default [
{
path: "",
name: "General",
@@ -25,48 +23,71 @@ export const routesGen: Array<RouteRecordRaw> = [
title: "general",
icon: "chart",
hasGroup: true,
exact: true,
},
component: Layout,
children: [
{
path: "/general",
name: "GeneralServices",
meta: {
exact: true,
title: "services",
layer: "GENERAL",
},
component: () =>
import(/* webpackChunkName: "layers" */ "@/views/Layer.vue"),
},
{
path: "/general/tab/:activeTabIndex",
name: "GeneralServicesActiveTabIndex",
meta: {
exact: true,
notShow: true,
layer: "GENERAL",
},
component: () =>
import(/* webpackChunkName: "layers" */ "@/views/Layer.vue"),
},
{
path: "/database",
name: "VirtualDatabase",
meta: {
title: "virtualDatabase",
exact: true,
layer: "VIRTUAL_DATABASE",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/database/tab/:activeTabIndex",
name: "VirtualDatabaseActiveTabIndex",
meta: {
notShow: true,
layer: "VIRTUAL_DATABASE",
},
},
{
path: "/cache",
name: "VirtualCache",
meta: {
title: "virtualCache",
layer: "VIRTUAL_CACHE",
},
},
{
path: "/cache/tab/:activeTabIndex",
name: "VirtualCacheActiveTabIndex",
meta: {
notShow: true,
layer: "VIRTUAL_CACHE",
},
},
{
path: "/mq",
name: "VirtualMQ",
meta: {
title: "virtualMQ",
layer: "VIRTUAL_MQ",
},
},
{
path: "/mq/tab/:activeTabIndex",
name: "VirtualMQActiveTabIndex",
meta: {
notShow: true,
layer: "VIRTUAL_MQ",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},

37
src/router/data/index.ts Normal file
View File

@@ -0,0 +1,37 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import general from "./general";
import serviceMesh from "./serviceMesh";
import database from "./database";
import infrastructure from "./infrastructure";
import selfObservability from "./selfObservability";
import functions from "./functions";
import browser from "./browser";
import k8s from "./k8s";
import gateway from "./gateway";
export default [
...general,
...serviceMesh,
...functions,
...k8s,
...infrastructure,
...browser,
...gateway,
...database,
...selfObservability,
];

View File

@@ -14,30 +14,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesInfra: Array<RouteRecordRaw> = [
export default [
{
path: "",
name: "Infrastructure",
meta: {
title: "infrastructure",
icon: "scatter_plot",
exact: true,
hasGroup: true,
},
redirect: "/linux",
component: Layout,
children: [
{
path: "/linux",
name: "Linux",
meta: {
title: "linux",
layer: "OS_LINUX",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/linux/tab/:activeTabIndex",
@@ -45,26 +40,9 @@ export const routesInfra: Array<RouteRecordRaw> = [
meta: {
title: "linux",
notShow: true,
layer: "OS_LINUX",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
// {
// path: "/infrastructure/vm",
// name: "VirtualMachine",
// meta: {
// title: "virtualMachine",
// },
// component: () => import("@/views/infrastructure/Infrastructure.vue"),
// },
// {
// path: "/infrastructure/k8s",
// name: "Kubernetes",
// meta: {
// title: "kubernetes",
// },
// component: () => import("@/views/infrastructure/Infrastructure.vue"),
// },
],
},
];

View File

@@ -14,10 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesK8s: Array<RouteRecordRaw> = [
export default [
{
path: "",
name: "Kubernetes",
@@ -27,7 +25,6 @@ export const routesK8s: Array<RouteRecordRaw> = [
hasGroup: true,
},
redirect: "/kubernetes/cluster",
component: Layout,
children: [
{
path: "/kubernetes/cluster",
@@ -35,9 +32,8 @@ export const routesK8s: Array<RouteRecordRaw> = [
meta: {
notShow: false,
title: "kubernetesCluster",
layer: "K8S",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/kubernetes/cluster/tab/:activeTabIndex",
@@ -45,9 +41,8 @@ export const routesK8s: Array<RouteRecordRaw> = [
meta: {
notShow: true,
title: "kubernetesClusterActiveTabIndex",
layer: "K8S",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/kubernetes/service",
@@ -55,9 +50,8 @@ export const routesK8s: Array<RouteRecordRaw> = [
meta: {
notShow: false,
title: "kubernetesService",
layer: "K8S_SERVICE",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/kubernetes/service/tab/:activeTabIndex",
@@ -65,9 +59,8 @@ export const routesK8s: Array<RouteRecordRaw> = [
meta: {
notShow: true,
title: "kubernetesServiceActiveTabIndex",
layer: "K8S_SERVICE",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},

View File

@@ -14,10 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesSelf: Array<RouteRecordRaw> = [
export default [
{
path: "",
name: "SelfObservability",
@@ -27,43 +25,38 @@ export const routesSelf: Array<RouteRecordRaw> = [
icon: "logo",
hasGroup: true,
},
component: Layout,
children: [
{
path: "/self/skyWalkingServer",
name: "SkyWalkingServer",
meta: {
title: "skyWalkingServer",
layer: "SO11Y_OAP",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/self/skyWalkingServer/tab/:activeTabIndex",
name: "SkyWalkingServerActiveTabIndex",
meta: {
notShow: true,
layer: "SO11Y_OAP",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/self/satellite",
name: "Satellite",
meta: {
title: "satellite",
layer: "SO11Y_SATELLITE",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/self/satellite/tab/:activeTabIndex",
name: "SatelliteActiveTabIndex",
meta: {
notShow: true,
layer: "SO11Y_SATELLITE",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},

View File

@@ -14,10 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesMesh: Array<RouteRecordRaw> = [
export default [
{
path: "",
name: "ServiceMesh",
@@ -27,7 +25,6 @@ export const routesMesh: Array<RouteRecordRaw> = [
icon: "epic",
hasGroup: true,
},
component: Layout,
children: [
{
path: "/mesh/services",
@@ -35,18 +32,16 @@ export const routesMesh: Array<RouteRecordRaw> = [
meta: {
notShow: false,
title: "services",
layer: "MESH",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mesh/services/tab/:activeTabIndex",
name: "MeshServicesActiveTabIndex",
meta: {
notShow: true,
layer: "MESH",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mesh/controlPanel",
@@ -54,18 +49,16 @@ export const routesMesh: Array<RouteRecordRaw> = [
meta: {
notShow: false,
title: "controlPanel",
layer: "MESH_CP",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mesh/controlPanel/tab/:activeTabIndex",
name: "ControlPanelActiveTabIndex",
meta: {
notShow: true,
layer: "MESH_CP",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mesh/dataPanel",
@@ -73,9 +66,8 @@ export const routesMesh: Array<RouteRecordRaw> = [
meta: {
notShow: false,
title: "dataPanel",
layer: "MESH_DP",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mesh/dataPanel/tab/:activeTabIndex",
@@ -83,9 +75,8 @@ export const routesMesh: Array<RouteRecordRaw> = [
meta: {
notShow: true,
title: "dataPanelActiveTabIndex",
layer: "MESH_DP",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},

View File

@@ -15,27 +15,13 @@
* limitations under the License.
*/
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import { routesGen } from "./general";
import { routesMesh } from "./serviceMesh";
import { routesDatabase } from "./database";
import { routesInfra } from "./infrastructure";
import { routesDashboard } from "./dashboard";
import { routesSetting } from "./setting";
import { routesAlarm } from "./alarm";
import { routesSelf } from "./selfObservability";
import { routesFunctions } from "./functions";
import { routesBrowser } from "./browser";
import { routesK8s } from "./k8s";
import routesLayers from "./layer";
const routes: Array<RouteRecordRaw> = [
...routesGen,
...routesMesh,
...routesFunctions,
...routesK8s,
...routesInfra,
...routesBrowser,
...routesDatabase,
...routesSelf,
...routesLayers,
...routesDashboard,
...routesAlarm,
...routesSetting,

35
src/router/layer.ts Normal file
View File

@@ -0,0 +1,35 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import LayerJson from "./data";
import Layout from "@/layout/Index.vue";
function layerDashboards() {
const routes = LayerJson.map((item: any) => {
item.component = Layout;
if (item.children) {
item.children = item.children.map((d: any) => {
d.component = () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue");
return d;
});
}
return item;
});
return routes;
}
export default layerDashboards();

View File

@@ -25,7 +25,6 @@ export const routesSetting: Array<RouteRecordRaw> = [
title: "settings",
icon: "settings",
hasGroup: false,
exact: false,
},
component: Layout,
children: [
@@ -36,7 +35,6 @@ export const routesSetting: Array<RouteRecordRaw> = [
title: "settings",
icon: "settings",
hasGroup: false,
exact: false,
},
component: () =>
import(/* webpackChunkName: "settings" */ "@/views/Settings.vue"),

View File

@@ -71,7 +71,7 @@ export const appStore = defineStore({
step: this.duration.step,
};
},
intervalTime(): string[] {
intervalUnix(): number[] {
let interval = 946080000000;
switch (this.duration.step) {
case "MINUTE":
@@ -97,12 +97,17 @@ export const appStore = defineStore({
this.utcMin * 60000;
const startUnix: number = this.duration.start.getTime();
const endUnix: number = this.duration.end.getTime();
const timeIntervals: string[] = [];
const timeIntervals: number[] = [];
for (let i = 0; i <= endUnix - startUnix; i += interval) {
const temp: string = dateFormatTime(
new Date(startUnix + i - utcSpace),
this.duration.step
);
timeIntervals.push(startUnix + i - utcSpace);
}
return timeIntervals;
},
intervalTime(): string[] {
const arr = this.intervalUnix;
const timeIntervals: string[] = [];
for (const item of arr) {
const temp: string = dateFormatTime(new Date(item), this.duration.step);
timeIntervals.push(temp);
}
return timeIntervals;

View File

@@ -92,25 +92,41 @@ export const networkProfilingStore = defineStore({
}
return prev;
}, []);
calls = calls.map((d: any) => {
d.sourceId = d.source;
d.targetId = d.target;
d.source = d.sourceObj;
d.target = d.targetObj;
delete d.sourceObj;
delete d.targetObj;
return d;
});
const param = {} as any;
calls = data.calls.reduce((prev: (Call | any)[], next: Call | any) => {
if (param[next.targetId + next.sourceId]) {
next.lowerArc = true;
}
param[next.sourceId + next.targetId] = true;
next.sourceId = next.source;
next.targetId = next.target;
next.source = next.sourceObj;
next.target = next.targetObj;
delete next.sourceObj;
delete next.targetObj;
prev.push(next);
return prev;
}, []);
this.calls = calls;
this.nodes = data.nodes;
},
async createNetworkTask(param: {
serviceId: string;
serviceInstanceId: string;
}) {
async createNetworkTask(
instanceId: string,
params: {
uriRegex: string;
when4xx: string;
when5xx: string;
minDuration: number;
}[]
) {
const res: AxiosResponse = await graphql
.query("newNetworkProfiling")
.params({ request: { instanceId: param.serviceInstanceId } });
.params({
request: {
instanceId,
samplings: params,
},
});
if (res.data.errors) {
return res.data;
@@ -136,6 +152,10 @@ export const networkProfilingStore = defineStore({
this.networkTasks = res.data.data.queryEBPFTasks || [];
this.selectedNetworkTask = this.networkTasks[0] || {};
this.setSelectedNetworkTask(this.selectedNetworkTask);
if (!this.networkTasks.length) {
this.nodes = [];
this.calls = [];
}
return res.data;
},
async keepNetworkProfiling(taskId: string) {

View File

@@ -83,7 +83,7 @@ export const profileStore = defineStore({
if (res.data.errors) {
return res.data;
}
this.endpoints = [{ value: "", label: "All" }, ...res.data.data.pods];
this.endpoints = res.data.data.pods || [];
return res.data;
},
async getTaskEndpoints(serviceId: string, keyword?: string) {

View File

@@ -23,7 +23,7 @@ import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app";
import { AxiosResponse } from "axios";
import query from "@/graphql/fetch";
import { useQueryTopologyMetrics } from "@/hooks/useProcessor";
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
import { ElMessage } from "element-plus";
interface MetricVal {

View File

@@ -22,6 +22,7 @@ import graphql from "@/graphql";
import { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { QueryOrders } from "@/views/dashboard/data";
interface TraceState {
services: Service[];
@@ -47,7 +48,7 @@ export const traceStore = defineStore({
conditions: {
queryDuration: useAppStoreWithOut().durationTime,
traceState: "ALL",
queryOrder: "BY_START_TIME",
queryOrder: QueryOrders[0].value,
paging: { pageNum: 1, pageSize: 20 },
},
traceSpanLogs: [],
@@ -71,7 +72,7 @@ export const traceStore = defineStore({
queryDuration: useAppStoreWithOut().durationTime,
paging: { pageNum: 1, pageSize: 20 },
traceState: "ALL",
queryOrder: "BY_START_TIME",
queryOrder: QueryOrders[0].value,
};
},
async getServices(layer: string) {
@@ -84,6 +85,36 @@ export const traceStore = defineStore({
this.services = res.data.data.services;
return res.data;
},
async getService(serviceId: string) {
if (!serviceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryService").params({
serviceId,
});
return res.data;
},
async getInstance(instanceId: string) {
if (!instanceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryInstance").params({
instanceId,
});
return res.data;
},
async getEndpoint(endpointId: string) {
if (!endpointId) {
return;
}
const res: AxiosResponse = await graphql.query("queryEndpoint").params({
endpointId,
});
return res.data;
},
async getInstances(id: string) {
const serviceId = this.selectorStore.currentService
? this.selectorStore.currentService.id
@@ -144,7 +175,8 @@ export const traceStore = defineStore({
if (res.data.errors) {
return res.data;
}
this.setTraceSpans(res.data.data.trace.spans || []);
const data = res.data.data.trace.spans;
this.setTraceSpans(data || []);
return res.data;
},
async getSpanLogs(params: any) {

View File

@@ -17,6 +17,7 @@
import "element-plus/es/components/message/style/css";
import "element-plus/es/components/message-box/style/css";
import "element-plus/es/components/notification/style/css";
import "element-plus/es/components/drawer/style/css";
import "./grid.scss";
import "./lib.scss";
import "./reset.scss";

View File

@@ -173,7 +173,7 @@
}
.scroll_bar_style::-webkit-scrollbar {
width: 9px;
width: 4px;
height: 4px;
background-color: #eee;
}

View File

@@ -153,6 +153,20 @@ pre {
margin-left: 5px;
}
.el-switch__label * {
font-size: 12px;
}
.el-drawer__header {
margin-bottom: 0;
padding-left: 10px;
font-size: 16px;
}
.el-drawer__body {
padding: 0;
}
.switch {
margin: 0 5px;
}
@@ -167,7 +181,7 @@ div.vis-tooltip {
.vis-item {
cursor: pointer;
height: 17px;
height: 20px;
}
.vis-item.Error {
@@ -184,7 +198,7 @@ div.vis-tooltip {
}
.vis-item .vis-item-content {
padding: 0 5px !important;
padding: 0 3px !important;
}
.vis-item.vis-selected.Error,

15
src/types/app.d.ts vendored
View File

@@ -32,3 +32,18 @@ export type Paging = {
pageNum: number;
pageSize: number;
};
export type EventParams = {
componentType: string;
seriesType: string;
seriesIndex: number;
seriesName: string;
name: string;
dataIndex: number;
data: unknown;
dataType: string;
value: number | Array;
color: string;
event: any;
dataIndex: number;
};

View File

@@ -9,6 +9,7 @@ declare module '@vue/runtime-core' {
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
@@ -24,6 +25,7 @@ declare module '@vue/runtime-core' {
ElPopover: typeof import('element-plus/es')['ElPopover']
ElProgress: typeof import('element-plus/es')['ElProgress']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSlider: typeof import('element-plus/es')['ElSlider']

View File

@@ -1,3 +1,4 @@
import { DurationTime } from "@/types/app";
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
@@ -39,19 +40,32 @@ export interface LayoutConfig {
id?: string;
associate?: { widgetId: string }[];
eventAssociate?: boolean;
filters?: {
dataIndex: number;
sourceId: string;
isRange?: boolean;
duration?: {
startTime: string;
endTime: string;
};
traceId?: string;
spanId?: string;
segmentId?: string;
};
filters?: Filters;
relatedTrace?: RelatedTrace;
}
export type RelatedTrace = {
duration: DurationTime;
status: string;
queryOrder: string;
latency: boolean;
enableRelate: boolean;
};
export type Filters = {
dataIndex: number;
sourceId: string;
isRange?: boolean;
duration?: {
startTime: string;
endTime: string;
};
traceId?: string;
spanId?: string;
segmentId?: string;
id?: string;
queryOrder?: string;
status?: string;
};
export type MetricConfigOpt = {
unit?: string;
@@ -60,6 +74,7 @@ export type MetricConfigOpt = {
labelsIndex: string;
sortOrder: string;
topN?: number;
index?: number;
};
export interface WidgetConfig {
@@ -80,6 +95,7 @@ export type GraphConfig =
export interface BarConfig {
type?: string;
showBackground?: boolean;
legend?: LegendOptions;
}
export interface LineConfig extends AreaConfig {
type?: string;
@@ -95,6 +111,7 @@ export interface LineConfig extends AreaConfig {
export interface AreaConfig {
type?: string;
opacity?: number;
legend?: LegendOptions;
}
export interface CardConfig {
@@ -165,3 +182,13 @@ export type EventParams = {
value: number | number[];
color: string;
};
export type LegendOptions = {
show: boolean;
total: boolean;
min: boolean;
max: boolean;
mean: boolean;
asTable: boolean;
toTheRight: boolean;
width: number;
};

8
src/types/ebpf.d.ts vendored
View File

@@ -74,4 +74,12 @@ export type ProcessNode = {
serviceInstanceName: string;
name: string;
isReal: boolean;
x?: number;
y?: number;
};
export interface NetworkProfilingRequest {
uriRegex: string;
when4xx: string;
when5xx: string;
minDuration: number;
}

17
src/types/trace.d.ts vendored
View File

@@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface Trace {
duration: number;
isError: boolean;
@@ -85,3 +84,19 @@ export class TraceTreeRef {
segmentMap: Map<string, Span>;
segmentIdGroup: string[];
}
type Instant = {
seconds: number;
nanos: number;
};
type KeyValue = {
key: string;
value: string | number;
};
export interface SpanAttachedEvent {
startTime: Instant;
endTime: Instant;
event: string;
tags: KeyValue[];
summary: KeyValue[];
}

View File

@@ -27,7 +27,6 @@ import { useDashboardStore } from "@/store/modules/dashboard";
import Dashboard from "./dashboard/Edit.vue";
import { useI18n } from "vue-i18n";
import { useAppStoreWithOut } from "@/store/modules/app";
import { RoutesMap } from "@/constants/data";
const route = useRoute();
const { t } = useI18n();
@@ -38,7 +37,7 @@ const layer = ref<string>("GENERAL");
getDashboard();
async function getDashboard() {
layer.value = RoutesMap[String(route.name)];
layer.value = String(route.meta.layer);
dashboardStore.setLayer(layer.value);
dashboardStore.setMode(false);
await dashboardStore.setDashboards();

View File

@@ -35,9 +35,10 @@ limitations under the License. -->
size="small"
v-model="tags"
class="trace-new-tag"
@input="searchTags"
@input="inputTags"
@blur="visible = false"
@focus="visible = true"
@change="addTags"
/>
</template>
<div class="content">
@@ -92,6 +93,7 @@ const tagsList = ref<string[]>([]);
const tagArr = ref<string[]>([]);
const tagList = ref<string[]>([]);
const tagKeys = ref<string[]>([]);
const keysList = ref<string[]>([]);
const visible = ref<boolean>(false);
const tipsMap = {
LOG: "logTagsTip",
@@ -137,6 +139,7 @@ async function fetchTagKeys() {
}
tagArr.value = resp.data.tagKeys;
tagKeys.value = resp.data.tagKeys;
keysList.value = resp.data.tagKeys;
searchTags();
}
@@ -157,13 +160,37 @@ async function fetchTagValues() {
searchTags();
}
function inputTags() {
if (!tags.value) {
tagArr.value = keysList.value;
tagKeys.value = keysList.value;
tagList.value = tagArr.value;
return;
}
let search = "";
if (tags.value.includes("=")) {
search = tags.value.split("=")[1];
fetchTagValues();
} else {
search = tags.value;
}
tagList.value = tagArr.value.filter((d: string) => d.includes(search));
}
function addTags() {
if (!tags.value.includes("=")) {
return;
}
addLabels();
tagArr.value = tagKeys.value;
searchTags();
}
function selectTag(item: string) {
if (tags.value.includes("=")) {
const key = tags.value.split("=")[0];
tags.value = key + "=" + item;
addLabels();
tagArr.value = tagKeys.value;
searchTags();
addTags();
return;
}
tags.value = item + "=";
@@ -171,10 +198,6 @@ function selectTag(item: string) {
}
function searchTags() {
if (!tags.value) {
tagList.value = tagArr.value;
return;
}
let search = "";
if (tags.value.includes("=")) {
search = tags.value.split("=")[1];

View File

@@ -46,7 +46,7 @@ limitations under the License. -->
ref="multipleTableRef"
:default-sort="{ prop: 'name', order: 'ascending' }"
@selection-change="handleSelectionChange"
height="637px"
height="calc(100% - 60px)"
size="small"
>
<el-table-column type="selection" width="55" />
@@ -156,7 +156,7 @@ import { isEmptyObject } from "@/utils/is";
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore();
const pageSize = 18;
const pageSize = 20;
const dashboards = ref<DashboardItem[]>([]);
const searchText = ref<string>("");
const loading = ref<boolean>(false);
@@ -500,12 +500,13 @@ function changePage(pageIndex: number) {
}
.table {
padding: 20px;
padding: 20px 10px;
background-color: #fff;
box-shadow: 0px 1px 4px 0px #00000029;
border-radius: 5px;
width: 100%;
overflow: hidden;
height: 100%;
overflow: auto;
}
.toggle-selection {

View File

@@ -81,6 +81,10 @@ const onCreate = () => {
states.entity === d.entity &&
states.selectedLayer === d.layer
);
if (!states.name) {
ElMessage.error(t("nameEmptyError"));
return;
}
if (index > -1) {
ElMessage.error(t("nameError"));
return;

View File

@@ -32,10 +32,12 @@ limitations under the License. -->
:data="states.source"
:config="{
...graph,
legend: (dashboardStore.selectedGrid.graph || {}).legend,
i: dashboardStore.selectedGrid.i,
metrics: dashboardStore.selectedGrid.metrics,
metricTypes: dashboardStore.selectedGrid.metricTypes,
metricConfig: dashboardStore.selectedGrid.metricConfig,
relatedTrace: dashboardStore.selectedGrid.relatedTrace,
}"
:needQuery="true"
/>
@@ -65,6 +67,13 @@ limitations under the License. -->
>
<AssociateOptions />
</el-collapse-item>
<el-collapse-item
:title="t('relatedTraceOptions')"
name="5"
v-if="hasAssociate"
>
<RelatedTraceOptions />
</el-collapse-item>
</el-collapse>
</div>
<div class="footer">
@@ -93,7 +102,7 @@ export default defineComponent({
...CustomOptions,
},
setup() {
const configHeight = document.documentElement.clientHeight - 520;
const configHeight = document.documentElement.clientHeight - 540;
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const appStoreWithOut = useAppStoreWithOut();
@@ -115,7 +124,7 @@ export default defineComponent({
const title = computed(() => encodeURIComponent(widget.value.title || ""));
const tips = computed(() => encodeURIComponent(widget.value.tips || ""));
const hasAssociate = computed(() =>
["Bar", "Line", "Area"].includes(
["Bar", "Line", "Area", "TopList"].includes(
dashboardStore.selectedGrid.graph &&
dashboardStore.selectedGrid.graph.type
)

View File

@@ -23,6 +23,7 @@ limitations under the License. -->
placeholder="Select a widget"
class="selectors"
@change="updateWidgetConfig"
:filterable="false"
/>
</div>
</template>

View File

@@ -0,0 +1,119 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div v-if="type === 'TopList'" class="item">
<span class="label">RefId</span>
<Selector
size="small"
:value="refIdType"
:options="RefIdTypes"
placeholder="Select a refId"
@change="updateConfig({ refIdType: $event[0].value })"
class="selector"
/>
</div>
<div v-else class="item">
<span class="label">{{ t("enableRelatedTrace") }}</span>
<el-switch
v-model="enableRelate"
active-text="Yes"
inactive-text="No"
@change="updateConfig({ enableRelate })"
/>
</div>
<div v-if="enableRelate">
<div class="item">
<span class="label">{{ t("status") }}</span>
<Selector
size="small"
:value="status"
:options="Status"
placeholder="Select a status"
@change="updateConfig({ status: $event[0].value })"
class="selector"
/>
</div>
<div class="item">
<span class="label">{{ t("setOrder") }}</span>
<Selector
size="small"
:value="queryOrder"
:options="QueryOrders"
placeholder="Select a option"
class="selector"
@change="updateConfig({ queryOrder: $event[0].value })"
/>
</div>
<div class="item">
<span class="label">{{ t("setLatencyDuration") }}</span>
<el-switch
v-model="latency"
active-text="Yes"
inactive-text="No"
@change="updateConfig({ latency })"
/>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { RefIdTypes } from "../../data";
const QueryOrders = [
{ label: "None", value: "BY_START_TIME" },
{ label: "Duration", value: "BY_DURATION" },
];
const Status = [
{ label: "None", value: "ALL" },
{ label: "Success", value: "SUCCESS" },
{ label: "Error", value: "ERROR" },
];
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const { graph, relatedTrace } = dashboardStore.selectedGrid;
const traceOpt = relatedTrace || {};
const status = ref<string>(traceOpt.status || Status[0].value);
const queryOrder = ref<string>(traceOpt.queryOrder || QueryOrders[0].value);
const latency = ref<boolean>(traceOpt.latency || false);
const enableRelate = ref<boolean>(traceOpt.enableRelate || false);
const type = ref<string>((graph && graph.type) || "");
const refIdType = ref<string>(traceOpt.refIdType || "traceId");
function updateConfig(param: { [key: string]: unknown }) {
const relatedTrace = {
...dashboardStore.selectedGrid.relatedTrace,
...param,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, relatedTrace });
}
</script>
<style lang="scss" scoped>
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.item {
margin-bottom: 10px;
}
.selector {
width: 500px;
}
</style>

View File

@@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<Legend />
<div>
<span class="label">{{ t("areaOpacity") }}</span>
<el-slider
@@ -31,6 +32,7 @@ limitations under the License. -->
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Legend from "./components/Legend.vue";
const { t } = useI18n();
const dashboardStore = useDashboardStore();

View File

@@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<Legend />
<div>
<span class="label">{{ t("showBackground") }}</span>
<el-switch
@@ -27,6 +28,7 @@ limitations under the License. -->
import { ref } from "vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useI18n } from "vue-i18n";
import Legend from "./components/Legend.vue";
const { t } = useI18n();
const dashboardStore = useDashboardStore();

View File

@@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<Legend />
<div>
<span class="label">{{ t("showXAxis") }}</span>
<el-switch
@@ -63,6 +64,7 @@ limitations under the License. -->
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Legend from "./components/Legend.vue";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
@@ -82,8 +84,8 @@ function updateConfig(param: { [key: string]: unknown }) {
<style lang="scss" scoped>
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
margin-top: 5px;
margin-bottom: -5px;
}
</style>

View File

@@ -0,0 +1,138 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div>
<span class="label mr-5">{{ t("showLegend") }}</span>
<el-switch
v-model="legend.show"
active-text="Yes"
inactive-text="No"
@change="updateLegendConfig({ show: legend.show })"
/>
</div>
<div>
<span class="label">{{ t("asTable") }}</span>
<el-switch
v-model="legend.asTable"
active-text="Yes"
inactive-text="No"
@change="updateLegendConfig({ asTable: legend.asTable })"
/>
</div>
<div v-show="legend.asTable">
<span class="label">{{ t("legendOptions") }}</span>
<span class="title mr-5">{{ t("toTheRight") }}</span>
<el-switch
v-model="legend.toTheRight"
active-text="Yes"
inactive-text="No"
@change="updateLegendConfig({ toTheRight: legend.toTheRight })"
/>
<span class="title ml-20 mr-5">{{ t("width") }}</span>
<el-input
v-model="legend.width"
class="inputs"
size="small"
placeholder="Please input the width"
@change="updateLegendConfig({ width: legend.width })"
/>
</div>
<div v-show="legend.asTable">
<span class="label">{{ t("legendValues") }}</span>
<span class="title mr-5">{{ t("min") }}</span>
<el-switch
v-model="legend.min"
active-text="Yes"
inactive-text="No"
@change="updateLegendConfig({ min: legend.min })"
/>
<span class="title ml-20 mr-5">{{ t("max") }}</span>
<el-switch
v-model="legend.max"
active-text="Yes"
inactive-text="No"
@change="updateLegendConfig({ max: legend.max })"
/>
<span class="title ml-20 mr-5">{{ t("mean") }}</span>
<el-switch
v-model="legend.mean"
active-text="Yes"
inactive-text="No"
@change="updateLegendConfig({ mean: legend.mean })"
/>
<span class="title ml-20 mr-5">{{ t("total") }}</span>
<el-switch
v-model="legend.total"
active-text="Yes"
inactive-text="No"
@change="updateLegendConfig({ total: legend.total })"
/>
</div>
</template>
<script lang="ts" setup>
import { computed, reactive } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { LegendOptions } from "@/types/dashboard";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = computed(() => dashboardStore.selectedGrid.graph || {});
const legend = reactive<LegendOptions>({
show: true,
total: false,
min: false,
max: false,
mean: false,
asTable: false,
toTheRight: false,
width: 130,
...graph.value.legend,
});
function updateLegendConfig(param: { [key: string]: unknown }) {
const g = {
...dashboardStore.selectedGrid.graph,
legend: {
...dashboardStore.selectedGrid.graph.legend,
...param,
},
};
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
graph: g,
});
}
</script>
<style lang="scss" scoped>
.label {
font-size: 13px;
display: block;
margin-top: 5px;
margin-bottom: -5px;
}
.title {
font-size: 12px;
display: inline-flex;
height: 32px;
line-height: 34px;
vertical-align: middle;
}
.inputs {
width: 120px;
}
</style>

View File

@@ -19,10 +19,12 @@ import StyleOptions from "./graph-styles";
import WidgetOptions from "./WidgetOptions.vue";
import MetricOptions from "./metric/Index.vue";
import AssociateOptions from "./AssociateOptions.vue";
import RelatedTraceOptions from "./RelatedTraceOptions.vue";
export default {
...StyleOptions,
WidgetOptions,
MetricOptions,
AssociateOptions,
RelatedTraceOptions,
};

View File

@@ -60,7 +60,10 @@ limitations under the License. -->
/>
</el-popover>
<span
v-show="states.isList || states.metricTypes[0] === 'readMetricsValues'"
v-show="
states.isList ||
states.metricTypes[0] === ProtocolTypes.ReadMetricsValues
"
>
<Icon
class="cp mr-5"
@@ -106,6 +109,7 @@ import {
ChartTypes,
PodsChartTypes,
MetricsType,
ProtocolTypes,
} from "../../../data";
import { ElMessage } from "element-plus";
import Icon from "@/components/Icon.vue";
@@ -113,7 +117,7 @@ import {
useQueryProcessor,
useSourceProcessor,
useGetMetricEntity,
} from "@/hooks/useProcessor";
} from "@/hooks/useMetricsProcessor";
import { useI18n } from "vue-i18n";
import { DashboardItem, MetricConfigOpt } from "@/types/dashboard";
import Standard from "./Standard.vue";
@@ -191,7 +195,10 @@ async function setMetricType(chart?: any) {
states.metricList = (arr || []).filter(
(d: { catalog: string; type: string }) => {
if (states.isList) {
if (d.type === MetricsType.REGULAR_VALUE) {
if (
d.type === MetricsType.REGULAR_VALUE ||
d.type === MetricsType.LABELED_VALUE
) {
return d;
}
} else if (g.type === "Table") {
@@ -239,7 +246,10 @@ async function setMetricType(chart?: any) {
}
function setDashboards(type?: string) {
const chart = type || dashboardStore.selectedGrid.graph || {};
const chart =
type ||
(dashboardStore.selectedGrid.graph &&
dashboardStore.selectedGrid.graph.type);
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
const arr = list.reduce(
(
@@ -248,9 +258,9 @@ function setDashboards(type?: string) {
) => {
if (d.layer === dashboardStore.layerId) {
if (
(d.entity === EntityType[0].value && chart.type === "ServiceList") ||
(d.entity === EntityType[2].value && chart.type === "EndpointList") ||
(d.entity === EntityType[3].value && chart.type === "InstanceList")
(d.entity === EntityType[0].value && chart === "ServiceList") ||
(d.entity === EntityType[2].value && chart === "EndpointList") ||
(d.entity === EntityType[3].value && chart === "InstanceList")
) {
prev.push({
...d,
@@ -419,13 +429,7 @@ function setMetricTypeList(type: string) {
return MetricTypes[type];
}
if (states.isList || graph.value.type === "Table") {
return [
{ label: "read all values in the duration", value: "readMetricsValues" },
{
label: "read the single value in the duration",
value: "readMetricsValue",
},
];
return [MetricTypes.REGULAR_VALUE[0], MetricTypes.REGULAR_VALUE[1]];
}
return MetricTypes[type];
}

View File

@@ -97,7 +97,7 @@ import { useI18n } from "vue-i18n";
import { SortOrder, CalculationOpts } from "../../../data";
import { useDashboardStore } from "@/store/modules/dashboard";
import { MetricConfigOpt } from "@/types/dashboard";
import { ListChartTypes } from "../../../data";
import { ListChartTypes, ProtocolTypes } from "../../../data";
/*global defineEmits, defineProps */
const props = defineProps({
@@ -115,16 +115,25 @@ const currentMetric = ref<MetricConfigOpt>({
topN: props.currentMetricConfig.topN || 10,
});
const metricTypes = dashboardStore.selectedGrid.metricTypes || [];
const metricType = ref<string>(metricTypes[props.index]);
const metricType = computed(
() => (dashboardStore.selectedGrid.metricTypes || [])[props.index]
);
const hasLabel = computed(() => {
const graph = dashboardStore.selectedGrid.graph || {};
return (
ListChartTypes.includes(graph.type) ||
metricType.value === "readLabeledMetricsValues"
[
ProtocolTypes.ReadLabeledMetricsValues,
ProtocolTypes.ReadMetricsValues,
].includes(metricType.value)
);
});
const isTopn = computed(() =>
["sortMetrics", "readSampledRecords"].includes(metricTypes[props.index])
[
ProtocolTypes.SortMetrics,
ProtocolTypes.ReadSampledRecords,
ProtocolTypes.ReadRecords,
].includes(metricTypes[props.index])
);
function updateConfig(index: number, param: { [key: string]: string }) {
const key = Object.keys(param)[0];

View File

@@ -40,7 +40,7 @@ limitations under the License. -->
<span class="tab-icons">
<el-tooltip content="Copy Link" placement="bottom">
<i @click="copyLink">
<Icon size="middle" iconName="review-list" class="tab-icon" />
<Icon size="middle" iconName="copy" class="tab-icon" />
</i>
</el-tooltip>
</span>
@@ -194,6 +194,7 @@ export default defineComponent({
editTabIndex.value = index;
}
function handleClick(el: any) {
el.stopPropagation();
needQuery.value = true;
if (["tab-name", "edit-tab"].includes(el.target.className)) {
return;

View File

@@ -62,8 +62,10 @@ limitations under the License. -->
metricTypes: data.metricTypes || [''],
i: data.i,
id: data.id,
metricConfig: data.metricConfig,
metricConfig: data.metricConfig || [],
filters: data.filters || {},
relatedTrace: data.relatedTrace || {},
associate: data.associate || [],
}"
:needQuery="needQuery"
@click="clickHandle"
@@ -85,7 +87,7 @@ import {
useQueryProcessor,
useSourceProcessor,
useGetMetricEntity,
} from "@/hooks/useProcessor";
} from "@/hooks/useMetricsProcessor";
import { EntityType, ListChartTypes } from "../data";
import { EventParams } from "@/types/dashboard";
import getDashboard from "@/hooks/useDashboardsSession";
@@ -228,6 +230,11 @@ export default defineComponent({
watch(
() => [selectorStore.currentProcess, selectorStore.currentDestProcess],
() => {
if (
!(selectorStore.currentDestProcess && selectorStore.currentProcess)
) {
return;
}
if (dashboardStore.entity === EntityType[7].value) {
queryMetrics();
}

View File

@@ -45,7 +45,17 @@ export const MetricChartType: any = {
readLabeledMetricsValues: [{ label: "Line", value: "Line" }],
readHeatMap: [{ label: "Heat Map", value: "HeatMap" }],
readSampledRecords: [{ label: "Top List", value: "TopList" }],
readRecords: [{ label: "Top List", value: "TopList" }],
};
export enum ProtocolTypes {
ReadRecords = "readRecords",
ReadSampledRecords = "readSampledRecords",
SortMetrics = "sortMetrics",
ReadLabeledMetricsValues = "readLabeledMetricsValues",
ReadHeatMap = "readHeatMap",
ReadMetricsValues = "readMetricsValues",
ReadMetricsValue = "readMetricsValue",
}
export const DefaultGraphConfig: { [key: string]: any } = {
Bar: {
type: "Bar",
@@ -133,9 +143,7 @@ export const MetricTypes: {
HEATMAP: [
{ label: "read heatmap values in the duration", value: "readHeatMap" },
],
SAMPLED_RECORD: [
{ label: "get sorted topN values", value: "readSampledRecords" },
],
SAMPLED_RECORD: [{ label: "get sorted topN values", value: "readRecords" }],
};
export enum MetricCatalog {
@@ -146,6 +154,7 @@ export enum MetricCatalog {
SERVICE_RELATION = "ServiceRelation",
SERVICE_INSTANCE_RELATION = "ServiceInstanceRelation",
ENDPOINT_RELATION = "EndpointRelation",
PROCESS_RELATION = "ProcessRelation",
}
export const EntityType = [
{ value: "Service", label: "Service", key: 1 },
@@ -172,7 +181,7 @@ export const SortOrder = [
];
export const AllTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
@@ -180,7 +189,7 @@ export const AllTools = [
];
export const ServiceTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
@@ -192,7 +201,7 @@ export const ServiceTools = [
];
export const InstanceTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "assignment", content: "Add Log", id: "addLog" },
@@ -206,7 +215,7 @@ export const InstanceTools = [
];
export const EndpointTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
@@ -215,25 +224,25 @@ export const EndpointTools = [
];
export const ProcessTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "time_range", content: "Add Time Range Text", id: "addTimeRange" },
];
export const ServiceRelationTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
];
export const EndpointRelationTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
];
export const InstanceRelationTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
];
@@ -299,3 +308,7 @@ export const CalculationOpts = [
{ label: "Seconds to days", value: "secondToDay" },
{ label: "Nanoseconds to milliseconds", value: "nanosecondToMillisecond" },
];
export const RefIdTypes = [
{ label: "Trace ID", value: "traceId" },
{ label: "None", value: "none" },
];

View File

@@ -24,7 +24,12 @@ limitations under the License. -->
<script lang="ts" setup>
import type { PropType } from "vue";
import Line from "./Line.vue";
import { AreaConfig, EventParams } from "@/types/dashboard";
import {
AreaConfig,
EventParams,
RelatedTrace,
Filters,
} from "@/types/dashboard";
/*global defineProps, defineEmits */
const emits = defineEmits(["click"]);
@@ -37,16 +42,11 @@ defineProps({
config: {
type: Object as PropType<
AreaConfig & {
filters: {
sourceId: string;
duration: {
startTime: string;
endTime: string;
};
isRange: boolean;
dataIndex?: number;
};
} & { id: string }
filters: Filters;
relatedTrace: RelatedTrace;
id: string;
associate: { widgetId: string }[];
}
>,
default: () => ({}),
},

View File

@@ -13,12 +13,26 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<Graph :option="option" @select="clickEvent" :filters="config.filters" />
<div class="graph" :class="isRight ? 'flex-h' : 'flex-v'">
<Graph
:option="option"
@select="clickEvent"
:filters="config.filters"
:associate="config.associate || []"
/>
<Legend :config="config.legend" :data="data" :intervalTime="intervalTime" />
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import type { PropType } from "vue";
import { BarConfig, EventParams } from "@/types/dashboard";
import {
BarConfig,
EventParams,
RelatedTrace,
Filters,
} from "@/types/dashboard";
import useLegendProcess from "@/hooks/useLegendProcessor";
/*global defineProps, defineEmits */
const emits = defineEmits(["click"]);
@@ -32,20 +46,18 @@ const props = defineProps({
config: {
type: Object as PropType<
BarConfig & {
filters: {
sourceId: string;
duration: {
startTime: string;
endTime: string;
};
isRange: boolean;
dataIndex?: number;
};
} & { id: string }
filters: Filters;
relatedTrace: RelatedTrace;
id: string;
associate: { widgetId: string }[];
}
>,
default: () => ({}),
},
});
const { showEchartsLegend, isRight, chartColors } = useLegendProcess(
props.config.legend
);
const option = computed(() => getOption());
function getOption() {
@@ -75,52 +87,20 @@ function getOption() {
},
};
});
let color: string[] = [];
switch (keys.length) {
case 2:
color = ["#FF6A84", "#a0b1e6"];
break;
case 1:
color = ["#3f96e3"];
break;
default:
color = [
"#30A4EB",
"#45BFC0",
"#FFCC55",
"#FF6A84",
"#a0a7e6",
"#c23531",
"#2f4554",
"#61a0a8",
"#d48265",
"#91c7ae",
"#749f83",
"#ca8622",
"#bda29a",
"#6e7074",
"#546570",
"#c4ccd3",
];
break;
}
const color: string[] = chartColors(keys);
return {
color,
tooltip: {
trigger: "axis",
zlevel: 1000,
z: 60,
confine: true,
textStyle: {
fontSize: 13,
trigger: "none",
axisPointer: {
type: "cross",
color: "#333",
backgroundColor: "rgba(255, 255, 255, 0.8)",
},
enterable: true,
extraCssText: "max-height: 300px; overflow: auto; border: none",
},
legend: {
type: "scroll",
show: keys.length === 1 ? false : true,
show: showEchartsLegend(keys),
icon: "circle",
top: 0,
left: 0,
@@ -136,6 +116,12 @@ function getOption() {
bottom: 5,
containLabel: true,
},
axisPointer: {
label: {
color: "#333",
backgroundColor: "rgba(255, 255, 255, 0.8)",
},
},
xAxis: {
type: "category",
axisTick: {
@@ -160,3 +146,9 @@ function clickEvent(params: EventParams) {
emits("click", params);
}
</script>
<style lang="scss" scoped>
.graph {
width: 100%;
height: 100%;
}
</style>

View File

@@ -17,21 +17,21 @@ limitations under the License. -->
<div class="search">
<el-input
v-model="searchText"
placeholder="Please input endpoint name"
size="small"
placeholder="Search for more endpoints"
@change="searchList"
class="inputs"
>
<template #append>
<el-button size="small" @click="searchList">
<Icon size="sm" iconName="search" />
<el-button @click="searchList" class="btn">
<Icon size="middle" iconName="search" />
</el-button>
</template>
</el-input>
<span class="ml-5 tips">{{ t("endpointTips") }}</span>
</div>
<div class="list">
<el-table v-loading="chartLoading" :data="endpoints" style="width: 100%">
<el-table-column label="Endpoints">
<el-table-column label="Endpoints" fixed min-width="220">
<template #default="scope">
<span
class="link"
@@ -45,7 +45,12 @@ limitations under the License. -->
<ColumnGraph
:intervalTime="intervalTime"
:colMetrics="colMetrics"
:config="config"
:config="{
...config,
metrics: colMetrics,
metricConfig,
metricTypes,
}"
v-if="colMetrics.length"
/>
</el-table>
@@ -53,14 +58,18 @@ limitations under the License. -->
</div>
</template>
<script setup lang="ts">
import { ref, watch, computed } from "vue";
import { ref, watch } from "vue";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
import { useI18n } from "vue-i18n";
import type { PropType } from "vue";
import { EndpointListConfig } from "@/types/dashboard";
import { Endpoint } from "@/types/selector";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useQueryPodsMetrics, usePodsSource } from "@/hooks/useProcessor";
import {
useQueryPodsMetrics,
usePodsSource,
} from "@/hooks/useMetricsProcessor";
import { EntityType } from "../data";
import router from "@/router";
import getDashboard from "@/hooks/useDashboardsSession";
@@ -92,14 +101,15 @@ const props = defineProps({
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
});
const { t } = useI18n();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const chartLoading = ref<boolean>(false);
const endpoints = ref<Endpoint[]>([]);
const searchText = ref<string>("");
const colMetrics = computed(() =>
(props.config.metrics || []).filter((d: string) => d)
);
const colMetrics = ref<string[]>([]);
const metricConfig = ref<MetricConfigOpt[]>(props.config.metricConfig || []);
const metricTypes = ref<string[]>(props.config.metricTypes || []);
if (props.needQuery) {
queryEndpoints();
@@ -123,8 +133,8 @@ async function queryEndpointMetrics(currentPods: Endpoint[]) {
return;
}
const metrics = props.config.metrics || [];
const metricTypes = props.config.metricTypes || [];
if (metrics.length && metrics[0] && metricTypes.length && metricTypes[0]) {
const types = props.config.metricTypes || [];
if (metrics.length && metrics[0] && types.length && types[0]) {
const params = await useQueryPodsMetrics(
currentPods,
props.config,
@@ -136,12 +146,18 @@ async function queryEndpointMetrics(currentPods: Endpoint[]) {
ElMessage.error(json.errors);
return;
}
const metricConfig = props.config.metricConfig || [];
endpoints.value = usePodsSource(currentPods, json, {
...props.config,
metricConfig: metricConfig,
});
const { data, names, metricConfigArr, metricTypesArr } = usePodsSource(
currentPods,
json,
{
...props.config,
metricConfig: metricConfig.value,
}
);
endpoints.value = data;
colMetrics.value = names;
metricTypes.value = metricTypesArr;
metricConfig.value = metricConfigArr;
return;
}
endpoints.value = currentPods;
@@ -164,11 +180,16 @@ async function searchList() {
await queryEndpoints();
}
watch(
() => [...(props.config.metricTypes || []), ...(props.config.metrics || [])],
() => [
...(props.config.metricTypes || []),
...(props.config.metrics || []),
...(props.config.metricConfig || []),
],
(data, old) => {
if (JSON.stringify(data) === JSON.stringify(old)) {
return;
}
metricConfig.value = props.config.metricConfig;
queryEndpointMetrics(endpoints.value);
}
);
@@ -178,24 +199,11 @@ watch(
queryEndpoints();
}
);
watch(
() => [...(props.config.metricConfig || [])],
(data, old) => {
if (JSON.stringify(data) === JSON.stringify(old)) {
return;
}
queryEndpointMetrics(endpoints.value);
}
);
</script>
<style lang="scss" scoped>
@import "./style.scss";
.chart {
height: 60px;
}
.inputs {
width: 300px;
.tips {
color: rgba(255, 0, 0, 0.5);
}
</style>

View File

@@ -18,12 +18,11 @@ limitations under the License. -->
<el-input
v-model="searchText"
placeholder="Please input instance name"
size="small"
@change="searchList"
class="inputs"
>
<template #append>
<el-button size="small" @click="searchList">
<el-button class="btn" @click="searchList">
<Icon size="sm" iconName="search" />
</el-button>
</template>
@@ -31,7 +30,7 @@ limitations under the License. -->
</div>
<div class="list">
<el-table v-loading="chartLoading" :data="instances" style="width: 100%">
<el-table-column label="Service Instances">
<el-table-column label="Service Instances" fixed min-width="320">
<template #default="scope">
<span
class="link"
@@ -43,12 +42,17 @@ limitations under the License. -->
</template>
</el-table-column>
<ColumnGraph
v-if="colMetrics.length"
:intervalTime="intervalTime"
:colMetrics="colMetrics"
:config="config"
:config="{
...config,
metrics: colMetrics,
metricConfig,
metricTypes,
}"
v-if="colMetrics.length"
/>
<el-table-column label="Attributes">
<el-table-column label="Attributes" fixed="right" min-width="100">
<template #default="scope">
<el-popover placement="left" :width="400" trigger="click">
<template #reference>
@@ -83,7 +87,7 @@ limitations under the License. -->
</div>
</template>
<script setup lang="ts">
import { ref, watch, computed } from "vue";
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
import type { PropType } from "vue";
@@ -91,7 +95,10 @@ import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard";
import { InstanceListConfig } from "@/types/dashboard";
import { Instance } from "@/types/selector";
import { useQueryPodsMetrics, usePodsSource } from "@/hooks/useProcessor";
import {
useQueryPodsMetrics,
usePodsSource,
} from "@/hooks/useMetricsProcessor";
import { EntityType } from "../data";
import router from "@/router";
import getDashboard from "@/hooks/useDashboardsSession";
@@ -127,9 +134,9 @@ const chartLoading = ref<boolean>(false);
const instances = ref<Instance[]>([]); // current instances
const pageSize = 10;
const searchText = ref<string>("");
const colMetrics = computed(() =>
(props.config.metrics || []).filter((d: string) => d)
);
const colMetrics = ref<string[]>([]);
const metricConfig = ref<MetricConfigOpt[]>(props.config.metricConfig || []);
const metricTypes = ref<string[]>(props.config.metricTypes || []);
if (props.needQuery) {
queryInstance();
}
@@ -155,9 +162,9 @@ async function queryInstanceMetrics(currentInstances: Instance[]) {
return;
}
const metrics = props.config.metrics || [];
const metricTypes = props.config.metricTypes || [];
const types = props.config.metricTypes || [];
if (metrics.length && metrics[0] && metricTypes.length && metricTypes[0]) {
if (metrics.length && metrics[0] && types.length && types[0]) {
const params = await useQueryPodsMetrics(
currentInstances,
props.config,
@@ -169,11 +176,18 @@ async function queryInstanceMetrics(currentInstances: Instance[]) {
ElMessage.error(json.errors);
return;
}
const metricConfig = props.config.metricConfig || [];
instances.value = usePodsSource(currentInstances, json, {
...props.config,
metricConfig,
});
const { data, names, metricConfigArr, metricTypesArr } = usePodsSource(
currentInstances,
json,
{
...props.config,
metricConfig: metricConfig.value,
}
);
instances.value = data;
colMetrics.value = names;
metricTypes.value = metricTypesArr;
metricConfig.value = metricConfigArr;
return;
}
instances.value = currentInstances;
@@ -216,11 +230,16 @@ function searchList() {
}
watch(
() => [...(props.config.metricTypes || []), ...(props.config.metrics || [])],
() => [
...(props.config.metricTypes || []),
...(props.config.metrics || []),
...(props.config.metricConfig || []),
],
(data, old) => {
if (JSON.stringify(data) === JSON.stringify(old)) {
return;
}
metricConfig.value = props.config.metricConfig;
queryInstanceMetrics(instances.value);
}
);
@@ -230,27 +249,10 @@ watch(
queryInstance();
}
);
watch(
() => [...(props.config.metricConfig || [])],
(data, old) => {
if (JSON.stringify(data) === JSON.stringify(old)) {
return;
}
queryInstanceMetrics(instances.value);
}
);
</script>
<style lang="scss" scoped>
@import "./style.scss";
.chart {
height: 60px;
}
.inputs {
width: 300px;
}
.attributes {
max-height: 400px;
overflow: auto;

View File

@@ -13,12 +13,28 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<Graph :option="option" @select="clickEvent" :filters="config.filters" />
<div class="graph flex-v" :class="setRight ? 'flex-h' : 'flex-v'">
<Graph
:option="option"
@select="clickEvent"
:filters="config.filters"
:relatedTrace="config.relatedTrace"
:associate="config.associate || []"
/>
<Legend :config="config.legend" :data="data" :intervalTime="intervalTime" />
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import { computed, ref } from "vue";
import type { PropType } from "vue";
import { LineConfig, EventParams } from "@/types/dashboard";
import {
LineConfig,
EventParams,
RelatedTrace,
Filters,
} from "@/types/dashboard";
import Legend from "./components/Legend.vue";
import useLegendProcess from "@/hooks/useLegendProcessor";
/*global defineProps, defineEmits */
const emits = defineEmits(["click"]);
@@ -32,16 +48,11 @@ const props = defineProps({
config: {
type: Object as PropType<
LineConfig & {
filters: {
sourceId: string;
duration: {
startTime: string;
endTime: string;
};
isRange: boolean;
dataIndex?: number;
};
} & { id: string }
filters?: Filters;
relatedTrace?: RelatedTrace;
id?: string;
associate: { widgetId: string }[];
}
>,
default: () => ({
step: false,
@@ -55,8 +66,13 @@ const props = defineProps({
}),
},
});
const setRight = ref<boolean>(false);
const option = computed(() => getOption());
function getOption() {
const { showEchartsLegend, isRight, chartColors } = useLegendProcess(
props.config.legend
);
setRight.value = isRight;
const keys = Object.keys(props.data || {}).filter(
(i: any) => Array.isArray(props.data[i]) && props.data[i].length
);
@@ -85,43 +101,21 @@ function getOption() {
}
return serie;
});
let color: string[] = [];
switch (keys.length) {
case 2:
color = ["#FF6A84", "#a0b1e6"];
break;
case 1:
color = ["#3f96e3"];
break;
default:
color = [
"#30A4EB",
"#45BFC0",
"#FFCC55",
"#FF6A84",
"#a0a7e6",
"#c23531",
"#2f4554",
"#61a0a8",
"#d48265",
"#91c7ae",
"#749f83",
"#ca8622",
"#bda29a",
"#6e7074",
"#546570",
"#c4ccd3",
];
break;
}
const color: string[] = chartColors(keys);
const tooltip = {
trigger: "axis",
textStyle: {
fontSize: 12,
trigger: "none",
axisPointer: {
type: "cross",
color: "#333",
backgroundColor: "rgba(255, 255, 255, 0.8)",
},
enterable: true,
confine: true,
// trigger: "axis",
// textStyle: {
// fontSize: 12,
// color: "#333",
// },
// enterable: true,
// confine: true,
extraCssText: "max-height: 300px; overflow: auto; border: none;",
};
const tips = {
@@ -142,7 +136,7 @@ function getOption() {
tooltip: props.config.smallTips ? tips : tooltip,
legend: {
type: "scroll",
show: keys.length === 1 ? false : true,
show: showEchartsLegend(keys),
icon: "circle",
top: 0,
left: 0,
@@ -151,8 +145,14 @@ function getOption() {
color: props.theme === "dark" ? "#fff" : "#333",
},
},
axisPointer: {
label: {
color: "#333",
backgroundColor: "rgba(255, 255, 255, 0.8)",
},
},
grid: {
top: keys.length === 1 ? 15 : 55,
top: showEchartsLegend(keys) ? 35 : 10,
left: 0,
right: 10,
bottom: 5,
@@ -190,3 +190,9 @@ function clickEvent(params: EventParams) {
emits("click", params);
}
</script>
<style lang="scss" scoped>
.graph {
width: 100%;
height: 100%;
}
</style>

View File

@@ -18,12 +18,11 @@ limitations under the License. -->
<el-input
v-model="searchText"
placeholder="Please input service name"
size="small"
@change="searchList"
class="inputs mt-5"
>
<template #append>
<el-button size="small" @click="searchList">
<el-button class="btn" @click="searchList">
<Icon size="sm" iconName="search" />
</el-button>
</template>
@@ -38,12 +37,17 @@ limitations under the License. -->
:border="true"
:style="{ fontSize: '14px' }"
>
<el-table-column label="Service Groups" v-if="config.showGroup">
<el-table-column
fixed
label="Service Groups"
v-if="config.showGroup"
min-width="150"
>
<template #default="scope">
{{ scope.row.group }}
</template>
</el-table-column>
<el-table-column label="Service Names">
<el-table-column fixed label="Service Names" min-width="220">
<template #default="scope">
<span
class="link"
@@ -57,7 +61,12 @@ limitations under the License. -->
<ColumnGraph
:intervalTime="intervalTime"
:colMetrics="colMetrics"
:config="config"
:config="{
...config,
metrics: colMetrics,
metricConfig,
metricTypes,
}"
v-if="colMetrics.length"
/>
</el-table>
@@ -76,7 +85,7 @@ limitations under the License. -->
</div>
</template>
<script setup lang="ts">
import { watch, ref, computed } from "vue";
import { watch, ref } from "vue";
import { ElMessage } from "element-plus";
import type { PropType } from "vue";
import { ServiceListConfig } from "@/types/dashboard";
@@ -84,7 +93,10 @@ import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { Service } from "@/types/selector";
import { useQueryPodsMetrics, usePodsSource } from "@/hooks/useProcessor";
import {
useQueryPodsMetrics,
usePodsSource,
} from "@/hooks/useMetricsProcessor";
import { EntityType } from "../data";
import router from "@/router";
import getDashboard from "@/hooks/useDashboardsSession";
@@ -103,7 +115,9 @@ const props = defineProps({
metrics: string[];
metricTypes: string[];
isEdit: boolean;
} & { metricConfig: MetricConfigOpt[] }
names: string[];
metricConfig: MetricConfigOpt[];
}
>,
default: () => ({ dashboardName: "", fontSize: 12 }),
},
@@ -116,12 +130,13 @@ const appStore = useAppStoreWithOut();
const chartLoading = ref<boolean>(false);
const pageSize = 10;
const services = ref<Service[]>([]);
const colMetrics = ref<string[]>([]);
const searchText = ref<string>("");
const groups = ref<any>({});
const sortServices = ref<(Service & { merge: boolean })[]>([]);
const colMetrics = computed(() =>
(props.config.metrics || []).filter((d: string) => d)
);
const metricConfig = ref<MetricConfigOpt[]>(props.config.metricConfig || []);
const metricTypes = ref<string[]>(props.config.metricTypes || []);
queryServices();
async function queryServices() {
@@ -199,12 +214,12 @@ async function queryServiceMetrics(currentServices: Service[]) {
return;
}
const metrics = props.config.metrics || [];
const metricTypes = props.config.metricTypes || [];
const types = props.config.metricTypes || [];
if (metrics.length && metrics[0] && metricTypes.length && metricTypes[0]) {
if (metrics.length && metrics[0] && types.length && types[0]) {
const params = await useQueryPodsMetrics(
currentServices,
props.config,
{ ...props.config, metricConfig: metricConfig.value || [] },
EntityType[0].value
);
const json = await dashboardStore.fetchMetricValue(params);
@@ -213,14 +228,22 @@ async function queryServiceMetrics(currentServices: Service[]) {
ElMessage.error(json.errors);
return;
}
const metricConfig = props.config.metricConfig || [];
services.value = usePodsSource(currentServices, json, {
...props.config,
metricConfig: metricConfig || [],
});
const { data, names, metricConfigArr, metricTypesArr } = usePodsSource(
currentServices,
json,
{
...props.config,
metricConfig: metricConfig.value || [],
}
);
services.value = data;
colMetrics.value = names;
metricTypes.value = metricTypesArr;
metricConfig.value = metricConfigArr;
return;
}
services.value = currentServices;
}
function objectSpanMethod(param: any): any {
@@ -258,20 +281,16 @@ function searchList() {
}
watch(
() => [...(props.config.metricTypes || []), ...(props.config.metrics || [])],
(data, old) => {
if (JSON.stringify(data) === JSON.stringify(old)) {
return;
}
queryServiceMetrics(services.value);
}
);
watch(
() => [...(props.config.metricConfig || [])],
() => [
...(props.config.metricTypes || []),
...(props.config.metrics || []),
...(props.config.metricConfig || []),
],
(data, old) => {
if (JSON.stringify(data) === JSON.stringify(old)) {
return;
}
metricConfig.value = props.config.metricConfig;
queryServiceMetrics(services.value);
}
);
@@ -286,8 +305,4 @@ watch(
</script>
<style lang="scss" scoped>
@import "./style.scss";
.inputs {
width: 300px;
}
</style>

View File

@@ -23,44 +23,84 @@ limitations under the License. -->
{{ i.name }}
</span>
</div>
<div class="copy">
<Icon
iconName="review-list"
size="middle"
class="cp"
@click="handleClick(i.name)"
/>
</div>
<el-popover placement="bottom" trigger="click">
<template #reference>
<div class="operation-icon cp ml-10">
<Icon iconName="ellipsis_v" size="middle" />
</div>
</template>
<div class="operation" @click="handleClick(i.name)">
<span>{{ t("copy") }}</span>
</div>
<div
class="operation"
@click="viewTrace(i)"
v-show="refIdType === RefIdTypes[0].value"
>
<span>{{ t("viewTrace") }}</span>
</div>
</el-popover>
</div>
<el-progress
:stroke-width="6"
:percentage="(i.value / maxValue) * 100"
:percentage="
isNaN(Number(i.value) / maxValue)
? 0
: (Number(i.value) / maxValue) * 100
"
:color="TextColors[config.color || 'purple']"
:show-text="false"
/>
</div>
<el-drawer
v-model="showTrace"
size="100%"
:destroy-on-close="true"
:before-close="() => (showTrace = false)"
:append-to-body="true"
title="The Related Traces"
>
<Trace :data="traceOptions" />
</el-drawer>
</div>
<div class="center no-data" v-else>No Data</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { computed, ref } from "vue";
import copy from "@/utils/copy";
import { TextColors } from "@/views/dashboard/data";
import Trace from "@/views/dashboard/related/trace/Index.vue";
import { QueryOrders, Status, RefIdTypes } from "../data";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<{
[key: string]: { name: string; value: number; traceIds: string[] }[];
[key: string]: { name: string; value: number; id: string }[];
}>,
default: () => ({}),
},
config: {
type: Object as PropType<{ color: string }>,
type: Object as PropType<{
color: string;
metrics: string[];
relatedTrace: any;
}>,
default: () => ({ color: "purple" }),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
});
const { t } = useI18n();
const showTrace = ref<boolean>(false);
const traceOptions = ref<{ type: string; filters?: unknown }>({
type: "Trace",
});
const refIdType = computed(
() =>
(props.config.relatedTrace && props.config.relatedTrace.refIdType) ||
RefIdTypes[0].value
);
const key = computed(() => Object.keys(props.data)[0] || "");
const available = computed(
() =>
@@ -78,6 +118,22 @@ const maxValue = computed(() => {
function handleClick(i: string) {
copy(i);
}
function viewTrace(item: { name: string; id: string; value: unknown }) {
const filters = {
...item,
queryOrder: QueryOrders[1].value,
status: Status[2].value,
id: item.id || item.name,
metricValue: [
{ label: props.config.metrics[0], data: item.value, value: item.name },
],
};
traceOptions.value = {
...traceOptions.value,
filters,
};
showTrace.value = true;
}
</script>
<style lang="scss" scoped>
.top-list {
@@ -109,10 +165,6 @@ function handleClick(i: string) {
text-overflow: ellipsis;
}
.copy {
width: 30px;
}
.calls {
font-size: 12px;
padding: 0 5px;
@@ -141,4 +193,22 @@ function handleClick(i: string) {
-webkit-box-pack: center;
-webkit-box-align: center;
}
.operation-icon {
color: #333;
}
.operation {
padding: 5px 0;
color: #333;
cursor: pointer;
position: relative;
text-align: center;
font-size: 12px;
&:hover {
color: #409eff;
background-color: #eee;
}
}
</style>

View File

@@ -20,6 +20,7 @@ limitations under the License. -->
getLabel(metric, index)
)} ${decodeURIComponent(getUnit(index))}`"
:key="metric + index"
min-width="150"
>
<template #default="scope">
<div class="chart">
@@ -90,18 +91,18 @@ import { MetricConfigOpt } from "@/types/dashboard";
import { useListConfig } from "@/hooks/useListConfig";
import Line from "../Line.vue";
import Card from "../Card.vue";
import { MetricQueryTypes } from "@/hooks/data";
/*global defineProps */
const props = defineProps({
colMetrics: { type: Object },
config: {
type: Object as PropType<
{
i: string;
metrics: string[];
metricTypes: string[];
} & { metricConfig: MetricConfigOpt[] }
>,
type: Object as PropType<{
i: string;
metrics: string[];
metricTypes: string[];
metricConfig: MetricConfigOpt[];
}>,
default: () => ({}),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
@@ -125,6 +126,16 @@ function getLabel(metric: string, index: string) {
props.config.metricConfig[i] &&
props.config.metricConfig[i].label;
if (label) {
if (
props.config.metricTypes[i] === MetricQueryTypes.ReadLabeledMetricsValues
) {
const name = (label || "")
.split(",")
.map((item: string) => item.replace(/^\s*|\s*$/g, ""))[
props.config.metricConfig[i].index || 0
];
return encodeURIComponent(name || "");
}
return encodeURIComponent(label);
}
return encodeURIComponent(metric);
@@ -157,5 +168,6 @@ function getLabel(metric: string, index: string) {
display: inline-block;
flex-grow: 2;
height: 100%;
width: calc(100% - 30px);
}
</style>

View File

@@ -0,0 +1,195 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div
v-if="tableData.length && config.asTable"
role="region"
aria-labelledby="caption"
tabindex="0"
:style="`width: ${width}; maxHeight:${isRight ? '100%' : 130}`"
class="scroll_bar_style"
>
<table v-if="tableData.length === 1">
<thead>
<tr>
<th v-show="headerRow.length"></th>
<th>
{{ tableData[0].name }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="h in headerRow" :key="h.value">
<th>
{{ h.label }}
</th>
<td>
{{ tableData[0][h.value] }}
</td>
</tr>
</tbody>
</table>
<table v-else>
<thead>
<tr>
<th></th>
<th v-for="h in headerRow" :key="h.value">
{{ h.label }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in tableData" :key="index">
<th>
<el-popover placement="bottom" :width="230" trigger="click">
<template #reference>
<div class="name">
<Icon iconName="circle" :style="`color: ${colors[index]};`" />
<i>{{ item.name }}</i>
</div>
</template>
<div class="list">
<div class="value">
<span>{{ t("key") }}</span>
<span>{{ t("value") }}</span>
</div>
<div class="value" v-for="(d, index) in item.topN" :key="index">
<span>{{ d.key }}</span>
<span>{{ d.value }}</span>
</div>
</div>
</el-popover>
</th>
<td v-for="h in headerRow" :key="h.value">
{{ item[h.value] }}
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { LegendOptions } from "@/types/dashboard";
import useLegendProcess from "@/hooks/useLegendProcessor";
/*global defineProps */
const props = defineProps({
data: {
type: Object as PropType<{ [key: string]: number[] }>,
default: () => ({}),
},
config: {
type: Object as PropType<LegendOptions>,
default: () => ({}),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
});
const { t } = useI18n();
const tableData: any = computed(() => {
const { aggregations } = useLegendProcess(props.config);
return aggregations(props.data, props.intervalTime).source;
});
const headerRow = computed(() => {
const { aggregations } = useLegendProcess(props.config);
return aggregations(props.data, props.intervalTime).headers;
});
const isRight = computed(() => useLegendProcess(props.config).isRight);
const width = computed(() =>
props.config.width
? props.config.width + "px"
: isRight.value
? "150px"
: "100%"
);
const colors = computed(() => {
const keys = Object.keys(props.data || {}).filter(
(i: any) => Array.isArray(props.data[i]) && props.data[i].length
);
const { chartColors } = useLegendProcess(props.config);
return chartColors(keys);
});
</script>
<style lang="scss" scoped>
table {
font-size: 12px;
white-space: nowrap;
margin: 0;
border: none;
border-collapse: separate;
border-spacing: 0;
table-layout: fixed;
}
table th {
padding: 5px;
}
table thead th {
position: sticky;
top: 0;
z-index: 1;
width: 25vw;
background: #fff;
text-align: left;
}
.name {
cursor: pointer;
}
table td {
padding: 5px;
}
table thead th:first-child {
position: sticky;
left: 0;
z-index: 2;
}
table tbody th {
font-weight: bold;
font-style: normal;
text-align: left;
background: #fff;
position: sticky;
left: 0;
z-index: 1;
}
[role="region"][aria-labelledby][tabindex] {
overflow: auto;
}
i {
font-style: normal;
}
.value {
span {
display: inline-block;
padding: 5px;
width: 80px;
}
}
.list {
height: 360px;
overflow: auto;
}
</style>

View File

@@ -23,6 +23,7 @@
.list {
margin-top: 10px;
margin-bottom: 10px;
height: calc(100% - 90px);
}
.pagination {
@@ -40,9 +41,21 @@
}
.search {
text-align: right;
margin-top: 5px;
}
.input-with-search {
width: 400px;
}
.btn {
margin-top: -12px;
}
.chart {
height: 60px;
}
.inputs {
width: 300px;
}

View File

@@ -19,11 +19,11 @@ export default (d3: any, graph: any, diff: number[]) =>
d3
.zoom()
.scaleExtent([0.3, 10])
.on("zoom", (event: any) => {
.on("zoom", (d: any) => {
graph.attr(
"transform",
`translate(${event.transform.x + diff[0]},${
event.transform.y + diff[1]
})scale(${event.transform.k})`
`translate(${d.transform.x + diff[0]},${
d.transform.y + diff[1]
})scale(${d.transform.k})`
);
});

View File

@@ -193,22 +193,22 @@ async function init() {
state.endpoint = { value: "0", label: "All" };
}
function fetchSelectors() {
async function fetchSelectors() {
if (dashboardStore.entity === EntityType[1].value) {
getServices();
await getServices();
return;
}
if (dashboardStore.entity === EntityType[2].value) {
getInstances();
await getInstances();
return;
}
if (dashboardStore.entity === EntityType[3].value) {
getEndpoints();
await getEndpoints();
return;
}
if (dashboardStore.entity === EntityType[0].value) {
getInstances();
getEndpoints();
await getInstances();
await getEndpoints();
}
}

View File

@@ -53,7 +53,7 @@ const { t } = useI18n();
height: 100%;
flex-grow: 2;
min-width: 700px;
overflow: auto;
overflow: hidden;
position: relative;
width: calc(100% - 330px);
}

View File

@@ -17,96 +17,6 @@
import icons from "@/assets/img/icons";
import { Call } from "@/types/topology";
export const linkElement = (graph: any) => {
const linkEnter = graph
.append("path")
.attr("class", "topo-call")
.attr("marker-end", "url(#arrow)")
.attr("stroke", "#97B0F8")
.attr("d", (d: Call) => {
const controlPos = computeControlPoint(
[d.source.x, d.source.y - 5],
[d.target.x, d.target.y - 5],
0.5
);
if (d.lowerArc) {
controlPos[1] =
Math.abs(controlPos[1]) < 50
? -controlPos[1] + 90
: -controlPos[1] - 10;
}
return (
"M" +
d.source.x +
" " +
(d.source.y - 5) +
" " +
"Q" +
controlPos[0] +
" " +
controlPos[1] +
" " +
d.target.x +
" " +
(d.target.y - 5)
);
});
return linkEnter;
};
export const anchorElement = (graph: any, funcs: any, tip: any) => {
const linkEnter = graph
.append("g")
.attr("class", "topo-line-anchor")
.on("mouseover", function (event: unknown, d: unknown) {
tip.html(funcs.tipHtml).show(d, this);
})
.on("mouseout", function () {
tip.hide(this);
})
.on("click", (event: unknown, d: unknown) => {
funcs.handleLinkClick(event, d);
});
linkEnter
.append("image")
.attr("width", 15)
.attr("height", 15)
.attr("x", (d: Call) => {
const p = getMidpoint(d);
return p[0] - 8;
})
.attr("y", (d: Call) => {
const p = getMidpoint(d);
return p[1] - 13;
})
.attr("xlink:href", (d: Call) => {
const types = [...d.sourceComponents, ...d.targetComponents];
if (types.includes("tcp") || types.includes("http")) {
return icons.HTTPDARK;
}
if (types.includes("https") || types.includes("tls")) {
return icons.HTTPS;
}
});
return linkEnter;
};
export const arrowMarker = (graph: any) => {
const defs = graph.append("defs");
const arrow = defs
.append("marker")
.attr("id", "arrow")
.attr("class", "topo-line-arrow")
.attr("markerUnits", "strokeWidth")
.attr("markerWidth", "8")
.attr("markerHeight", "8")
.attr("viewBox", "0 0 12 12")
.attr("refX", "10")
.attr("refY", "6")
.attr("orient", "auto");
const arrowPath = "M2,2 L10,6 L2,10 L6,6 L2,2";
arrow.append("path").attr("d", arrowPath).attr("fill", "#97B0F8");
return arrow;
};
// Control Point coordinates of quadratic Bezier curve
function computeControlPoint(ps: number[], pe: number[], arc = 0.5) {
const deltaX = pe[0] - ps[0];
@@ -137,15 +47,20 @@ function quadraticBezier(
const y = (1 - t) * (1 - t) * ps.y + 2 * t * (1 - t) * pc.y + t * t * pe.y;
return [x, y];
}
function getMidpoint(d: Call) {
export function getMidpoint(d: Call) {
if (isNaN(d.source.x) || isNaN(d.source.y)) {
return [0, 0];
}
if (isNaN(d.target.x) || isNaN(d.target.y)) {
return [0, 0];
}
const controlPos = computeControlPoint(
[d.source.x, d.source.y],
[d.target.x, d.target.y],
0.5
);
if (d.lowerArc) {
controlPos[1] =
Math.abs(controlPos[1]) < 50 ? -controlPos[1] + 100 : -controlPos[1] - 10;
controlPos[1] = -controlPos[1];
}
const p = quadraticBezier(
0.5,
@@ -155,3 +70,43 @@ function getMidpoint(d: Call) {
);
return p;
}
export function linkPath(d: Call) {
if (isNaN(d.source.x) || isNaN(d.source.y)) {
return;
}
if (isNaN(d.target.x) || isNaN(d.target.y)) {
return;
}
const controlPos = computeControlPoint(
[d.source.x, d.source.y - 5],
[d.target.x, d.target.y - 5],
0.5
);
if (d.lowerArc) {
controlPos[1] = -controlPos[1] - 10;
}
return (
"M" +
d.source.x +
" " +
(d.source.y - 5) +
" " +
"Q" +
controlPos[0] +
" " +
controlPos[1] +
" " +
d.target.x +
" " +
(d.target.y - 5)
);
}
export function getAnchor(d: Call) {
const types = [...d.sourceComponents, ...d.targetComponents];
if (types.includes("tcp") || types.includes("http")) {
return icons.HTTPDARK;
}
if (types.includes("https") || types.includes("tls")) {
return icons.HTTPS;
}
}

View File

@@ -1,54 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import icons from "@/assets/img/icons";
import { Node } from "@/types/topology";
export default (d3: any, graph: any, funcs: any, tip: any) => {
const nodeEnter = graph
.append("g")
.call(
d3
.drag()
.on("start", funcs.dragstart)
.on("drag", funcs.dragged)
.on("end", funcs.dragended)
)
.on("mouseover", function (event: unknown, d: Node) {
tip.html(funcs.tipHtml).show(d, this);
})
.on("mouseout", function () {
tip.hide(this);
});
nodeEnter
.append("image")
.attr("width", 35)
.attr("height", 35)
.attr("x", (d: any) => d.x - 15)
.attr("y", (d: any) => d.y - 15)
.attr("style", "cursor: move;")
.attr("xlink:href", icons.CUBE);
nodeEnter
.append("text")
.attr("fill", "#000")
.attr("text-anchor", "middle")
.attr("x", (d: any) => d.x)
.attr("y", (d: any) => d.y + 28)
.text((d: { name: string }) =>
d.name.length > 10 ? `${d.name.substring(0, 10)}...` : d.name
);
return nodeEnter;
};

View File

@@ -0,0 +1,96 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="profile-task">
<div>
<div class="label">URI Regex</div>
<el-input size="small" class="profile-input" v-model="states.uriRegex" />
</div>
<div>
<div class="label">{{ t("minDuration") }} (ms)</div>
<el-input
size="small"
class="profile-input"
:min="0"
v-model="states.minDuration"
type="number"
/>
</div>
<div>
<div class="label">{{ t("when4xx") }}</div>
<Radio
class="mb-5"
:value="states.when4xx"
:options="InitTaskField.Whenxx"
@change="changeWhen4xx"
/>
</div>
<div>
<div class="label">{{ t("when5xx") }}</div>
<Radio
class="mb-5"
:value="states.when5xx"
:options="InitTaskField.Whenxx"
@change="changeWhen5xx"
/>
</div>
</div>
</template>
<script lang="ts" setup>
import { defineProps, reactive } from "vue";
import { useI18n } from "vue-i18n";
import type { PropType } from "vue";
import { InitTaskField } from "./data";
import { NetworkProfilingRequest } from "@/types/ebpf";
/* global defineEmits */
const emits = defineEmits(["change"]);
const props = defineProps({
condition: {
type: Object as PropType<NetworkProfilingRequest>,
default: () => ({ children: [] }),
},
key: {
type: Number,
default: () => 0,
},
});
const { t } = useI18n();
const states = reactive<NetworkProfilingRequest>(props.condition);
function changeWhen5xx(value: string) {
states.when5xx = value;
emits("change", states, props.key);
}
function changeWhen4xx(value: string) {
states.when4xx = value;
emits("change", states, props.key);
}
</script>
<style lang="scss" scoped>
.date {
font-size: 12px;
}
.label {
margin-top: 10px;
font-size: 14px;
}
.profile-input {
width: 300px;
}
</style>

View File

@@ -0,0 +1,139 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="profile-task">
<el-collapse v-model="activeNames">
<el-collapse-item
v-for="(item, index) in conditionsList"
:key="index"
:name="index"
>
<template #title>
<div>
<span class="title">{{ `Rule - ${index + 1}` }}</span>
<Icon
class="mr-5 cp"
iconName="remove_circle_outline"
size="middle"
v-show="conditionsList.length !== 1"
@click="removeConditions($event, index)"
/>
<Icon
class="cp"
v-show="index === conditionsList.length - 1"
iconName="add_circle_outlinecontrol_point"
size="middle"
@click="createConditions"
/>
</div>
</template>
<NewCondition
:name="index"
:condition="item"
:key="index"
@change="changeConfig"
/>
</el-collapse-item>
</el-collapse>
<div>
<el-button @click="createTask" type="primary" class="create-task-btn">
{{ t("createTask") }}
</el-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { InitTaskField } from "./data";
import NewCondition from "./NewConditions.vue";
import { NetworkProfilingRequest } from "@/types/ebpf";
/* global defineEmits */
const emits = defineEmits(["create"]);
const { t } = useI18n();
const activeNames = ref([0]);
const conditionsList = ref<NetworkProfilingRequest[]>([
{
uriRegex: "",
when4xx: InitTaskField.Whenxx[1].value,
when5xx: InitTaskField.Whenxx[0].value,
minDuration: NaN,
},
]);
function changeConfig(
params: { [key: string]: number | string },
index: number
) {
const key: string = Object.keys(params)[0];
(conditionsList.value[index] as any)[key] = params[key];
}
function createTask() {
const list = conditionsList.value.map((d: NetworkProfilingRequest) => {
return {
uriRegex: d.uriRegex || undefined,
when4xx: d.when4xx === InitTaskField.Whenxx[0].value ? true : false,
when5xx: d.when5xx === InitTaskField.Whenxx[0].value ? true : false,
minDuration: isNaN(Number(d.minDuration))
? undefined
: Number(d.minDuration),
settings: {
requireCompleteRequest: true,
requireCompleteResponse: true,
},
};
});
emits("create", list);
}
function createConditions(e: any) {
e.stopPropagation();
conditionsList.value.push({
uriRegex: "",
when4xx: InitTaskField.Whenxx[0].value,
when5xx: InitTaskField.Whenxx[1].value,
minDuration: NaN,
});
activeNames.value = [conditionsList.value.length - 1];
}
function removeConditions(e: any, key: number) {
e.stopPropagation();
if (conditionsList.value.length === 1) {
return;
}
conditionsList.value = conditionsList.value.filter(
(_, index: number) => index !== key
);
}
</script>
<style lang="scss" scoped>
.profile-task {
width: 100%;
}
.create-task-btn {
width: 300px;
margin-top: 50px;
}
.title {
display: inline-block;
margin-right: 5px;
}
</style>

View File

@@ -13,13 +13,103 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div ref="chart" class="process-topo"></div>
<el-popover
placement="bottom"
:width="295"
trigger="click"
v-if="dashboardStore.editMode"
>
<div ref="chart" class="process-topo">
<svg
class="process-svg"
:width="width"
:height="height"
@click="clickTopology"
>
<g class="svg-graph" :transform="`translate(${diff[0]}, ${diff[1]})`">
<g class="hex-polygon">
<path
:d="getHexPolygonVertices()"
stroke="#D5DDF6"
stroke-width="2"
fill="none"
/>
<text :x="0" :y="radius - 15" fill="#000" text-anchor="middle">
{{ selectorStore.currentPod.label }}
</text>
</g>
<g class="nodes">
<g
v-for="(node, index) in nodeList"
:key="index"
class="node"
@mouseover="showNodeTip(node, $event)"
@mouseout="hideNodeTip"
@mousedown="startMoveNode($event, node)"
@mouseup="stopMoveNode($event)"
>
<image
:href="icons.CUBE"
style="cursor: 'move'"
width="35"
height="35"
:x="(node.x || 0) - 15"
:y="(node.y || 0) - 15"
/>
<text
:x="node.x"
:y="(node.y || 0) + 28"
fill="#000"
text-anchor="middle"
>
{{
node.name.length > 10
? `${node.name.substring(0, 10)}...`
: node.name
}}
</text>
</g>
</g>
<g class="calls">
<path
v-for="(call, index) in networkProfilingStore.calls"
:key="index"
class="topo-call"
marker-end="url(#arrow)"
stroke="#97B0F8"
:d="linkPath(call)"
/>
</g>
<g class="anchors">
<image
v-for="(call, index) in networkProfilingStore.calls"
:key="index"
class="topo-line-anchor"
:href="getAnchor(call)"
width="15"
height="15"
:x="getMidpoint(call)[0] - 8"
:y="getMidpoint(call)[1] - 13"
@click="handleLinkClick($event, call)"
@mouseover="showLinkTip(call, $event)"
@mouseout="hideLinkTip"
/>
</g>
<g class="arrows">
<defs v-for="(_, index) in networkProfilingStore.calls" :key="index">
<marker
id="arrow"
markerUnits="strokeWidth"
markerWidth="8"
markerHeight="8"
viewBox="0 0 12 12"
refX="10"
refY="6"
orient="auto"
>
<path d="M2,2 L10,6 L2,10 L6,6 L2,2" fill="#97B0F8" />
</marker>
</defs>
</g>
</g>
</svg>
<div id="tooltip"></div>
</div>
<el-popover placement="bottom" :width="295" trigger="click">
<template #reference>
<div class="switch-icon-edit ml-5" title="Settings" @click="setConfig">
<Icon size="middle" iconName="setting_empty" />
@@ -39,9 +129,7 @@ import router from "@/router";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import d3tip from "d3-tip";
import { linkElement, anchorElement, arrowMarker } from "./Graph/linkProcess";
import nodeElement from "./Graph/nodeProcess";
import { linkPath, getAnchor, getMidpoint } from "./Graph/linkProcess";
import { Call } from "@/types/topology";
import zoom from "../../components/utils/zoom";
import { ProcessNode } from "@/types/ebpf";
@@ -52,6 +140,7 @@ import getDashboard from "@/hooks/useDashboardsSession";
import { Layout } from "./Graph/layout";
import TimeLine from "./TimeLine.vue";
import { useAppStoreWithOut } from "@/store/modules/app";
import icons from "@/assets/img/icons";
/*global Nullable, defineProps */
const props = defineProps({
@@ -67,19 +156,18 @@ const selectorStore = useSelectorStore();
const networkProfilingStore = useNetworkProfilingStore();
const height = ref<number>(100);
const width = ref<number>(100);
const svg = ref<Nullable<any>>(null);
const chart = ref<Nullable<HTMLDivElement>>(null);
const tip = ref<Nullable<HTMLDivElement>>(null);
const graph = ref<any>(null);
const node = ref<any>(null);
const link = ref<any>(null);
const anchor = ref<any>(null);
const arrow = ref<any>(null);
const tooltip = ref<Nullable<any>>(null);
const svg = ref<Nullable<any>>(null);
const graph = ref<Nullable<any>>(null);
const oldVal = ref<{ width: number; height: number }>({ width: 0, height: 0 });
const config = ref<any>(props.config || {});
const diff = ref<number[]>([220, 200]);
const radius = 210;
const dates = ref<Nullable<{ start: number; end: number }>>(null);
const nodeList = ref<ProcessNode[]>([]);
const currentNode = ref<Nullable<ProcessNode>>(null);
const origin = [0, 0];
onMounted(() => {
init();
@@ -90,12 +178,14 @@ onMounted(() => {
});
async function init() {
svg.value = d3.select(chart.value).append("svg").attr("class", "process-svg");
if (!networkProfilingStore.nodes.length) {
return;
}
drawGraph();
createLayout();
svg.value = d3.select(".process-svg");
graph.value = d3.select(".svg-graph");
tooltip.value = d3.select("#tooltip");
freshNodes();
useThrottleFn(resize, 500)();
}
function drawGraph() {
@@ -105,27 +195,16 @@ function drawGraph() {
};
height.value = (dom.height || 40) - 20;
width.value = dom.width;
svg.value.attr("height", height.value).attr("width", width.value);
tip.value = (d3tip as any)().attr("class", "d3-tip").offset([-8, 0]);
diff.value[0] = (dom.width - radius * 2) / 2 + radius;
graph.value = svg.value
.append("g")
.attr("class", "svg-graph")
.attr("transform", `translate(${diff.value[0]}, ${diff.value[1]})`);
graph.value.call(tip.value);
node.value = graph.value.append("g").selectAll(".topo-node");
link.value = graph.value.append("g").selectAll(".topo-call");
anchor.value = graph.value.append("g").selectAll(".topo-line-anchor");
arrow.value = graph.value.append("g").selectAll(".topo-line-arrow");
svg.value.call(zoom(d3, graph.value, diff.value));
svg.value.on("click", (event: any) => {
event.stopPropagation();
event.preventDefault();
networkProfilingStore.setNode(null);
networkProfilingStore.setLink(null);
dashboardStore.selectWidget(props.config);
});
useThrottleFn(resize, 500)();
}
function clickTopology(event: MouseEvent) {
event.stopPropagation();
event.preventDefault();
networkProfilingStore.setNode(null);
networkProfilingStore.setLink(null);
dashboardStore.selectWidget(props.config);
}
function hexGrid(n = 1, radius = 1, origin = [0, 0]) {
@@ -157,7 +236,6 @@ function createPolygon(radius: number, sides = 6, offset = 0) {
}
function getCirclePoint(radius: number, p = 1) {
const data = [];
const origin = [0, 0];
for (let index = 0; index < 360; index = index + p) {
if (index < 230 || index > 310) {
let x = radius * Math.cos((Math.PI * 2 * index) / 360);
@@ -167,10 +245,22 @@ function getCirclePoint(radius: number, p = 1) {
}
return data;
}
function createLayout() {
if (!node.value || !link.value) {
return;
function getHexPolygonVertices() {
const p = {
count: 1,
radius, // layout hexagons radius 300
};
const polygon = createPolygon(p.radius, 6, 0);
const vertices: any = []; // a hexagon vertices
for (let v = 0; v < polygon.length; v++) {
vertices.push([origin[0] + polygon[v][0], origin[1] + polygon[v][1]]);
}
const linePath = d3.line();
linePath.curve(d3.curveLinearClosed);
return linePath(vertices) || "";
}
function createLayout() {
const dom: any = (chart.value && chart.value.getBoundingClientRect()) || {
width: 0,
height: 0,
@@ -182,28 +272,6 @@ function createLayout() {
count: 1,
radius, // layout hexagons radius 300
};
const polygon = createPolygon(p.radius, 6, 0);
const origin = [0, 0];
const vertices: any = []; // a hexagon vertices
for (let v = 0; v < polygon.length; v++) {
vertices.push([origin[0] + polygon[v][0], origin[1] + polygon[v][1]]);
}
const linePath = d3.line();
linePath.curve(d3.curveLinearClosed);
const hexPolygon = graph.value.append("g");
hexPolygon
.append("path")
.attr("d", linePath(vertices))
.attr("stroke", "#D5DDF6")
.attr("stroke-width", 2)
.style("fill", "none");
hexPolygon
.append("text")
.attr("fill", "#000")
.attr("text-anchor", "middle")
.attr("x", 0)
.attr("y", p.radius - 15)
.text(() => selectorStore.currentPod.label);
const nodeArr = networkProfilingStore.nodes.filter(
(d: ProcessNode) => d.isReal || d.name === "UNKNOWN_LOCAL"
);
@@ -278,67 +346,11 @@ function createLayout() {
outNodes[v].x = pointArr[v][0];
outNodes[v].y = pointArr[v][1];
}
drawTopology([...nodeArr, ...outNodes]);
}
function drawTopology(nodeArr: any[]) {
node.value = node.value.data(nodeArr, (d: ProcessNode) => d.id);
node.value.exit().remove();
node.value = nodeElement(
d3,
node.value.enter(),
{
tipHtml: (data: ProcessNode) => {
return ` <div class="mb-5"><span class="grey">name: </span>${data.name}</div>`;
},
},
tip.value
).merge(node.value);
// line element
const obj = {} as any;
const calls = networkProfilingStore.calls.reduce((prev: any[], next: any) => {
if (obj[next.targetId + next.sourceId]) {
next.lowerArc = true;
}
obj[next.sourceId + next.targetId] = true;
prev.push(next);
return prev;
}, []);
link.value = link.value.data(calls, (d: Call) => d.id);
link.value.exit().remove();
link.value = linkElement(link.value.enter()).merge(link.value);
anchor.value = anchor.value.data(calls, (d: Call) => d.id);
anchor.value.exit().remove();
anchor.value = anchorElement(
anchor.value.enter(),
{
handleLinkClick: handleLinkClick,
tipHtml: (data: Call) => {
const types = [...data.sourceComponents, ...data.targetComponents];
let l = "TCP";
if (types.includes("https")) {
l = "HTTPS";
}
if (types.includes("http")) {
l = "HTTP";
}
if (types.includes("tls")) {
l = "TLS";
}
const html = `<div><span class="grey">${t(
"detectPoint"
)}: </span>${data.detectPoints.join(" | ")}</div>
<div><span class="grey">Type: </span>${l}</div>`;
return html;
},
},
tip.value
).merge(anchor.value);
// arrow marker
arrow.value = arrow.value.data(calls, (d: Call) => d.id);
arrow.value.exit().remove();
arrow.value = arrowMarker(arrow.value.enter()).merge(arrow.value);
nodeList.value = [...nodeArr, ...outNodes];
const drag: any = d3.drag().on("drag", (d: ProcessNode) => {
moveNode(d);
});
d3.selectAll(".node").call(drag);
}
function shuffleArray(array: number[][]) {
@@ -347,7 +359,6 @@ function shuffleArray(array: number[][]) {
[array[i], array[j]] = [array[j], array[i]];
}
}
function handleLinkClick(event: any, d: Call) {
event.stopPropagation();
networkProfilingStore.setNode(null);
@@ -423,13 +434,100 @@ function resize() {
}
async function freshNodes() {
svg.value.selectAll(".svg-graph").remove();
if (!networkProfilingStore.nodes.length) {
return;
}
drawGraph();
createLayout();
}
function startMoveNode(event: MouseEvent, d: ProcessNode) {
event.stopPropagation();
currentNode.value = d;
}
function stopMoveNode(event: MouseEvent) {
event.stopPropagation();
currentNode.value = null;
}
function moveNode(d: ProcessNode) {
if (!currentNode.value) {
return;
}
const inNode =
currentNode.value.isReal || currentNode.value.name === "UNKNOWN_LOCAL";
const diff = inNode ? -20 : 20;
const inside = posInHex(d.x || 0, d.y || 0, diff);
if (inNode) {
if (!inside) {
return;
}
} else {
if (inside) {
return;
}
}
nodeList.value = nodeList.value.map((node: ProcessNode) => {
if (currentNode.value && node.id === currentNode.value.id) {
node.x = d.x;
node.y = d.y;
}
return node;
});
}
function posInHex(posX: number, posY: number, diff: number) {
const halfSideLen = (radius + diff) / 2;
const mathSqrt3 = Math.sqrt(3);
const dx = Math.abs(origin[0] - posX);
const dy = Math.abs(origin[1] - posY);
if (dx < halfSideLen) {
return dy <= halfSideLen * mathSqrt3;
} else {
const maxY = -mathSqrt3 * (dx - halfSideLen) + halfSideLen * mathSqrt3;
return dy < maxY;
}
}
function showNodeTip(d: ProcessNode, event: MouseEvent) {
const tipHtml = ` <div class="mb-5"><span class="grey">name: </span>${d.name}</div>`;
tooltip.value
.style("top", event.offsetY + "px")
.style("left", event.offsetX + "px")
.style("visibility", "visible")
.html(tipHtml);
}
function hideNodeTip() {
tooltip.value.style("visibility", "hidden");
}
function showLinkTip(link: Call, event: MouseEvent) {
const types = [...link.sourceComponents, ...link.targetComponents];
let l = "TCP";
if (types.includes("https")) {
l = "HTTPS";
}
if (types.includes("http")) {
l = "HTTP";
}
if (types.includes("tls")) {
l = "TLS";
}
const tipHtml = `<div><span class="grey">${t(
"detectPoint"
)}: </span>${link.detectPoints.join(" | ")}</div>
<div><span class="grey">Type: </span>${l}</div>`;
tooltip.value
.style("top", event.offsetY + "px")
.style("left", event.offsetX + "px")
.style("visibility", "visible")
.html(tipHtml);
}
function hideLinkTip() {
tooltip.value.style("visibility", "hidden");
}
watch(
() => networkProfilingStore.nodes,
@@ -500,4 +598,13 @@ watch(
.query {
margin-left: 510px;
}
#tooltip {
position: absolute;
visibility: hidden;
padding: 5px;
border: 1px solid #000;
border-radius: 3px;
background-color: #fff;
}
</style>

View File

@@ -17,24 +17,13 @@ limitations under the License. -->
<div class="profile-task-wrapper flex-v">
<div class="profile-t-tool">
<span>{{ t("taskList") }}</span>
<span v-if="inProcess" class="new-task cp" @click="createTask">
<span class="new-task cp" @click="createTask">
<Icon
:style="{ color: '#ccc' }"
:style="{ color: inProcess ? '#ccc' : '#000' }"
iconName="library_add"
size="middle"
/>
</span>
<el-popconfirm
title="Are you sure to create a task?"
@confirm="createTask"
v-else
>
<template #reference>
<span class="new-task cp">
<Icon iconName="library_add" size="middle" />
</span>
</template>
</el-popconfirm>
</div>
<div class="profile-t-wrapper">
<div
@@ -87,6 +76,15 @@ limitations under the License. -->
>
<TaskDetails :details="networkProfilingStore.selectedNetworkTask" />
</el-dialog>
<el-dialog
v-model="newTask"
:title="t('taskTitle')"
:destroy-on-close="true"
fullscreen
@closed="newTask = false"
>
<NewTask @create="saveNewTask" />
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
@@ -99,13 +97,15 @@ import TaskDetails from "../../components/TaskDetails.vue";
import dateFormatStep, { dateFormat } from "@/utils/dateFormat";
import getLocalTime from "@/utils/localtime";
import { useAppStoreWithOut } from "@/store/modules/app";
import NewTask from "./NewTask.vue";
/*global Nullable */
const { t } = useI18n();
const selectorStore = useSelectorStore();
const networkProfilingStore = useNetworkProfilingStore();
const appStore = useAppStoreWithOut();
const viewDetail = ref<boolean>(false);
/*global Nullable */
const newTask = ref<boolean>(false);
const intervalFn = ref<Nullable<any>>(null);
const intervalKeepAlive = ref<Nullable<any>>(null);
const inProcess = ref<boolean>(false);
@@ -163,28 +163,35 @@ async function getTopology() {
}
return resp;
}
async function createTask() {
function createTask() {
if (inProcess.value) {
return;
}
const serviceId =
(selectorStore.currentService && selectorStore.currentService.id) || "";
const serviceInstanceId =
newTask.value = true;
}
async function saveNewTask(
params: {
uriRegex: string;
when4xx: string;
when5xx: string;
minDuration: number;
}[]
) {
const instanceId =
(selectorStore.currentPod && selectorStore.currentPod.id) || "";
if (!serviceId) {
return;
if (!instanceId) {
return ElMessage.error("No Instance ID");
}
if (!serviceInstanceId) {
return;
}
const res = await networkProfilingStore.createNetworkTask({
serviceId,
serviceInstanceId,
});
const res = await networkProfilingStore.createNetworkTask(instanceId, params);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
if (!res.data.createEBPFNetworkProfiling.status) {
ElMessage.error(res.data.createEBPFNetworkProfiling.errorReason);
return;
}
newTask.value = false;
await fetchTasks();
}
function enableInterval() {
@@ -239,6 +246,7 @@ async function fetchTasks() {
watch(
() => selectorStore.currentPod,
() => {
inProcess.value = false;
fetchTasks();
}
);

View File

@@ -0,0 +1,36 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const ProfileMode: any[] = [
{ label: "Include Children", value: "include" },
{ label: "Exclude Children", value: "exclude" },
];
export const NewTaskField = {
service: { key: "", label: "None" },
monitorTime: { key: "0", label: "monitor now" },
monitorDuration: { key: 5, label: "5 min" },
minThreshold: 0,
dumpPeriod: { key: 10, label: "10ms" },
endpointName: "",
maxSamplingCount: { key: 5, label: "5" },
};
export const InitTaskField = {
Whenxx: [
{ value: "1", label: "True" },
{ value: "0", label: "False" },
],
};

View File

@@ -119,8 +119,8 @@ function updateTimeRange() {
if (!children || !children.length) {
timeRange.value = [
{
start: this.currentSpan.startTime,
end: this.currentSpan.endTime,
start: startTime,
end: endTime,
},
];
return;

View File

@@ -90,6 +90,7 @@ import {
reactive,
watch,
computed,
nextTick,
} from "vue";
import { useI18n } from "vue-i18n";
import * as d3 from "d3";
@@ -111,9 +112,9 @@ import { Service } from "@/types/selector";
import { useAppStoreWithOut } from "@/store/modules/app";
import getDashboard from "@/hooks/useDashboardsSession";
import { MetricConfigOpt } from "@/types/dashboard";
import { aggregation } from "@/hooks/useProcessor";
import { aggregation } from "@/hooks/useMetricsProcessor";
import icons from "@/assets/img/icons";
import { useQueryTopologyMetrics } from "@/hooks/useProcessor";
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
/*global Nullable, defineProps */
const props = defineProps({
@@ -149,6 +150,14 @@ const graphConfig = computed(() => props.config.graph || {});
const depth = ref<number>(graphConfig.value.depth || 2);
onMounted(async () => {
await nextTick();
const dom = document.querySelector(".topology")?.getBoundingClientRect() || {
height: 40,
width: 0,
};
height.value = dom.height - 40;
width.value = dom.width;
loading.value = true;
const json = await selectorStore.fetchServices(dashboardStore.layerId);
if (json.errors) {
@@ -157,18 +166,13 @@ onMounted(async () => {
}
const resp = await getTopology();
loading.value = false;
if (resp && resp.errors) {
ElMessage.error(resp.errors);
}
topologyStore.getLinkClientMetrics(settings.value.linkClientMetrics || []);
topologyStore.getLinkServerMetrics(settings.value.linkServerMetrics || []);
topologyStore.queryNodeMetrics(settings.value.nodeMetrics || []);
const dom = document.querySelector(".topology")?.getBoundingClientRect() || {
height: 40,
width: 0,
};
height.value = dom.height - 40;
width.value = dom.width;
window.addEventListener("resize", resize);
svg.value = d3.select(chart.value).append("svg").attr("class", "topo-svg");
await initLegendMetrics();
@@ -733,4 +737,8 @@ watch(
stroke-dashoffset: 0;
}
}
.el-loading-spinner {
top: 30%;
}
</style>

View File

@@ -21,7 +21,7 @@ import { computed, PropType } from "vue";
import { useTopologyStore } from "@/store/modules/topology";
import { Node, Call } from "@/types/topology";
import { MetricConfigOpt } from "@/types/dashboard";
import { aggregation } from "@/hooks/useProcessor";
import { aggregation } from "@/hooks/useMetricsProcessor";
/*global defineEmits, defineProps */
const props = defineProps({

View File

@@ -248,7 +248,7 @@ import { useTopologyStore } from "@/store/modules/topology";
import { ElMessage } from "element-plus";
import { MetricCatalog, ScopeType, MetricConditions } from "../../../data";
import { Option } from "@/types/app";
import { useQueryTopologyMetrics } from "@/hooks/useProcessor";
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
import { Node } from "@/types/topology";
import { DashboardItem, MetricConfigOpt } from "@/types/dashboard";
import { EntityType, LegendOpt, MetricsType } from "../../../data";

View File

@@ -17,11 +17,6 @@ limitations under the License. -->
v-if="traceStore.currentTrace.endpointNames"
>
<h5 class="mb-5 mt-0">
<Icon
icon="clear"
v-if="traceStore.currentTrace.isError"
class="red mr-5 sm"
/>
<span class="vm">{{ traceStore.currentTrace.endpointNames[0] }}</span>
<div class="trace-log-btn">
<el-button
@@ -34,7 +29,7 @@ limitations under the License. -->
</el-button>
</div>
</h5>
<div class="mb-5 blue sm">
<div class="mb-5 blue">
<Selector
size="small"
:value="
@@ -46,12 +41,7 @@ limitations under the License. -->
@change="changeTraceId"
class="trace-detail-ids"
/>
<Icon
size="sm"
class="icon grey link-hover cp ml-5"
iconName="review-list"
@click="handleClick"
/>
<Icon class="cp ml-5" iconName="copy" @click="handleClick" />
</div>
<div class="flex-h item">
<div>

View File

@@ -95,17 +95,15 @@ limitations under the License. -->
import { ref, reactive, watch, onUnmounted } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { Option } from "@/types/app";
import { Status } from "../../data";
import { Option, DurationTime } from "@/types/app";
import { useTraceStore } from "@/store/modules/trace";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import ConditionTags from "@/views/components/ConditionTags.vue";
import { ElMessage } from "element-plus";
import { EntityType } from "../../data";
import { EntityType, QueryOrders, Status } from "../../data";
import { LayoutConfig } from "@/types/dashboard";
import { DurationTime } from "@/types/app";
/*global defineProps, Recordable */
const props = defineProps({
@@ -115,33 +113,35 @@ const props = defineProps({
default: () => ({ graph: {} }),
},
});
const traceId = ref<string>(
(props.data.filters && props.data.filters.traceId) || ""
);
const filters = reactive<Recordable>(props.data.filters || {});
const traceId = ref<string>(filters.traceId || "");
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const traceStore = useTraceStore();
const duration = ref<DurationTime>(
(props.data.filters && props.data.filters.duration) || appStore.durationTime
);
const duration = ref<DurationTime>(filters.duration || appStore.durationTime);
const minTraceDuration = ref<number>();
const maxTraceDuration = ref<number>();
const tagsList = ref<string[]>([]);
const tagsMap = ref<Option[]>([]);
const state = reactive<Recordable>({
status: { label: "All", value: "ALL" },
status: filters.status === "ERROR" ? Status[2] : Status[0],
instance: { value: "0", label: "All" },
endpoint: { value: "0", label: "All" },
service: { value: "", label: "" },
});
if (filters.queryOrder) {
traceStore.setTraceCondition({
queryOrder: filters.queryOrder,
});
}
if (props.needQuery) {
init();
}
async function init() {
duration.value = filters.duration || appStore.durationTime;
if (dashboardStore.entity === EntityType[1].value) {
await getServices();
}
@@ -164,7 +164,7 @@ async function getServices() {
ElMessage.error(resp.errors);
return;
}
state.service = traceStore.services[0];
state.service = getCurrentNode(traceStore.services) || traceStore.services[0];
getEndpoints(state.service.id);
getInstances(state.service.id);
}
@@ -175,7 +175,8 @@ async function getEndpoints(id?: string, keyword?: string) {
ElMessage.error(resp.errors);
return;
}
state.endpoint = traceStore.endpoints[0];
state.endpoint =
getCurrentNode(traceStore.endpoints) || traceStore.endpoints[0];
}
async function getInstances(id?: string) {
const resp = await traceStore.getInstances(id);
@@ -183,9 +184,39 @@ async function getInstances(id?: string) {
ElMessage.error(resp.errors);
return;
}
state.instance = traceStore.instances[0];
state.instance =
getCurrentNode(traceStore.instances) || traceStore.instances[0];
}
function searchTraces() {
function getCurrentNode(arr: { id: string }[]) {
let item;
if (!props.data.filters) {
return item;
}
if (props.data.filters.id) {
item = arr.find((d: { id: string }) => d.id === props.data.filters?.id);
}
return item;
}
function setCondition() {
let param: any = {
traceState: state.status.value || "ALL",
tags: tagsMap.value.length ? tagsMap.value : undefined,
queryOrder: traceStore.conditions.queryOrder || QueryOrders[1].value,
queryDuration: duration.value,
minTraceDuration: Number(minTraceDuration.value),
maxTraceDuration: Number(maxTraceDuration.value),
traceId: traceId.value || undefined,
paging: { pageNum: 1, pageSize: 20 },
};
if (props.data.filters && props.data.filters.id) {
param = {
...param,
serviceId: selectorStore.currentService.id,
endpointId: state.endpoint.id || undefined,
serviceInstanceId: state.instance.id || undefined,
};
return param;
}
let endpoint = "",
instance = "";
if (dashboardStore.entity === EntityType[2].value) {
@@ -194,21 +225,18 @@ function searchTraces() {
if (dashboardStore.entity === EntityType[3].value) {
instance = selectorStore.currentPod.id;
}
traceStore.setTraceCondition({
param = {
...param,
serviceId: selectorStore.currentService
? selectorStore.currentService.id
: state.service.id,
traceId: traceId.value || undefined,
endpointId: endpoint || state.endpoint.id || undefined,
serviceInstanceId: instance || state.instance.id || undefined,
traceState: state.status.value || "ALL",
queryDuration: duration.value,
minTraceDuration: Number(minTraceDuration.value),
maxTraceDuration: Number(maxTraceDuration.value),
queryOrder: traceStore.conditions.queryOrder || "BY_DURATION",
tags: tagsMap.value.length ? tagsMap.value : undefined,
paging: { pageNum: 1, pageSize: 20 },
});
};
return param;
}
function searchTraces() {
traceStore.setTraceCondition(setCondition());
queryTraces();
}
async function queryTraces() {
@@ -263,17 +291,19 @@ watch(
}
}
);
// Event widget associate with trace widget
watch(
() => props.data.filters,
(newJson, oldJson) => {
if (props.data.filters) {
if (JSON.stringify(newJson) === JSON.stringify(oldJson)) {
return;
}
traceId.value = props.data.filters.traceId || "";
duration.value = props.data.filters.duration || appStore.durationTime;
init();
if (!props.data.filters) {
return;
}
if (JSON.stringify(newJson) === JSON.stringify(oldJson)) {
return;
}
traceId.value = props.data.filters.traceId || "";
duration.value = props.data.filters.duration || appStore.durationTime;
init();
}
);
</script>

View File

@@ -0,0 +1,290 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div v-if="filters.id" class="conditions flex-h">
<div class="label grey">TraceId:</div>
<el-input size="small" v-model="traceId" class="trace-id" />
</div>
<div class="conditions flex-h" v-else>
<el-radio-group v-model="conditions" @change="changeCondition" size="small">
<el-radio-button
v-for="(item, index) in items"
:label="item.label"
:key="item.label + index"
border
>
{{ t(item.label) }}
</el-radio-button>
</el-radio-group>
<Selector
v-if="conditions === 'latency' && filters.latency.length > 1"
:value="filters.latency[0].value"
:options="filters.latency"
placeholder="Select a option"
@change="changeLatency"
class="ml-10"
/>
<el-popover trigger="hover" width="250" placement="bottom" effect="light">
<template #reference>
<div class="cp conditions-popup">
<Icon iconName="conditions" size="middle" />
</div>
</template>
<div>
<div class="title">{{ t("queryConditions") }}</div>
<div v-for="key in Object.keys(FiltersKeys)" :key="key">
<span
v-if="
[
FiltersKeys.minTraceDuration,
FiltersKeys.maxTraceDuration,
].includes(key) && !isNaN(traceStore.conditions[FiltersKeys[key]])
"
>
{{ t(key) }}: {{ traceStore.conditions[FiltersKeys[key]] }}
</span>
<span v-else-if="key !== 'duration'">
{{ t(key) }}: {{ traceStore.conditions[FiltersKeys[key]] }}
</span>
</div>
</div>
</el-popover>
<el-popover trigger="hover" width="250" placement="bottom" effect="light">
<template #reference>
<div class="cp metric-value">
<Icon iconName="info_outline" size="middle" />
</div>
</template>
<div>
<div class="title">{{ t("metricValues") }}</div>
<div v-for="metric in filters.metricValue" :key="metric.value">
{{ metric.label }}: {{ metric.data }}
</div>
</div>
</el-popover>
</div>
<div class="flex-h">
<ConditionTags :type="'TRACE'" @update="updateTags" />
<div class="search-btn">
<el-button size="small" type="primary" @click="queryTraces">
{{ t("search") }}
</el-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onUnmounted } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { Option, DurationTime } from "@/types/app";
import { useTraceStore } from "@/store/modules/trace";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import ConditionTags from "@/views/components/ConditionTags.vue";
import { ElMessage } from "element-plus";
import { EntityType, QueryOrders, Status } from "../../data";
import { LayoutConfig } from "@/types/dashboard";
const FiltersKeys: { [key: string]: string } = {
status: "traceState",
queryOrder: "queryOrder",
duration: "queryDuration",
minTraceDuration: "minTraceDuration",
maxTraceDuration: "maxTraceDuration",
};
/*global defineProps, Recordable */
const props = defineProps({
needQuery: { type: Boolean, default: true },
data: {
type: Object as PropType<LayoutConfig>,
default: () => ({ graph: {} }),
},
});
const { t } = useI18n();
const filters = reactive<Recordable>(props.data.filters || {});
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const traceStore = useTraceStore();
const tagsList = ref<string[]>([]);
const tagsMap = ref<Option[]>([]);
const traceId = ref<string>(filters.refId || "");
const duration = ref<DurationTime>(filters.duration || appStore.durationTime);
const state = reactive<Recordable>({
instance: "",
endpoint: "",
service: "",
});
const conditions = ref<string>("");
const items = ref<{ label: string; value: string }[]>([]);
const currentLatency = ref<number[]>(
filters.latency ? filters.latency[0].data : []
);
init();
async function init() {
for (const d of Object.keys(filters)) {
if (
(d === "queryOrder" &&
filters[d] &&
filters[d] === QueryOrders[1].value) ||
(d === "status" && filters[d] && filters[d] !== Status[0].value) ||
(filters[d] && d === "latency")
) {
items.value.push({ label: d, value: FiltersKeys[d] });
}
}
conditions.value = (items.value[0] && items.value[0].label) || "";
if (!filters.id) {
state.service = selectorStore.currentService.id;
if (dashboardStore.entity === EntityType[2].value) {
state.endpoint = selectorStore.currentPod.id;
}
if (dashboardStore.entity === EntityType[3].value) {
state.instance = selectorStore.currentPod.id;
}
await queryTraces();
return;
}
if (dashboardStore.entity === EntityType[1].value) {
await getService();
}
if (dashboardStore.entity === EntityType[0].value) {
state.service = selectorStore.currentService.id;
await getInstance();
if (!state.instance) {
await getEndpoint();
}
}
await queryTraces();
}
function changeCondition() {
if (conditions.value === "latency") {
currentLatency.value = filters.latency ? filters.latency[0].data : [];
}
queryTraces();
}
function changeLatency(options: any[]) {
currentLatency.value = options[0].data;
queryTraces();
}
async function getService() {
const resp = await traceStore.getService(filters.id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.service = (resp.data.service && resp.data.service) || "";
}
async function getEndpoint() {
const resp = await traceStore.getEndpoint(filters.id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.endpoint = (resp.data.endpoint && resp.data.endpoint.id) || "";
}
async function getInstance() {
const resp = await traceStore.getInstance(filters.id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.instance = (resp.data.instance && resp.data.instance.id) || "";
}
function setCondition() {
let params: any = {
traceState: Status[0].value,
queryOrder: QueryOrders[0].value,
queryDuration: duration.value,
minTraceDuration: currentLatency.value[0],
maxTraceDuration: currentLatency.value[1],
tags: tagsMap.value.length ? tagsMap.value : undefined,
paging: { pageNum: 1, pageSize: 20 },
serviceId: state.service || undefined,
endpointId: state.endpoint || undefined,
serviceInstanceId: state.instance || undefined,
traceId: traceId.value || undefined,
};
for (const k of items.value) {
if (
k.label === conditions.value &&
FiltersKeys[k.label] &&
filters[k.label]
) {
params[k.value] = filters[k.label];
}
}
if (!isNaN(params.minTraceDuration)) {
params.queryOrder = QueryOrders[1].value;
}
return params;
}
async function queryTraces() {
traceStore.setTraceCondition(setCondition());
const res = await traceStore.getTraces();
if (res && res.errors) {
ElMessage.error(res.errors);
}
}
function updateTags(data: { tagsMap: Array<Option>; tagsList: string[] }) {
tagsList.value = data.tagsList;
tagsMap.value = data.tagsMap;
}
onUnmounted(() => {
traceStore.resetState();
});
</script>
<style lang="scss" scoped>
.row {
margin-bottom: 5px;
position: relative;
}
.conditions {
margin-bottom: 10px;
}
.search-btn {
margin-top: 2px;
}
.metric-value {
padding: 0 5px;
line-height: 32px;
}
.conditions-popup {
padding-left: 10px;
line-height: 32px;
}
.title {
margin-bottom: 10px;
font-weight: bold;
}
.trace-id {
width: 300px;
margin-left: 10px;
}
.label {
line-height: 22px;
}
</style>

Some files were not shown because too many files have changed in this diff Show More