Compare commits
45 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1f98619a5b | ||
![]() |
aab44626da | ||
![]() |
0ff5d4d6bb | ||
![]() |
611731d6d0 | ||
![]() |
221751f034 | ||
![]() |
d4dde7e73b | ||
![]() |
5be106fc4f | ||
![]() |
d8f91bbdf3 | ||
![]() |
23e9742946 | ||
![]() |
7a1c83b5fb | ||
![]() |
7d802d490e | ||
![]() |
da1db8def6 | ||
![]() |
2230d05508 | ||
![]() |
e8d909792d | ||
![]() |
dc842609ba | ||
![]() |
670bef1d69 | ||
![]() |
882828b04a | ||
![]() |
ed6fb0448b | ||
![]() |
a0fc879eb1 | ||
![]() |
b37d65eaac | ||
![]() |
fd46211a37 | ||
![]() |
ae0b8c056d | ||
![]() |
4b88d8bbb3 | ||
![]() |
09051e916b | ||
![]() |
e597f91448 | ||
![]() |
4232161d36 | ||
![]() |
eda44db0cd | ||
![]() |
78f0096c00 | ||
![]() |
5e161f17c2 | ||
![]() |
77d189cdfb | ||
![]() |
9f57e35119 | ||
![]() |
2bf90d6a6d | ||
![]() |
0f4319499a | ||
![]() |
5bb58a00cd | ||
![]() |
d50e9fc261 | ||
![]() |
b235929c77 | ||
![]() |
4561e2e374 | ||
![]() |
214b34ddfd | ||
![]() |
26817e9f92 | ||
![]() |
9ed0121fd0 | ||
![]() |
0d63d538c3 | ||
![]() |
5da441ff9a | ||
![]() |
49bc349064 | ||
![]() |
61a4d2f759 | ||
![]() |
0b4e738699 |
4
.github/workflows/nodejs.yml
vendored
@@ -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
@@ -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",
|
||||
|
@@ -19,7 +19,7 @@ limitations under the License. -->
|
||||
#app {
|
||||
color: #2c3e50;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
overflow: hidden;
|
||||
min-width: 1024px;
|
||||
}
|
||||
</style>
|
||||
|
15
src/assets/icons/circle.svg
Normal 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 |
16
src/assets/icons/conditions.svg
Normal 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
@@ -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 |
15
src/assets/icons/gateway.svg
Normal 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 |
@@ -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 |
15
src/assets/icons/operation.svg
Normal 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 |
BIN
src/assets/img/technologies/EVENTMESH.png
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
src/assets/img/technologies/IMPALA.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
@@ -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;
|
||||
|
@@ -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>
|
||||
|
@@ -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 */
|
||||
|
@@ -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);
|
||||
|
@@ -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",
|
||||
};
|
||||
|
@@ -69,6 +69,25 @@ export const TraceSpans = {
|
||||
value
|
||||
}
|
||||
}
|
||||
attachedEvents {
|
||||
startTime {
|
||||
seconds
|
||||
nanos
|
||||
}
|
||||
event
|
||||
endTime {
|
||||
seconds
|
||||
nanos
|
||||
}
|
||||
tags {
|
||||
key
|
||||
value
|
||||
}
|
||||
summary {
|
||||
key
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
@@ -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}}`;
|
||||
|
@@ -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
|
||||
}`,
|
||||
};
|
||||
|
138
src/hooks/useAssociateProcessor.ts
Normal 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 };
|
||||
}
|
142
src/hooks/useLegendProcessor.ts
Normal 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 };
|
||||
}
|
@@ -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,
|
||||
};
|
||||
}
|
||||
|
@@ -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);
|
@@ -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>
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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"),
|
||||
},
|
||||
|
@@ -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,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@@ -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"),
|
||||
},
|
||||
],
|
||||
},
|
@@ -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"),
|
||||
},
|
||||
],
|
||||
},
|
@@ -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"),
|
||||
},
|
||||
],
|
||||
},
|
47
src/router/data/gateway.ts
Normal 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",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
@@ -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
@@ -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,
|
||||
];
|
@@ -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"),
|
||||
// },
|
||||
],
|
||||
},
|
||||
];
|
@@ -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"),
|
||||
},
|
||||
],
|
||||
},
|
@@ -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"),
|
||||
},
|
||||
],
|
||||
},
|
@@ -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"),
|
||||
},
|
||||
],
|
||||
},
|
@@ -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
@@ -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();
|
@@ -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"),
|
||||
|
@@ -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;
|
||||
|
@@ -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) {
|
||||
|
@@ -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) {
|
||||
|
@@ -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 {
|
||||
|
@@ -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) {
|
||||
|
@@ -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";
|
||||
|
@@ -173,7 +173,7 @@
|
||||
}
|
||||
|
||||
.scroll_bar_style::-webkit-scrollbar {
|
||||
width: 9px;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
@@ -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
@@ -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;
|
||||
};
|
||||
|
2
src/types/components.d.ts
vendored
@@ -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']
|
||||
|
51
src/types/dashboard.d.ts
vendored
@@ -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
@@ -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
@@ -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[];
|
||||
}
|
||||
|
@@ -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();
|
||||
|
@@ -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];
|
||||
|
@@ -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 {
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
)
|
||||
|
@@ -23,6 +23,7 @@ limitations under the License. -->
|
||||
placeholder="Select a widget"
|
||||
class="selectors"
|
||||
@change="updateWidgetConfig"
|
||||
:filterable="false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
119
src/views/dashboard/configuration/widget/RelatedTraceOptions.vue
Normal 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>
|
@@ -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();
|
||||
|
@@ -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();
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
@@ -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,
|
||||
};
|
||||
|
@@ -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];
|
||||
}
|
||||
|
@@ -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];
|
||||
|
@@ -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;
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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" },
|
||||
];
|
||||
|
@@ -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: () => ({}),
|
||||
},
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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;
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
195
src/views/dashboard/graphs/components/Legend.vue
Normal 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>
|
@@ -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;
|
||||
}
|
||||
|
@@ -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})`
|
||||
);
|
||||
});
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
};
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
||||
|
@@ -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();
|
||||
}
|
||||
);
|
||||
|
@@ -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" },
|
||||
],
|
||||
};
|
@@ -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;
|
||||
|
@@ -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>
|
||||
|
@@ -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({
|
||||
|
@@ -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";
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
290
src/views/dashboard/related/trace/Header.vue
Normal 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>
|