45 Commits

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

View File

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

221
package-lock.json generated
View File

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

View File

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

View File

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

After

Width:  |  Height:  |  Size: 1001 B

View File

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

After

Width:  |  Height:  |  Size: 1.7 KiB

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

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

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

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

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -13,6 +13,5 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> <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> <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> </svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

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

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

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

View File

@@ -15,6 +15,28 @@ limitations under the License. -->
<template> <template>
<div class="chart" ref="chartRef" :style="`height:${height};width:${width};`"> <div class="chart" ref="chartRef" :style="`height:${height};width:${width};`">
<div v-if="!available" class="no-data">No Data</div> <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> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@@ -28,15 +50,28 @@ import {
computed, computed,
} from "vue"; } from "vue";
import type { PropType } 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 { useECharts } from "@/hooks/useEcharts";
import { addResizeListener, removeResizeListener } from "@/utils/event"; import { addResizeListener, removeResizeListener } from "@/utils/event";
import Trace from "@/views/dashboard/related/trace/Index.vue";
import associateProcessor from "@/hooks/useAssociateProcessor";
/*global Nullable, defineProps, defineEmits*/ /*global Nullable, defineProps, defineEmits*/
const emits = defineEmits(["select"]); const emits = defineEmits(["select"]);
const { t } = useI18n();
const chartRef = ref<Nullable<HTMLDivElement>>(null); const chartRef = ref<Nullable<HTMLDivElement>>(null);
const menus = ref<Nullable<HTMLDivElement>>(null);
const visMenus = ref<boolean>(false);
const { setOptions, resize, getInstance } = useECharts( const { setOptions, resize, getInstance } = useECharts(
chartRef as Ref<HTMLDivElement> 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({ const props = defineProps({
height: { type: String, default: "100%" }, height: { type: String, default: "100%" },
width: { type: String, default: "100%" }, width: { type: String, default: "100%" },
@@ -45,15 +80,14 @@ const props = defineProps({
default: () => ({}), default: () => ({}),
}, },
filters: { filters: {
type: Object as PropType<{ type: Object as PropType<Filters>,
duration: { },
startTime: string; relatedTrace: {
endTime: string; type: Object as PropType<RelatedTrace>,
}; },
isRange: boolean; associate: {
dataIndex?: number; type: Array as PropType<{ widgetId: string }[]>,
sourceId: string; default: () => [],
}>,
}, },
}); });
const available = computed( const available = computed(
@@ -66,14 +100,34 @@ const available = computed(
onMounted(async () => { onMounted(async () => {
await setOptions(props.option); await setOptions(props.option);
chartRef.value && addResizeListener(unref(chartRef), resize); chartRef.value && addResizeListener(unref(chartRef), resize);
instanceEvent();
});
function instanceEvent() {
setTimeout(() => { setTimeout(() => {
const instance = getInstance(); const instance = getInstance();
if (!instance) { if (!instance) {
return; return;
} }
instance.on("click", (params: unknown) => { instance.on("click", (params: EventParams) => {
emits("select", params); 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( document.addEventListener(
"click", "click",
@@ -81,9 +135,7 @@ onMounted(async () => {
if (instance.isDisposed()) { if (instance.isDisposed()) {
return; return;
} }
instance.dispatchAction({ visMenus.value = false;
type: "hideTip",
});
instance.dispatchAction({ instance.dispatchAction({
type: "updateAxisPointer", type: "updateAxisPointer",
currTrigger: "leave", currTrigger: "leave",
@@ -92,9 +144,18 @@ onMounted(async () => {
true true
); );
}, 1000); }, 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(); const instance = getInstance();
if (!instance) { if (!instance) {
return; return;
@@ -103,60 +164,32 @@ function updateOptions() {
return; return;
} }
if (props.filters.isRange) { if (props.filters.isRange) {
const { eventAssociate } = associateProcessor(props);
const options = eventAssociate(); const options = eventAssociate();
setOptions(options || props.option); setOptions(options || props.option);
} else { } else {
instance.dispatchAction({ instance.dispatchAction({
type: "showTip", type: "updateAxisPointer",
dataIndex: props.filters.dataIndex, dataIndex: params ? params.dataIndex : props.filters.dataIndex,
seriesIndex: 0, 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() { function viewTrace() {
if (!props.filters) { const item = associateProcessor(props).traceFilters(currentParams.value);
return; traceOptions.value = {
} ...traceOptions.value,
if (!props.filters.duration) { filters: item,
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); showTrace.value = true;
for (const [key, temp] of series.entries()) { visMenus.value = true;
if (key === 0) {
temp.markArea = markArea;
}
}
const options = {
...props.option,
series,
};
return options;
} }
watch( watch(
@@ -170,6 +203,7 @@ watch(
} }
let options; let options;
if (props.filters && props.filters.isRange) { if (props.filters && props.filters.isRange) {
const { eventAssociate } = associateProcessor(props);
options = eventAssociate(); options = eventAssociate();
} }
setOptions(options || props.option); setOptions(options || props.option);
@@ -200,5 +234,30 @@ onBeforeUnmount(() => {
.chart { .chart {
overflow: hidden; 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> </style>

View File

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

View File

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

View File

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

View File

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

View File

@@ -32,6 +32,6 @@ export const queryInstances = `query queryInstances(${Instances.variable}) {${In
export const queryLayers = `query listLayer {${Layers.query}}`; export const queryLayers = `query listLayer {${Layers.query}}`;
export const queryService = `query queryService(${getService.variable}) {${getService.query}}`; export const queryService = `query queryService(${getService.variable}) {${getService.query}}`;
export const queryInstance = `query queryInstance(${getInstance.variable}) {${getInstance.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 queryProcesses = `query queryProcesses(${Processes.variable}) {${Processes.query}}`;
export const queryProcess = `query queryProcess(${getProcess.variable}) {${getProcess.query}}`; export const queryProcess = `query queryProcess(${getProcess.variable}) {${getProcess.query}}`;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -52,17 +52,19 @@ const msg = {
instance: "Instance", instance: "Instance",
create: "Create", create: "Create",
loading: "Loading", loading: "Loading",
selectVisualization: "Visualize your metrics", selectVisualization: "Visualize Metrics",
visualization: "Visualization", visualization: "Visualization",
graphStyles: "Graph styles", graphStyles: "Graph Styles",
widgetOptions: "Widget options", widgetOptions: "Widget Options",
standardOptions: "Standard options", standardOptions: "Standard Options",
max: "Max", max: "Max",
min: "Min", min: "Min",
plus: "Plus", plus: "Plus",
mean: "Mean",
minus: "Minus", minus: "Minus",
multiply: "Multiply", multiply: "Multiply",
divide: "Divide", divide: "Divide",
total: "Total",
convertToMilliseconds: "Convert Unix Timestamp(milliseconds)", convertToMilliseconds: "Convert Unix Timestamp(milliseconds)",
convertToSeconds: "Convert Unix Timestamp(seconds)", convertToSeconds: "Convert Unix Timestamp(seconds)",
smooth: "Smooth", smooth: "Smooth",
@@ -108,6 +110,7 @@ const msg = {
showXAxis: "Show X Axis", showXAxis: "Show X Axis",
showYAxis: "Show Y Axis", showYAxis: "Show Y Axis",
nameError: "The dashboard name cannot be duplicate", nameError: "The dashboard name cannot be duplicate",
nameEmptyError: "The dashboard name cannot be empty",
showGroup: "Show Group", showGroup: "Show Group",
noRoot: "Please set a root dashboard for", noRoot: "Please set a root dashboard for",
noWidget: "Please add widgets.", noWidget: "Please add widgets.",
@@ -123,6 +126,7 @@ const msg = {
editWarning: "You are entering edit mode", editWarning: "You are entering edit mode",
viewWarning: "You are entering view mode", viewWarning: "You are entering view mode",
virtualDatabase: "Virtual Database", virtualDatabase: "Virtual Database",
virtualCache: "Virtual Cache",
reloadDashboards: "Reload dashboards", reloadDashboards: "Reload dashboards",
kubernetesService: "Service", kubernetesService: "Service",
kubernetesCluster: "Cluster", kubernetesCluster: "Cluster",
@@ -144,6 +148,7 @@ const msg = {
pause: "Pause", pause: "Pause",
begin: "Start", begin: "Start",
associateOptions: "Association Options", associateOptions: "Association Options",
associateMetrics: "Association Metrics",
widget: "Widget", widget: "Widget",
nameTip: nameTip:
"The name only supports Chinese and English, horizontal lines and underscores. The length of the name is limited to 300 characters", "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", text: "Text",
query: "Query", query: "Query",
postgreSQL: "PostgreSQL", 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", seconds: "Seconds",
hourTip: "Select Hour", hourTip: "Select Hour",
minuteTip: "Select Minute", minuteTip: "Select Minute",
@@ -244,7 +273,7 @@ const msg = {
entityType: "Entity Type", entityType: "Entity Type",
maxItemNum: "Max number of Item", maxItemNum: "Max number of Item",
unknownMetrics: "Unknown Metrics", unknownMetrics: "Unknown Metrics",
labels: "Labels", labels: "Label",
aggregation: "Calculation", aggregation: "Calculation",
unit: "Unit", unit: "Unit",
labelsIndex: "Label Subscript", labelsIndex: "Label Subscript",
@@ -305,6 +334,7 @@ const msg = {
eventsParameters: "Event Parameters", eventsParameters: "Event Parameters",
eventDetail: "Event Detail", eventDetail: "Event Detail",
value: "Value", value: "Value",
key: "Key",
show: "Show", show: "Show",
hide: "Hide", hide: "Hide",
statistics: "Statistics", statistics: "Statistics",
@@ -343,5 +373,7 @@ const msg = {
conditionNotice: conditionNotice:
"Notice: Please press Enter after inputting a key of content, exclude key of content(key=value).", "Notice: Please press Enter after inputting a key of content, exclude key of content(key=value).",
language: "Language", language: "Language",
gateway: "Gateway",
virtualMQ: "Virtual MQ",
}; };
export default msg; export default msg;

View File

@@ -59,9 +59,11 @@ const msg = {
standardOptions: "Opciones estandar", standardOptions: "Opciones estandar",
max: "Máx", max: "Máx",
min: "Mín", min: "Mín",
mean: "Promedio",
plus: "Más", plus: "Más",
minus: "Menoss", minus: "Menoss",
multiply: "Multiplcar", multiply: "Multiplcar",
total: "Todo",
divide: "Dividir", divide: "Dividir",
convertToMilliseconds: "Convertir Unix Timestamp(milisegundos)", convertToMilliseconds: "Convertir Unix Timestamp(milisegundos)",
convertToSeconds: "Convertir Unix Timestamp(segundos)", convertToSeconds: "Convertir Unix Timestamp(segundos)",
@@ -110,6 +112,7 @@ const msg = {
showXAxis: "Mostrar Eje X", showXAxis: "Mostrar Eje X",
showYAxis: "Mostrar Eje Y", showYAxis: "Mostrar Eje Y",
nameError: "El nombre del panel no puede ser duplicado", nameError: "El nombre del panel no puede ser duplicado",
nameEmptyError: "El nombre del panel no puede estar vacío",
showGroup: "Mostrar Grupo", showGroup: "Mostrar Grupo",
noRoot: "Por favor ponga la raíz del panel", noRoot: "Por favor ponga la raíz del panel",
noWidget: "Por favor añada widgets.", noWidget: "Por favor añada widgets.",
@@ -125,6 +128,7 @@ const msg = {
editWarning: "Estás entrando en modo edición", editWarning: "Estás entrando en modo edición",
viewWarning: "Estás entrando en modo visualización", viewWarning: "Estás entrando en modo visualización",
virtualDatabase: "Base de Datos Virtual", virtualDatabase: "Base de Datos Virtual",
virtualCache: "Caché virtual",
reloadDashboards: "Recargar Panel", reloadDashboards: "Recargar Panel",
kubernetesService: "Servicio", kubernetesService: "Servicio",
kubernetesCluster: "Cluster", kubernetesCluster: "Cluster",
@@ -144,6 +148,7 @@ const msg = {
pause: "Pausa", pause: "Pausa",
begin: "Inicio", begin: "Inicio",
associateOptions: "Opciones de asociación", associateOptions: "Opciones de asociación",
associateMetrics: "Índice de correlación",
widget: "Dispositivo pequeño", widget: "Dispositivo pequeño",
text: "Texto", text: "Texto",
duplicateName: "Nombre duplicado", duplicateName: "Nombre duplicado",
@@ -152,10 +157,34 @@ const msg = {
enableAssociate: "Activar asociación", enableAssociate: "Activar asociación",
query: "Consulta", query: "Consulta",
postgreSQL: "PostgreSQL", 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", seconds: "Segundos",
hourTip: "Seleccione Hora", hourTip: "Seleccione Hora",
minuteTip: "Seleccione Minuto", minuteTip: "Seleccione Minuto",
secondTip: "Seleccione Segundo", 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", second: "s",
yearSuffix: "Año", yearSuffix: "Año",
monthsHead: "Ene_Feb_Mar_Abr_May_Jun_Jul_Ago_Set_Oct_Nov_Dic", monthsHead: "Ene_Feb_Mar_Abr_May_Jun_Jul_Ago_Set_Oct_Nov_Dic",
@@ -305,6 +334,7 @@ const msg = {
eventsParameters: "Parámetro del Evento", eventsParameters: "Parámetro del Evento",
eventDetail: "Detalle del Evento", eventDetail: "Detalle del Evento",
value: "Valor", value: "Valor",
key: "Clave",
show: "Mostrar", show: "Mostrar",
hide: "Oculatr", hide: "Oculatr",
statistics: "Estadísticas", statistics: "Estadísticas",
@@ -346,5 +376,7 @@ const msg = {
conditionNotice: conditionNotice:
"Aviso: Por favor presione Intro después de introducir una clave de contenido, excluir clave de contenido(clave=valor).", "Aviso: Por favor presione Intro después de introducir una clave de contenido, excluir clave de contenido(clave=valor).",
language: "Lenguaje", language: "Lenguaje",
gateway: "Puerta",
virtualMQ: "MQ virtual",
}; };
export default msg; export default msg;

View File

@@ -56,10 +56,12 @@ const msg = {
standardOptions: "标准选项", standardOptions: "标准选项",
max: "最大值", max: "最大值",
min: "最小值", min: "最小值",
mean: "平均值",
plus: "加法", plus: "加法",
minus: "减法", minus: "减法",
multiply: "乘法", multiply: "乘法",
divide: "除法", divide: "除法",
total: "总计",
convertToMilliseconds: "转换Unix时间戳毫秒", convertToMilliseconds: "转换Unix时间戳毫秒",
convertToSeconds: "转换Unix时间戳", convertToSeconds: "转换Unix时间戳",
smooth: "光滑的", smooth: "光滑的",
@@ -106,6 +108,7 @@ const msg = {
showXAxis: "显示X轴", showXAxis: "显示X轴",
showYAxis: "显示Y轴", showYAxis: "显示Y轴",
nameError: "仪表板名称不能重复", nameError: "仪表板名称不能重复",
nameEmptyError: "仪表板名称不能为空",
noRoot: "请设置根仪表板,为", noRoot: "请设置根仪表板,为",
showGroup: "显示分组", showGroup: "显示分组",
noWidget: "请添加组件", noWidget: "请添加组件",
@@ -121,6 +124,7 @@ const msg = {
editWarning: "你正在进入编辑模式", editWarning: "你正在进入编辑模式",
viewWarning: "你正在进入预览模式", viewWarning: "你正在进入预览模式",
virtualDatabase: "虚拟数据库", virtualDatabase: "虚拟数据库",
virtualCache: "虚拟缓存",
reloadDashboards: "重新加载仪表盘", reloadDashboards: "重新加载仪表盘",
kubernetesService: "服务", kubernetesService: "服务",
kubernetesCluster: "集群", kubernetesCluster: "集群",
@@ -142,6 +146,7 @@ const msg = {
pause: "暂停", pause: "暂停",
begin: "开始", begin: "开始",
associateOptions: "关联选项", associateOptions: "关联选项",
associateMetrics: "关联指标",
widget: "部件", widget: "部件",
enableAssociate: "启用关联", enableAssociate: "启用关联",
nameTip: "该名称仅支持中文和英文、横线和下划线, 并且限制长度为300个字符", nameTip: "该名称仅支持中文和英文、横线和下划线, 并且限制长度为300个字符",
@@ -149,6 +154,28 @@ const msg = {
text: "文本", text: "文本",
query: "查询", query: "查询",
postgreSQL: "PostgreSQL", 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: "秒", seconds: "秒",
hourTip: "选择小时", hourTip: "选择小时",
minuteTip: "选择分钟", minuteTip: "选择分钟",
@@ -303,6 +330,7 @@ const msg = {
eventsParameters: "事件参数", eventsParameters: "事件参数",
eventDetail: "事件详情", eventDetail: "事件详情",
value: "数值", value: "数值",
key: "Key",
tableHeader: "表头名称", tableHeader: "表头名称",
tableValues: "表值", tableValues: "表值",
show: "展示", show: "展示",
@@ -343,5 +371,7 @@ const msg = {
conditionNotice: conditionNotice:
"请输入一个内容关键词或者内容不包含的关键词(key=value)之后回车", "请输入一个内容关键词或者内容不包含的关键词(key=value)之后回车",
language: "语言", language: "语言",
gateway: "网关",
virtualMQ: "虚拟消息队列",
}; };
export default msg; export default msg;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -153,6 +153,20 @@ pre {
margin-left: 5px; 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 { .switch {
margin: 0 5px; margin: 0 5px;
} }
@@ -167,7 +181,7 @@ div.vis-tooltip {
.vis-item { .vis-item {
cursor: pointer; cursor: pointer;
height: 17px; height: 20px;
} }
.vis-item.Error { .vis-item.Error {
@@ -184,7 +198,7 @@ div.vis-tooltip {
} }
.vis-item .vis-item-content { .vis-item .vis-item-content {
padding: 0 5px !important; padding: 0 3px !important;
} }
.vis-item.vis-selected.Error, .vis-item.vis-selected.Error,

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

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

View File

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

View File

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,12 +13,26 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <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> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from "vue"; import { computed } from "vue";
import type { PropType } 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 */ /*global defineProps, defineEmits */
const emits = defineEmits(["click"]); const emits = defineEmits(["click"]);
@@ -32,20 +46,18 @@ const props = defineProps({
config: { config: {
type: Object as PropType< type: Object as PropType<
BarConfig & { BarConfig & {
filters: { filters: Filters;
sourceId: string; relatedTrace: RelatedTrace;
duration: { id: string;
startTime: string; associate: { widgetId: string }[];
endTime: string; }
};
isRange: boolean;
dataIndex?: number;
};
} & { id: string }
>, >,
default: () => ({}), default: () => ({}),
}, },
}); });
const { showEchartsLegend, isRight, chartColors } = useLegendProcess(
props.config.legend
);
const option = computed(() => getOption()); const option = computed(() => getOption());
function getOption() { function getOption() {
@@ -75,52 +87,20 @@ function getOption() {
}, },
}; };
}); });
let color: string[] = []; const color: string[] = chartColors(keys);
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 { return {
color, color,
tooltip: { tooltip: {
trigger: "axis", trigger: "none",
zlevel: 1000, axisPointer: {
z: 60, type: "cross",
confine: true,
textStyle: {
fontSize: 13,
color: "#333", color: "#333",
backgroundColor: "rgba(255, 255, 255, 0.8)",
}, },
enterable: true,
extraCssText: "max-height: 300px; overflow: auto; border: none",
}, },
legend: { legend: {
type: "scroll", type: "scroll",
show: keys.length === 1 ? false : true, show: showEchartsLegend(keys),
icon: "circle", icon: "circle",
top: 0, top: 0,
left: 0, left: 0,
@@ -136,6 +116,12 @@ function getOption() {
bottom: 5, bottom: 5,
containLabel: true, containLabel: true,
}, },
axisPointer: {
label: {
color: "#333",
backgroundColor: "rgba(255, 255, 255, 0.8)",
},
},
xAxis: { xAxis: {
type: "category", type: "category",
axisTick: { axisTick: {
@@ -160,3 +146,9 @@ function clickEvent(params: EventParams) {
emits("click", params); emits("click", params);
} }
</script> </script>
<style lang="scss" scoped>
.graph {
width: 100%;
height: 100%;
}
</style>

View File

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

View File

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

View File

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

View File

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

View File

@@ -23,44 +23,84 @@ limitations under the License. -->
{{ i.name }} {{ i.name }}
</span> </span>
</div> </div>
<div class="copy"> <el-popover placement="bottom" trigger="click">
<Icon <template #reference>
iconName="review-list" <div class="operation-icon cp ml-10">
size="middle" <Icon iconName="ellipsis_v" size="middle" />
class="cp" </div>
@click="handleClick(i.name)" </template>
/> <div class="operation" @click="handleClick(i.name)">
</div> <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> </div>
<el-progress <el-progress
:stroke-width="6" :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']" :color="TextColors[config.color || 'purple']"
:show-text="false" :show-text="false"
/> />
</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> </div>
<div class="center no-data" v-else>No Data</div> <div class="center no-data" v-else>No Data</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { PropType } from "vue"; 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 copy from "@/utils/copy";
import { TextColors } from "@/views/dashboard/data"; import { TextColors } from "@/views/dashboard/data";
import Trace from "@/views/dashboard/related/trace/Index.vue";
import { QueryOrders, Status, RefIdTypes } from "../data";
/*global defineProps */ /*global defineProps */
const props = defineProps({ const props = defineProps({
data: { data: {
type: Object as PropType<{ type: Object as PropType<{
[key: string]: { name: string; value: number; traceIds: string[] }[]; [key: string]: { name: string; value: number; id: string }[];
}>, }>,
default: () => ({}), default: () => ({}),
}, },
config: { config: {
type: Object as PropType<{ color: string }>, type: Object as PropType<{
color: string;
metrics: string[];
relatedTrace: any;
}>,
default: () => ({ color: "purple" }), default: () => ({ color: "purple" }),
}, },
intervalTime: { type: Array as PropType<string[]>, default: () => [] }, 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 key = computed(() => Object.keys(props.data)[0] || "");
const available = computed( const available = computed(
() => () =>
@@ -78,6 +118,22 @@ const maxValue = computed(() => {
function handleClick(i: string) { function handleClick(i: string) {
copy(i); 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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.top-list { .top-list {
@@ -109,10 +165,6 @@ function handleClick(i: string) {
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.copy {
width: 30px;
}
.calls { .calls {
font-size: 12px; font-size: 12px;
padding: 0 5px; padding: 0 5px;
@@ -141,4 +193,22 @@ function handleClick(i: string) {
-webkit-box-pack: center; -webkit-box-pack: center;
-webkit-box-align: 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> </style>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,96 +17,6 @@
import icons from "@/assets/img/icons"; import icons from "@/assets/img/icons";
import { Call } from "@/types/topology"; 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 // Control Point coordinates of quadratic Bezier curve
function computeControlPoint(ps: number[], pe: number[], arc = 0.5) { function computeControlPoint(ps: number[], pe: number[], arc = 0.5) {
const deltaX = pe[0] - ps[0]; 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; const y = (1 - t) * (1 - t) * ps.y + 2 * t * (1 - t) * pc.y + t * t * pe.y;
return [x, 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( const controlPos = computeControlPoint(
[d.source.x, d.source.y], [d.source.x, d.source.y],
[d.target.x, d.target.y], [d.target.x, d.target.y],
0.5 0.5
); );
if (d.lowerArc) { if (d.lowerArc) {
controlPos[1] = controlPos[1] = -controlPos[1];
Math.abs(controlPos[1]) < 50 ? -controlPos[1] + 100 : -controlPos[1] - 10;
} }
const p = quadraticBezier( const p = quadraticBezier(
0.5, 0.5,
@@ -155,3 +70,43 @@ function getMidpoint(d: Call) {
); );
return p; return p;
} }
export function linkPath(d: Call) {
if (isNaN(d.source.x) || isNaN(d.source.y)) {
return;
}
if (isNaN(d.target.x) || isNaN(d.target.y)) {
return;
}
const controlPos = computeControlPoint(
[d.source.x, d.source.y - 5],
[d.target.x, d.target.y - 5],
0.5
);
if (d.lowerArc) {
controlPos[1] = -controlPos[1] - 10;
}
return (
"M" +
d.source.x +
" " +
(d.source.y - 5) +
" " +
"Q" +
controlPos[0] +
" " +
controlPos[1] +
" " +
d.target.x +
" " +
(d.target.y - 5)
);
}
export function getAnchor(d: Call) {
const types = [...d.sourceComponents, ...d.targetComponents];
if (types.includes("tcp") || types.includes("http")) {
return icons.HTTPDARK;
}
if (types.includes("https") || types.includes("tls")) {
return icons.HTTPS;
}
}

View File

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

View File

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

View File

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

View File

@@ -13,13 +13,103 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div ref="chart" class="process-topo"></div> <div ref="chart" class="process-topo">
<el-popover <svg
placement="bottom" class="process-svg"
:width="295" :width="width"
trigger="click" :height="height"
v-if="dashboardStore.editMode" @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> <template #reference>
<div class="switch-icon-edit ml-5" title="Settings" @click="setConfig"> <div class="switch-icon-edit ml-5" title="Settings" @click="setConfig">
<Icon size="middle" iconName="setting_empty" /> <Icon size="middle" iconName="setting_empty" />
@@ -39,9 +129,7 @@ import router from "@/router";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling"; import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import d3tip from "d3-tip"; import { linkPath, getAnchor, getMidpoint } from "./Graph/linkProcess";
import { linkElement, anchorElement, arrowMarker } from "./Graph/linkProcess";
import nodeElement from "./Graph/nodeProcess";
import { Call } from "@/types/topology"; import { Call } from "@/types/topology";
import zoom from "../../components/utils/zoom"; import zoom from "../../components/utils/zoom";
import { ProcessNode } from "@/types/ebpf"; import { ProcessNode } from "@/types/ebpf";
@@ -52,6 +140,7 @@ import getDashboard from "@/hooks/useDashboardsSession";
import { Layout } from "./Graph/layout"; import { Layout } from "./Graph/layout";
import TimeLine from "./TimeLine.vue"; import TimeLine from "./TimeLine.vue";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import icons from "@/assets/img/icons";
/*global Nullable, defineProps */ /*global Nullable, defineProps */
const props = defineProps({ const props = defineProps({
@@ -67,19 +156,18 @@ const selectorStore = useSelectorStore();
const networkProfilingStore = useNetworkProfilingStore(); const networkProfilingStore = useNetworkProfilingStore();
const height = ref<number>(100); const height = ref<number>(100);
const width = ref<number>(100); const width = ref<number>(100);
const svg = ref<Nullable<any>>(null);
const chart = ref<Nullable<HTMLDivElement>>(null); const chart = ref<Nullable<HTMLDivElement>>(null);
const tip = ref<Nullable<HTMLDivElement>>(null); const tooltip = ref<Nullable<any>>(null);
const graph = ref<any>(null); const svg = ref<Nullable<any>>(null);
const node = ref<any>(null); const graph = ref<Nullable<any>>(null);
const link = ref<any>(null);
const anchor = ref<any>(null);
const arrow = ref<any>(null);
const oldVal = ref<{ width: number; height: number }>({ width: 0, height: 0 }); const oldVal = ref<{ width: number; height: number }>({ width: 0, height: 0 });
const config = ref<any>(props.config || {}); const config = ref<any>(props.config || {});
const diff = ref<number[]>([220, 200]); const diff = ref<number[]>([220, 200]);
const radius = 210; const radius = 210;
const dates = ref<Nullable<{ start: number; end: number }>>(null); const dates = ref<Nullable<{ start: number; end: number }>>(null);
const nodeList = ref<ProcessNode[]>([]);
const currentNode = ref<Nullable<ProcessNode>>(null);
const origin = [0, 0];
onMounted(() => { onMounted(() => {
init(); init();
@@ -90,12 +178,14 @@ onMounted(() => {
}); });
async function init() { async function init() {
svg.value = d3.select(chart.value).append("svg").attr("class", "process-svg");
if (!networkProfilingStore.nodes.length) { if (!networkProfilingStore.nodes.length) {
return; return;
} }
drawGraph(); svg.value = d3.select(".process-svg");
createLayout(); graph.value = d3.select(".svg-graph");
tooltip.value = d3.select("#tooltip");
freshNodes();
useThrottleFn(resize, 500)();
} }
function drawGraph() { function drawGraph() {
@@ -105,27 +195,16 @@ function drawGraph() {
}; };
height.value = (dom.height || 40) - 20; height.value = (dom.height || 40) - 20;
width.value = dom.width; 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; 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.call(zoom(d3, graph.value, diff.value));
svg.value.on("click", (event: any) => { }
event.stopPropagation();
event.preventDefault(); function clickTopology(event: MouseEvent) {
networkProfilingStore.setNode(null); event.stopPropagation();
networkProfilingStore.setLink(null); event.preventDefault();
dashboardStore.selectWidget(props.config); networkProfilingStore.setNode(null);
}); networkProfilingStore.setLink(null);
useThrottleFn(resize, 500)(); dashboardStore.selectWidget(props.config);
} }
function hexGrid(n = 1, radius = 1, origin = [0, 0]) { 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) { function getCirclePoint(radius: number, p = 1) {
const data = []; const data = [];
const origin = [0, 0];
for (let index = 0; index < 360; index = index + p) { for (let index = 0; index < 360; index = index + p) {
if (index < 230 || index > 310) { if (index < 230 || index > 310) {
let x = radius * Math.cos((Math.PI * 2 * index) / 360); let x = radius * Math.cos((Math.PI * 2 * index) / 360);
@@ -167,10 +245,22 @@ function getCirclePoint(radius: number, p = 1) {
} }
return data; return data;
} }
function createLayout() {
if (!node.value || !link.value) { function getHexPolygonVertices() {
return; 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()) || { const dom: any = (chart.value && chart.value.getBoundingClientRect()) || {
width: 0, width: 0,
height: 0, height: 0,
@@ -182,28 +272,6 @@ function createLayout() {
count: 1, count: 1,
radius, // layout hexagons radius 300 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( const nodeArr = networkProfilingStore.nodes.filter(
(d: ProcessNode) => d.isReal || d.name === "UNKNOWN_LOCAL" (d: ProcessNode) => d.isReal || d.name === "UNKNOWN_LOCAL"
); );
@@ -278,67 +346,11 @@ function createLayout() {
outNodes[v].x = pointArr[v][0]; outNodes[v].x = pointArr[v][0];
outNodes[v].y = pointArr[v][1]; outNodes[v].y = pointArr[v][1];
} }
drawTopology([...nodeArr, ...outNodes]); nodeList.value = [...nodeArr, ...outNodes];
} const drag: any = d3.drag().on("drag", (d: ProcessNode) => {
moveNode(d);
function drawTopology(nodeArr: any[]) { });
node.value = node.value.data(nodeArr, (d: ProcessNode) => d.id); d3.selectAll(".node").call(drag);
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);
} }
function shuffleArray(array: number[][]) { function shuffleArray(array: number[][]) {
@@ -347,7 +359,6 @@ function shuffleArray(array: number[][]) {
[array[i], array[j]] = [array[j], array[i]]; [array[i], array[j]] = [array[j], array[i]];
} }
} }
function handleLinkClick(event: any, d: Call) { function handleLinkClick(event: any, d: Call) {
event.stopPropagation(); event.stopPropagation();
networkProfilingStore.setNode(null); networkProfilingStore.setNode(null);
@@ -423,13 +434,100 @@ function resize() {
} }
async function freshNodes() { async function freshNodes() {
svg.value.selectAll(".svg-graph").remove();
if (!networkProfilingStore.nodes.length) { if (!networkProfilingStore.nodes.length) {
return; return;
} }
drawGraph(); drawGraph();
createLayout(); 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( watch(
() => networkProfilingStore.nodes, () => networkProfilingStore.nodes,
@@ -500,4 +598,13 @@ watch(
.query { .query {
margin-left: 510px; margin-left: 510px;
} }
#tooltip {
position: absolute;
visibility: hidden;
padding: 5px;
border: 1px solid #000;
border-radius: 3px;
background-color: #fff;
}
</style> </style>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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