47 Commits

Author SHA1 Message Date
Fine0830
065337c344 Fix the topology layout for there are multiple independent network relationships (#397) 2024-05-24 17:15:31 +08:00
Starry
21fe455fd6 fix: browser log display (#396) 2024-05-21 23:11:23 +08:00
Starry
88dbee311c fix: statistics span data (#395) 2024-05-18 13:45:02 +08:00
Fine0830
9001a96128 fix: widget title and tips (#394) 2024-05-11 13:44:11 +08:00
Fine0830
e4b2203cf6 fix: update (#393) 2024-04-19 15:54:17 +08:00
Chen Ziyan
54c236bacf fix: optimize search reset and format in marketplace (#392) 2024-04-18 17:27:56 +08:00
Chen Ziyan
f001290658 docs: update img of ActiveMQ (#388) 2024-04-17 12:35:41 +08:00
Starry
13b2693f29 fix: css details (#391) 2024-04-17 09:53:43 +08:00
Fine0830
731d652a7d fix: polish (#390) 2024-04-16 17:52:51 +08:00
Fine0830
7f6e4d09c0 Fix the Table widget (#389) 2024-04-16 15:00:18 +08:00
Fine0830
12cd279c90 Feat: enhance the Trace widget for batch consuming spans (#387) 2024-04-15 15:33:09 +08:00
Fine0830
62eb054ff5 fix: remove unused configurations (#386) 2024-04-12 18:08:47 +08:00
Fine0830
e0bbe99b6c fix: remove metrics for unreal nodes (#385) 2024-04-12 13:25:34 +08:00
Fine0830
03f321b62a refactor: remove the General metric mode and related logical code (#384) 2024-04-11 17:50:43 +08:00
Fine0830
460b24f42c feat: support the SINGLE_VALUE for table widgets (#383) 2024-04-10 23:35:15 +08:00
Fine0830
fd2c7ca716 feat: enhance metric labels (#382) 2024-04-10 16:40:14 +08:00
Fine0830
8bc6761468 feat: add workflow icon (#381) 2024-04-06 19:44:58 +08:00
Starry
8746d3c985 feat: add Support for dragging in the trace panel (#377) 2024-04-06 19:17:17 +08:00
songzhendong
c18058765a Add Airflow monitor (#379) 2024-04-06 19:14:24 +08:00
dependabot[bot]
b9e0eadecb build(deps-dev): bump axios from 0.25.0 to 1.6.8 (#380) 2024-04-06 19:09:41 +08:00
dependabot[bot]
680f1263a5 build(deps-dev): bump vite from 4.5.2 to 4.5.3 (#378) 2024-04-06 19:00:30 +08:00
Chen Ziyan
0e0b4e1ff1 fix(doc): fix the dev local port (#375) 2024-03-12 18:11:50 +08:00
Fine0830
2faeecebcc Fix: refresh nodes/calls metrics as global duration changes for Topology widget, change date picker theme (#374) 2024-03-04 11:48:55 +08:00
Fine0830
e25bf9ee8b fix: end loading without query results (#373) 2024-02-26 19:57:02 +08:00
周小杰
d0ebdefcee fix: remove click event after unmounted (#372) 2024-02-21 16:42:56 +08:00
peachisai
7342036646 Update i18n for rocketmq monitoring (#359) 2024-02-20 21:25:52 +08:00
Fine0830
8e58f000a0 refactor: change the nodes type for Hierarchy Topology (#371) 2024-02-02 11:50:43 +08:00
dependabot[bot]
931cea4c4c build(deps-dev): bump vite from 4.0.5 to 4.5.2 (#366) 2024-01-31 12:37:37 +08:00
Fine0830
ccb4d78f6b feat: add the layers filed and associate layers dashboards for the service topology nodes (#370) 2024-01-30 15:59:16 +08:00
Fine0830
860af150f7 fix: enhance VNode logic and support multiple Trace IDs in span's ref (#369) 2024-01-29 23:09:00 +08:00
Fine0830
7d24e065e9 feat: add the not found page (#368) 2024-01-24 16:09:15 +08:00
Fine0830
7aef327d2e fix: resizing window causes the trace graph to display incorrectly (#367) 2024-01-22 22:07:03 +08:00
Fine0830
f76500bb6e fix: optimize tooltips style (#365) 2024-01-18 11:43:07 +08:00
Fine0830
63e01540dc fix: list (#364) 2024-01-17 12:21:07 +08:00
Fine0830
a46b91d1cf fix: import dashboard templates (#363) 2024-01-16 18:23:55 +08:00
Fine0830
5061b19cf7 fix: polish the hierarchy topology and dashboard configs (#362) 2024-01-16 17:04:21 +08:00
Fine0830
f809123b4f fix: link addresses on hierarchy topology nodes (#361) 2024-01-15 22:04:39 +08:00
Fine0830
306508856b feat: implement the Service and Instance hierarchy topology. (#360) 2024-01-13 11:05:32 +08:00
Fine0830
867af6924d feat: add expressions to dashboard configurations on the dashboard list page (#358) 2024-01-11 13:40:37 +08:00
dependabot[bot]
4ec99fc868 build(deps): bump follow-redirects from 1.15.2 to 1.15.4 (#357) 2024-01-10 17:55:48 +08:00
苗大
b5c135b811 perf(Theme): add theme change animation (#356) 2024-01-04 15:49:36 +08:00
Fine0830
300ec27ec4 feat: add isDefault to the dashboard configuration (#355) 2024-01-03 19:11:37 +08:00
何延龙
c5d80d96fb fix: set default route (#354) 2023-12-26 19:56:42 +08:00
Fine0830
001fa25a3b feat: support Tabs in the widget visiable when MQE expressions (#353) 2023-12-20 17:58:00 +08:00
Fine0830
0d82507a87 fix: span related logs (#352) 2023-12-05 20:09:23 -08:00
Fine0830
591484b11c fix: Trace associates with Log widget (#351)
* fix: view logs on trace widget

* fix: service
2023-12-04 16:45:07 +08:00
Fine0830
b2ab93926d feat: support search on Marketplace (#350) 2023-11-30 07:30:01 -08:00
113 changed files with 3765 additions and 3182 deletions

View File

@@ -34,14 +34,15 @@ npm install
npm run dev
```
The default UI address is `http://localhost:8080`.
The default UI address is `http://localhost:3000`.
# Contact Us
* Mail list: **dev@skywalking.apache.org**. Mail to `dev-subscribe@skywalking.apache.org`, follow the reply to subscribe to the mail list.
* Send `Request to join SkyWalking slack` mail to the mail list(`dev@skywalking.apache.org`), we will invite you in.
* For Chinese speaker, send `[CN] Request to join SkyWalking slack` mail to the mail list(`dev@skywalking.apache.org`), we will invite you in.
* Twitter, [ASFSkyWalking](https://twitter.com/AsfSkyWalking)
* [bilibili B站 视频](https://space.bilibili.com/390683219)
- Mail list: **dev@skywalking.apache.org**. Mail to `dev-subscribe@skywalking.apache.org`, follow the reply to subscribe to the mail list.
- Send `Request to join SkyWalking slack` mail to the mail list(`dev@skywalking.apache.org`), we will invite you in.
- For Chinese speaker, send `[CN] Request to join SkyWalking slack` mail to the mail list(`dev@skywalking.apache.org`), we will invite you in.
- Twitter, [ASFSkyWalking](https://twitter.com/AsfSkyWalking)
- [bilibili B 站 视频](https://space.bilibili.com/390683219)
# License

467
package-lock.json generated
View File

@@ -64,7 +64,7 @@
"typescript": "~4.7.4",
"unplugin-auto-import": "^0.7.0",
"unplugin-vue-components": "^0.19.2",
"vite": "^4.0.5",
"vite": "^4.5.3",
"vite-plugin-monaco-editor": "^1.1.0",
"vite-plugin-svg-icons": "^2.0.1",
"vitest": "^0.25.6",
@@ -1383,9 +1383,9 @@
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.4.tgz",
"integrity": "sha512-rZzb7r22m20S1S7ufIc6DC6W659yxoOrl7sKP1nCYhuvUlnCFHVSbATG4keGUtV8rDz11sRRDbWkvQZpzPaHiw==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
"integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
"cpu": [
"arm"
],
@@ -1399,9 +1399,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.4.tgz",
"integrity": "sha512-VPuTzXFm/m2fcGfN6CiwZTlLzxrKsWbPkG7ArRFpuxyaHUm/XFHQPD4xNwZT6uUmpIHhnSjcaCmcla8COzmZ5Q==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
"integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
"cpu": [
"arm64"
],
@@ -1415,9 +1415,9 @@
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.4.tgz",
"integrity": "sha512-MW+B2O++BkcOfMWmuHXB15/l1i7wXhJFqbJhp82IBOais8RBEQv2vQz/jHrDEHaY2X0QY7Wfw86SBL2PbVOr0g==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
"integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
"cpu": [
"x64"
],
@@ -1431,9 +1431,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.4.tgz",
"integrity": "sha512-a28X1O//aOfxwJVZVs7ZfM8Tyih2Za4nKJrBwW5Wm4yKsnwBy9aiS/xwpxiiTRttw3EaTg4Srerhcm6z0bu9Wg==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
"integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
"cpu": [
"arm64"
],
@@ -1447,9 +1447,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.4.tgz",
"integrity": "sha512-e3doCr6Ecfwd7VzlaQqEPrnbvvPjE9uoTpxG5pyLzr2rI2NMjDHmvY1E5EO81O/e9TUOLLkXA5m6T8lfjK9yAA==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
"integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
"cpu": [
"x64"
],
@@ -1463,9 +1463,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.4.tgz",
"integrity": "sha512-Oup3G/QxBgvvqnXWrBed7xxkFNwAwJVHZcklWyQt7YCAL5bfUkaa6FVWnR78rNQiM8MqqLiT6ZTZSdUFuVIg1w==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
"integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
"cpu": [
"arm64"
],
@@ -1479,9 +1479,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.4.tgz",
"integrity": "sha512-vAP+eYOxlN/Bpo/TZmzEQapNS8W1njECrqkTpNgvXskkkJC2AwOXwZWai/Kc2vEFZUXQttx6UJbj9grqjD/+9Q==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
"integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
"cpu": [
"x64"
],
@@ -1495,9 +1495,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.4.tgz",
"integrity": "sha512-A47ZmtpIPyERxkSvIv+zLd6kNIOtJH03XA0Hy7jaceRDdQaQVGSDt4mZqpWqJYgDk9rg96aglbF6kCRvPGDSUA==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
"integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
"cpu": [
"arm"
],
@@ -1511,9 +1511,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.4.tgz",
"integrity": "sha512-2zXoBhv4r5pZiyjBKrOdFP4CXOChxXiYD50LRUU+65DkdS5niPFHbboKZd/c81l0ezpw7AQnHeoCy5hFrzzs4g==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
"integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
"cpu": [
"arm64"
],
@@ -1527,9 +1527,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.4.tgz",
"integrity": "sha512-uxdSrpe9wFhz4yBwt2kl2TxS/NWEINYBUFIxQtaEVtglm1eECvsj1vEKI0KX2k2wCe17zDdQ3v+jVxfwVfvvjw==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
"integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
"cpu": [
"ia32"
],
@@ -1543,9 +1543,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.4.tgz",
"integrity": "sha512-peDrrUuxbZ9Jw+DwLCh/9xmZAk0p0K1iY5d2IcwmnN+B87xw7kujOkig6ZRcZqgrXgeRGurRHn0ENMAjjD5DEg==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
"integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
"cpu": [
"loong64"
],
@@ -1559,9 +1559,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.4.tgz",
"integrity": "sha512-sD9EEUoGtVhFjjsauWjflZklTNr57KdQ6xfloO4yH1u7vNQlOfAlhEzbyBKfgbJlW7rwXYBdl5/NcZ+Mg2XhQA==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
"integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
"cpu": [
"mips64el"
],
@@ -1575,9 +1575,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.4.tgz",
"integrity": "sha512-X1HSqHUX9D+d0l6/nIh4ZZJ94eQky8d8z6yxAptpZE3FxCWYWvTDd9X9ST84MGZEJx04VYUD/AGgciddwO0b8g==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
"integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
"cpu": [
"ppc64"
],
@@ -1591,9 +1591,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.4.tgz",
"integrity": "sha512-97ANpzyNp0GTXCt6SRdIx1ngwncpkV/z453ZuxbnBROCJ5p/55UjhbaG23UdHj88fGWLKPFtMoU4CBacz4j9FA==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
"integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
"cpu": [
"riscv64"
],
@@ -1607,9 +1607,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.4.tgz",
"integrity": "sha512-pUvPQLPmbEeJRPjP0DYTC1vjHyhrnCklQmCGYbipkep+oyfTn7GTBJXoPodR7ZS5upmEyc8lzAkn2o29wD786A==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
"integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
"cpu": [
"s390x"
],
@@ -1623,9 +1623,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.4.tgz",
"integrity": "sha512-N55Q0mJs3Sl8+utPRPBrL6NLYZKBCLLx0bme/+RbjvMforTGGzFvsRl4xLTZMUBFC1poDzBEPTEu5nxizQ9Nlw==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
"integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
"cpu": [
"x64"
],
@@ -1639,9 +1639,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.4.tgz",
"integrity": "sha512-LHSJLit8jCObEQNYkgsDYBh2JrJT53oJO2HVdkSYLa6+zuLJh0lAr06brXIkljrlI+N7NNW1IAXGn/6IZPi3YQ==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
"integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
"cpu": [
"x64"
],
@@ -1655,9 +1655,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.4.tgz",
"integrity": "sha512-nLgdc6tWEhcCFg/WVFaUxHcPK3AP/bh+KEwKtl69Ay5IBqUwKDaq/6Xk0E+fh/FGjnLwqFSsarsbPHeKM8t8Sw==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
"integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
"cpu": [
"x64"
],
@@ -1671,9 +1671,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.4.tgz",
"integrity": "sha512-08SluG24GjPO3tXKk95/85n9kpyZtXCVwURR2i4myhrOfi3jspClV0xQQ0W0PYWHioJj+LejFMt41q+PG3mlAQ==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
"integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
"cpu": [
"x64"
],
@@ -1687,9 +1687,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.4.tgz",
"integrity": "sha512-yYiRDQcqLYQSvNQcBKN7XogbrSvBE45FEQdH8fuXPl7cngzkCvpsG2H9Uey39IjQ6gqqc+Q4VXYHsQcKW0OMjQ==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
"integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
"cpu": [
"arm64"
],
@@ -1703,9 +1703,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.4.tgz",
"integrity": "sha512-5rabnGIqexekYkh9zXG5waotq8mrdlRoBqAktjx2W3kb0zsI83mdCwrcAeKYirnUaTGztR5TxXcXmQrEzny83w==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
"integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
"cpu": [
"ia32"
],
@@ -1719,9 +1719,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.4.tgz",
"integrity": "sha512-sN/I8FMPtmtT2Yw+Dly8Ur5vQ5a/RmC8hW7jO9PtPSQUPkowxWpcUZnqOggU7VwyT3Xkj6vcXWd3V/qTXwultQ==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
"integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
"cpu": [
"x64"
],
@@ -3666,11 +3666,11 @@
"dev": true
},
"node_modules/axios": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz",
"integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==",
"version": "1.6.8",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
"integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
"dependencies": {
"follow-redirects": "^1.15.0",
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
@@ -5912,9 +5912,9 @@
}
},
"node_modules/esbuild": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.4.tgz",
"integrity": "sha512-qQrPMQpPTWf8jHugLWHoGqZjApyx3OEm76dlTXobHwh/EBbavbRdjXdYi/GWr43GyN0sfpap14GPkb05NH3ROA==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
"integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
"dev": true,
"hasInstallScript": true,
"bin": {
@@ -5924,28 +5924,28 @@
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/android-arm": "0.16.4",
"@esbuild/android-arm64": "0.16.4",
"@esbuild/android-x64": "0.16.4",
"@esbuild/darwin-arm64": "0.16.4",
"@esbuild/darwin-x64": "0.16.4",
"@esbuild/freebsd-arm64": "0.16.4",
"@esbuild/freebsd-x64": "0.16.4",
"@esbuild/linux-arm": "0.16.4",
"@esbuild/linux-arm64": "0.16.4",
"@esbuild/linux-ia32": "0.16.4",
"@esbuild/linux-loong64": "0.16.4",
"@esbuild/linux-mips64el": "0.16.4",
"@esbuild/linux-ppc64": "0.16.4",
"@esbuild/linux-riscv64": "0.16.4",
"@esbuild/linux-s390x": "0.16.4",
"@esbuild/linux-x64": "0.16.4",
"@esbuild/netbsd-x64": "0.16.4",
"@esbuild/openbsd-x64": "0.16.4",
"@esbuild/sunos-x64": "0.16.4",
"@esbuild/win32-arm64": "0.16.4",
"@esbuild/win32-ia32": "0.16.4",
"@esbuild/win32-x64": "0.16.4"
"@esbuild/android-arm": "0.18.20",
"@esbuild/android-arm64": "0.18.20",
"@esbuild/android-x64": "0.18.20",
"@esbuild/darwin-arm64": "0.18.20",
"@esbuild/darwin-x64": "0.18.20",
"@esbuild/freebsd-arm64": "0.18.20",
"@esbuild/freebsd-x64": "0.18.20",
"@esbuild/linux-arm": "0.18.20",
"@esbuild/linux-arm64": "0.18.20",
"@esbuild/linux-ia32": "0.18.20",
"@esbuild/linux-loong64": "0.18.20",
"@esbuild/linux-mips64el": "0.18.20",
"@esbuild/linux-ppc64": "0.18.20",
"@esbuild/linux-riscv64": "0.18.20",
"@esbuild/linux-s390x": "0.18.20",
"@esbuild/linux-x64": "0.18.20",
"@esbuild/netbsd-x64": "0.18.20",
"@esbuild/openbsd-x64": "0.18.20",
"@esbuild/sunos-x64": "0.18.20",
"@esbuild/win32-arm64": "0.18.20",
"@esbuild/win32-ia32": "0.18.20",
"@esbuild/win32-x64": "0.18.20"
}
},
"node_modules/esbuild-android-64": {
@@ -7167,9 +7167,9 @@
"dev": true
},
"node_modules/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"funding": [
{
"type": "individual",
@@ -9839,9 +9839,9 @@
"dev": true
},
"node_modules/nanoid": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"funding": [
{
"type": "github",
@@ -10645,9 +10645,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.24",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
"integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
"version": "8.4.33",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
"integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
"funding": [
{
"type": "opencollective",
@@ -10663,7 +10663,7 @@
}
],
"dependencies": {
"nanoid": "^3.3.6",
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
@@ -11602,9 +11602,9 @@
"integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g=="
},
"node_modules/rollup": {
"version": "3.7.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.7.3.tgz",
"integrity": "sha512-7e68MQbAWCX6mI4/0lG1WHd+NdNAlVamg0Zkd+8LZ/oXojligdGnCNyHlzXqXCZObyjs5FRc3AH0b17iJESGIQ==",
"version": "3.29.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
"integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
@@ -14569,15 +14569,14 @@
}
},
"node_modules/vite": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.5.tgz",
"integrity": "sha512-7m87RC+caiAxG+8j3jObveRLqaWA/neAdCat6JAZwMkSWqFHOvg8MYe5fAQxVBRAuKAQ1S6XDh3CBQuLNbY33w==",
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
"dev": true,
"dependencies": {
"esbuild": "^0.16.3",
"postcss": "^8.4.20",
"resolve": "^1.22.1",
"rollup": "^3.7.0"
"esbuild": "^0.18.10",
"postcss": "^8.4.27",
"rollup": "^3.27.1"
},
"bin": {
"vite": "bin/vite.js"
@@ -14585,12 +14584,16 @@
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
},
"peerDependencies": {
"@types/node": ">= 14",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"stylus": "*",
"sugarss": "*",
@@ -14603,6 +14606,9 @@
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
@@ -16219,156 +16225,156 @@
"requires": {}
},
"@esbuild/android-arm": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.4.tgz",
"integrity": "sha512-rZzb7r22m20S1S7ufIc6DC6W659yxoOrl7sKP1nCYhuvUlnCFHVSbATG4keGUtV8rDz11sRRDbWkvQZpzPaHiw==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
"integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
"dev": true,
"optional": true
},
"@esbuild/android-arm64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.4.tgz",
"integrity": "sha512-VPuTzXFm/m2fcGfN6CiwZTlLzxrKsWbPkG7ArRFpuxyaHUm/XFHQPD4xNwZT6uUmpIHhnSjcaCmcla8COzmZ5Q==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
"integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
"dev": true,
"optional": true
},
"@esbuild/android-x64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.4.tgz",
"integrity": "sha512-MW+B2O++BkcOfMWmuHXB15/l1i7wXhJFqbJhp82IBOais8RBEQv2vQz/jHrDEHaY2X0QY7Wfw86SBL2PbVOr0g==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
"integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
"dev": true,
"optional": true
},
"@esbuild/darwin-arm64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.4.tgz",
"integrity": "sha512-a28X1O//aOfxwJVZVs7ZfM8Tyih2Za4nKJrBwW5Wm4yKsnwBy9aiS/xwpxiiTRttw3EaTg4Srerhcm6z0bu9Wg==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
"integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
"dev": true,
"optional": true
},
"@esbuild/darwin-x64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.4.tgz",
"integrity": "sha512-e3doCr6Ecfwd7VzlaQqEPrnbvvPjE9uoTpxG5pyLzr2rI2NMjDHmvY1E5EO81O/e9TUOLLkXA5m6T8lfjK9yAA==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
"integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
"dev": true,
"optional": true
},
"@esbuild/freebsd-arm64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.4.tgz",
"integrity": "sha512-Oup3G/QxBgvvqnXWrBed7xxkFNwAwJVHZcklWyQt7YCAL5bfUkaa6FVWnR78rNQiM8MqqLiT6ZTZSdUFuVIg1w==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
"integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
"dev": true,
"optional": true
},
"@esbuild/freebsd-x64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.4.tgz",
"integrity": "sha512-vAP+eYOxlN/Bpo/TZmzEQapNS8W1njECrqkTpNgvXskkkJC2AwOXwZWai/Kc2vEFZUXQttx6UJbj9grqjD/+9Q==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
"integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-arm": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.4.tgz",
"integrity": "sha512-A47ZmtpIPyERxkSvIv+zLd6kNIOtJH03XA0Hy7jaceRDdQaQVGSDt4mZqpWqJYgDk9rg96aglbF6kCRvPGDSUA==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
"integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
"dev": true,
"optional": true
},
"@esbuild/linux-arm64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.4.tgz",
"integrity": "sha512-2zXoBhv4r5pZiyjBKrOdFP4CXOChxXiYD50LRUU+65DkdS5niPFHbboKZd/c81l0ezpw7AQnHeoCy5hFrzzs4g==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
"integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
"dev": true,
"optional": true
},
"@esbuild/linux-ia32": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.4.tgz",
"integrity": "sha512-uxdSrpe9wFhz4yBwt2kl2TxS/NWEINYBUFIxQtaEVtglm1eECvsj1vEKI0KX2k2wCe17zDdQ3v+jVxfwVfvvjw==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
"integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
"dev": true,
"optional": true
},
"@esbuild/linux-loong64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.4.tgz",
"integrity": "sha512-peDrrUuxbZ9Jw+DwLCh/9xmZAk0p0K1iY5d2IcwmnN+B87xw7kujOkig6ZRcZqgrXgeRGurRHn0ENMAjjD5DEg==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
"integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
"dev": true,
"optional": true
},
"@esbuild/linux-mips64el": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.4.tgz",
"integrity": "sha512-sD9EEUoGtVhFjjsauWjflZklTNr57KdQ6xfloO4yH1u7vNQlOfAlhEzbyBKfgbJlW7rwXYBdl5/NcZ+Mg2XhQA==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
"integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-ppc64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.4.tgz",
"integrity": "sha512-X1HSqHUX9D+d0l6/nIh4ZZJ94eQky8d8z6yxAptpZE3FxCWYWvTDd9X9ST84MGZEJx04VYUD/AGgciddwO0b8g==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
"integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
"dev": true,
"optional": true
},
"@esbuild/linux-riscv64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.4.tgz",
"integrity": "sha512-97ANpzyNp0GTXCt6SRdIx1ngwncpkV/z453ZuxbnBROCJ5p/55UjhbaG23UdHj88fGWLKPFtMoU4CBacz4j9FA==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
"integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
"dev": true,
"optional": true
},
"@esbuild/linux-s390x": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.4.tgz",
"integrity": "sha512-pUvPQLPmbEeJRPjP0DYTC1vjHyhrnCklQmCGYbipkep+oyfTn7GTBJXoPodR7ZS5upmEyc8lzAkn2o29wD786A==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
"integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-x64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.4.tgz",
"integrity": "sha512-N55Q0mJs3Sl8+utPRPBrL6NLYZKBCLLx0bme/+RbjvMforTGGzFvsRl4xLTZMUBFC1poDzBEPTEu5nxizQ9Nlw==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
"integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
"dev": true,
"optional": true
},
"@esbuild/netbsd-x64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.4.tgz",
"integrity": "sha512-LHSJLit8jCObEQNYkgsDYBh2JrJT53oJO2HVdkSYLa6+zuLJh0lAr06brXIkljrlI+N7NNW1IAXGn/6IZPi3YQ==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
"integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
"dev": true,
"optional": true
},
"@esbuild/openbsd-x64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.4.tgz",
"integrity": "sha512-nLgdc6tWEhcCFg/WVFaUxHcPK3AP/bh+KEwKtl69Ay5IBqUwKDaq/6Xk0E+fh/FGjnLwqFSsarsbPHeKM8t8Sw==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
"integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
"dev": true,
"optional": true
},
"@esbuild/sunos-x64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.4.tgz",
"integrity": "sha512-08SluG24GjPO3tXKk95/85n9kpyZtXCVwURR2i4myhrOfi3jspClV0xQQ0W0PYWHioJj+LejFMt41q+PG3mlAQ==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
"integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
"dev": true,
"optional": true
},
"@esbuild/win32-arm64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.4.tgz",
"integrity": "sha512-yYiRDQcqLYQSvNQcBKN7XogbrSvBE45FEQdH8fuXPl7cngzkCvpsG2H9Uey39IjQ6gqqc+Q4VXYHsQcKW0OMjQ==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
"integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
"dev": true,
"optional": true
},
"@esbuild/win32-ia32": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.4.tgz",
"integrity": "sha512-5rabnGIqexekYkh9zXG5waotq8mrdlRoBqAktjx2W3kb0zsI83mdCwrcAeKYirnUaTGztR5TxXcXmQrEzny83w==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
"integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
"dev": true,
"optional": true
},
"@esbuild/win32-x64": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.4.tgz",
"integrity": "sha512-sN/I8FMPtmtT2Yw+Dly8Ur5vQ5a/RmC8hW7jO9PtPSQUPkowxWpcUZnqOggU7VwyT3Xkj6vcXWd3V/qTXwultQ==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
"integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
"dev": true,
"optional": true
},
@@ -17901,11 +17907,11 @@
"dev": true
},
"axios": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz",
"integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==",
"version": "1.6.8",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
"integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
"requires": {
"follow-redirects": "^1.15.0",
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
},
@@ -19562,33 +19568,33 @@
}
},
"esbuild": {
"version": "0.16.4",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.4.tgz",
"integrity": "sha512-qQrPMQpPTWf8jHugLWHoGqZjApyx3OEm76dlTXobHwh/EBbavbRdjXdYi/GWr43GyN0sfpap14GPkb05NH3ROA==",
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
"integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
"dev": true,
"requires": {
"@esbuild/android-arm": "0.16.4",
"@esbuild/android-arm64": "0.16.4",
"@esbuild/android-x64": "0.16.4",
"@esbuild/darwin-arm64": "0.16.4",
"@esbuild/darwin-x64": "0.16.4",
"@esbuild/freebsd-arm64": "0.16.4",
"@esbuild/freebsd-x64": "0.16.4",
"@esbuild/linux-arm": "0.16.4",
"@esbuild/linux-arm64": "0.16.4",
"@esbuild/linux-ia32": "0.16.4",
"@esbuild/linux-loong64": "0.16.4",
"@esbuild/linux-mips64el": "0.16.4",
"@esbuild/linux-ppc64": "0.16.4",
"@esbuild/linux-riscv64": "0.16.4",
"@esbuild/linux-s390x": "0.16.4",
"@esbuild/linux-x64": "0.16.4",
"@esbuild/netbsd-x64": "0.16.4",
"@esbuild/openbsd-x64": "0.16.4",
"@esbuild/sunos-x64": "0.16.4",
"@esbuild/win32-arm64": "0.16.4",
"@esbuild/win32-ia32": "0.16.4",
"@esbuild/win32-x64": "0.16.4"
"@esbuild/android-arm": "0.18.20",
"@esbuild/android-arm64": "0.18.20",
"@esbuild/android-x64": "0.18.20",
"@esbuild/darwin-arm64": "0.18.20",
"@esbuild/darwin-x64": "0.18.20",
"@esbuild/freebsd-arm64": "0.18.20",
"@esbuild/freebsd-x64": "0.18.20",
"@esbuild/linux-arm": "0.18.20",
"@esbuild/linux-arm64": "0.18.20",
"@esbuild/linux-ia32": "0.18.20",
"@esbuild/linux-loong64": "0.18.20",
"@esbuild/linux-mips64el": "0.18.20",
"@esbuild/linux-ppc64": "0.18.20",
"@esbuild/linux-riscv64": "0.18.20",
"@esbuild/linux-s390x": "0.18.20",
"@esbuild/linux-x64": "0.18.20",
"@esbuild/netbsd-x64": "0.18.20",
"@esbuild/openbsd-x64": "0.18.20",
"@esbuild/sunos-x64": "0.18.20",
"@esbuild/win32-arm64": "0.18.20",
"@esbuild/win32-ia32": "0.18.20",
"@esbuild/win32-x64": "0.18.20"
}
},
"esbuild-android-64": {
@@ -20411,9 +20417,9 @@
"dev": true
},
"follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA=="
},
"for-in": {
"version": "1.0.2",
@@ -22353,9 +22359,9 @@
"dev": true
},
"nanoid": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA=="
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g=="
},
"nanomatch": {
"version": "1.2.13",
@@ -22934,11 +22940,11 @@
"dev": true
},
"postcss": {
"version": "8.4.24",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
"integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
"version": "8.4.33",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
"integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
"requires": {
"nanoid": "^3.3.6",
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
@@ -23645,9 +23651,9 @@
"integrity": "sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g=="
},
"rollup": {
"version": "3.7.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.7.3.tgz",
"integrity": "sha512-7e68MQbAWCX6mI4/0lG1WHd+NdNAlVamg0Zkd+8LZ/oXojligdGnCNyHlzXqXCZObyjs5FRc3AH0b17iJESGIQ==",
"version": "3.29.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
"integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
"dev": true,
"requires": {
"fsevents": "~2.3.2"
@@ -25703,16 +25709,15 @@
"requires": {}
},
"vite": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.0.5.tgz",
"integrity": "sha512-7m87RC+caiAxG+8j3jObveRLqaWA/neAdCat6JAZwMkSWqFHOvg8MYe5fAQxVBRAuKAQ1S6XDh3CBQuLNbY33w==",
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
"dev": true,
"requires": {
"esbuild": "^0.16.3",
"esbuild": "^0.18.10",
"fsevents": "~2.3.2",
"postcss": "^8.4.20",
"resolve": "^1.22.1",
"rollup": "^3.7.0"
"postcss": "^8.4.27",
"rollup": "^3.27.1"
}
},
"vite-plugin-monaco-editor": {

View File

@@ -74,7 +74,7 @@
"typescript": "~4.7.4",
"unplugin-auto-import": "^0.7.0",
"unplugin-vue-components": "^0.19.2",
"vite": "^4.0.5",
"vite": "^4.5.3",
"vite-plugin-monaco-editor": "^1.1.0",
"vite-plugin-svg-icons": "^2.0.1",
"vitest": "^0.25.6",

View File

@@ -12,4 +12,4 @@ 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 class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M856.32 428.064a32 32 0 0 0-32 32v163.328H372.48c-0.896 0-1.664 0.448-2.56 0.512v-177.696h244.48a32 32 0 1 0 0-64H130.56c-0.896 0-1.664 0.448-2.56 0.512V231.68h488.16a32 32 0 1 0 0-64H96a32 32 0 0 0-32 32v701.824a32 32 0 0 0 32 32h760.32a32 32 0 0 0 32-32V460.064a32 32 0 0 0-32-32zM128 445.728c0.896 0.064 1.664 0.512 2.56 0.512h175.36v423.264H128V445.728z m241.92 423.776v-182.624c0.896 0.064 1.664 0.512 2.56 0.512h451.84v182.08h-454.4zM960 174.656h-61.376V113.28a32 32 0 1 0-64 0v61.344H752.64a32 32 0 1 0 0 64h81.984v81.984a32 32 0 1 0 64 0V238.656H960a32 32 0 1 0 0-64z" fill="#2c2c2c"></path></svg>
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M856.32 428.064a32 32 0 0 0-32 32v163.328H372.48c-0.896 0-1.664 0.448-2.56 0.512v-177.696h244.48a32 32 0 1 0 0-64H130.56c-0.896 0-1.664 0.448-2.56 0.512V231.68h488.16a32 32 0 1 0 0-64H96a32 32 0 0 0-32 32v701.824a32 32 0 0 0 32 32h760.32a32 32 0 0 0 32-32V460.064a32 32 0 0 0-32-32zM128 445.728c0.896 0.064 1.664 0.512 2.56 0.512h175.36v423.264H128V445.728z m241.92 423.776v-182.624c0.896 0.064 1.664 0.512 2.56 0.512h451.84v182.08h-454.4zM960 174.656h-61.376V113.28a32 32 0 1 0-64 0v61.344H752.64a32 32 0 1 0 0 64h81.984v81.984a32 32 0 1 0 64 0V238.656H960a32 32 0 1 0 0-64z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -13,4 +13,4 @@ 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="1654161407133" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1721" width="16" height="16"><path d="M804.224 86.144c0-19.264-15.616-34.944-34.944-34.944l-734.336 0c-19.264 0-34.944 15.68-34.944 34.944l0 82.048 804.224 0 0-82.048zM738.88 602.432c0 47.072-38.176 85.248-85.248 85.248s-85.248-38.176-85.248-85.248c0-47.072 38.176-85.248 85.248-85.248s85.248 38.176 85.248 85.248zM804.992 264.64l0-62.976-804.224 0 0 665.408c0 18.56 14.656 33.472 32.96 34.56l402.24 0c61.12 44.544 136.192 71.168 217.664 71.168 204.544 0 370.368-165.824 370.368-370.368 0-150.592-89.984-279.936-219.008-337.792zM412.096 298.24l30.528 0c-10.624 7.36-20.8 15.36-30.528 23.744l0-23.744zM63.04 298.24l153.024 0 0 153.024-153.024 0 0-153.024zM216.064 805.056l-153.024 0 0-153.024 153.024 0 0 153.024zM219.136 631.232l-153.024 0 0-153.024 153.024 0 0 153.024zM237.568 805.056l0-153.024 49.408 0c7.488 55.936 27.264 107.904 56.832 153.024l-106.24 0zM284.672 631.232l-44.032 0 0-153.024 64.384 0c-13.824 38.848-21.824 80.576-21.824 124.224 0 9.728 0.768 19.264 1.472 28.8zM390.592 341.76c-31.168 31.424-56.512 68.544-74.88 109.44l-78.144 0 0-152.96 153.024 0 0 43.52zM899.136 638.4l-63.36 12.864c-4.288 16.064-10.688 31.296-18.816 45.376l35.712 53.888-50.944 50.944-53.888-35.712c-14.08 8.128-29.312 14.528-45.376 18.816l-12.864 63.36-72 0-12.864-63.36c-16.064-4.288-31.296-10.688-45.376-18.816l-53.888 35.712-50.944-50.944 35.712-53.888c-8.128-14.08-14.528-29.312-18.816-45.376l-63.36-12.864 0-72 63.36-12.864c4.352-16.064 10.688-31.296 18.816-45.312l-35.712-53.952 50.944-50.944 53.888 35.776c14.08-8.128 29.312-14.464 45.376-18.816l12.864-63.36 72 0 12.864 63.36c16.064 4.288 31.296 10.688 45.376 18.816l53.888-35.712 50.944 50.944-35.712 53.824c8.128 14.08 14.528 29.312 18.816 45.376l63.36 12.864 0 72z" p-id="1722" fill="#707070"></path></svg>
<svg t="1654161407133" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1721" width="16" height="16"><path d="M804.224 86.144c0-19.264-15.616-34.944-34.944-34.944l-734.336 0c-19.264 0-34.944 15.68-34.944 34.944l0 82.048 804.224 0 0-82.048zM738.88 602.432c0 47.072-38.176 85.248-85.248 85.248s-85.248-38.176-85.248-85.248c0-47.072 38.176-85.248 85.248-85.248s85.248 38.176 85.248 85.248zM804.992 264.64l0-62.976-804.224 0 0 665.408c0 18.56 14.656 33.472 32.96 34.56l402.24 0c61.12 44.544 136.192 71.168 217.664 71.168 204.544 0 370.368-165.824 370.368-370.368 0-150.592-89.984-279.936-219.008-337.792zM412.096 298.24l30.528 0c-10.624 7.36-20.8 15.36-30.528 23.744l0-23.744zM63.04 298.24l153.024 0 0 153.024-153.024 0 0-153.024zM216.064 805.056l-153.024 0 0-153.024 153.024 0 0 153.024zM219.136 631.232l-153.024 0 0-153.024 153.024 0 0 153.024zM237.568 805.056l0-153.024 49.408 0c7.488 55.936 27.264 107.904 56.832 153.024l-106.24 0zM284.672 631.232l-44.032 0 0-153.024 64.384 0c-13.824 38.848-21.824 80.576-21.824 124.224 0 9.728 0.768 19.264 1.472 28.8zM390.592 341.76c-31.168 31.424-56.512 68.544-74.88 109.44l-78.144 0 0-152.96 153.024 0 0 43.52zM899.136 638.4l-63.36 12.864c-4.288 16.064-10.688 31.296-18.816 45.376l35.712 53.888-50.944 50.944-53.888-35.712c-14.08 8.128-29.312 14.528-45.376 18.816l-12.864 63.36-72 0-12.864-63.36c-16.064-4.288-31.296-10.688-45.376-18.816l-53.888 35.712-50.944-50.944 35.712-53.888c-8.128-14.08-14.528-29.312-18.816-45.376l-63.36-12.864 0-72 63.36-12.864c4.352-16.064 10.688-31.296 18.816-45.312l-35.712-53.952 50.944-50.944 53.888 35.776c14.08-8.128 29.312-14.464 45.376-18.816l12.864-63.36 72 0 12.864 63.36c16.064 4.288 31.296 10.688 45.376 18.816l53.888-35.712 50.944 50.944-35.712 53.824c8.128 14.08 14.528 29.312 18.816 45.376l63.36 12.864 0 72z" p-id="1722"></path></svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -12,4 +12,4 @@ 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="1655695739627" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2218" width="48" height="48"><path d="M173.292308 177.230769C86.646154 265.846154 39.384615 382.030769 39.384615 504.123077 39.384615 531.692308 61.046154 551.384615 86.646154 551.384615s47.261538-21.661538 47.261538-47.261538c-1.969231-96.492308 37.415385-189.046154 106.338462-257.969231s163.446154-106.338462 257.969231-106.338461c27.569231 0 47.261538-21.661538 47.261538-47.261539 0-27.569231-21.661538-47.261538-47.261538-47.261538C378.092308 43.323077 259.938462 90.584615 173.292308 177.230769z m57.107692 326.892308c0 27.569231 19.692308 47.261538 47.261538 47.261538s47.261538-21.661538 47.261539-47.261538c0-45.292308 17.723077-90.584615 51.2-122.092308 33.476923-33.476923 76.8-49.230769 122.092308-51.2 27.569231 0 47.261538-21.661538 47.261538-47.261538 0-27.569231-19.692308-47.261538-47.261538-47.261539-70.892308 0-139.815385 27.569231-191.015385 76.8-7.876923 9.846154-80.738462 82.707692-76.8 191.015385z m665.6-204.8c-17.723077-23.630769-41.353846-51.2-45.292308-55.138462-5.907692-3.938462-13.784615-7.876923-21.661538-7.876923-7.876923 0-15.753846 1.969231-19.692308 7.876923L610.461538 441.107692c-47.261538-39.384615-118.153846-37.415385-163.446153 7.876923-45.292308 45.292308-47.261538 116.184615-7.876923 163.446154l-191.015385 191.015385c-5.907692 5.907692-9.846154 13.784615-9.846154 21.661538 0 9.846154 3.938462 19.692308 11.815385 25.6l53.16923 39.384616c72.861538 57.107692 163.446154 88.615385 259.938462 88.615384 232.369231 0 421.415385-189.046154 421.415385-421.415384 0-94.523077-33.476923-185.107692-88.615385-257.969231z" p-id="2219" fill="#707070"></path></svg>
<svg t="1655695739627" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2218" width="48" height="48"><path d="M173.292308 177.230769C86.646154 265.846154 39.384615 382.030769 39.384615 504.123077 39.384615 531.692308 61.046154 551.384615 86.646154 551.384615s47.261538-21.661538 47.261538-47.261538c-1.969231-96.492308 37.415385-189.046154 106.338462-257.969231s163.446154-106.338462 257.969231-106.338461c27.569231 0 47.261538-21.661538 47.261538-47.261539 0-27.569231-21.661538-47.261538-47.261538-47.261538C378.092308 43.323077 259.938462 90.584615 173.292308 177.230769z m57.107692 326.892308c0 27.569231 19.692308 47.261538 47.261538 47.261538s47.261538-21.661538 47.261539-47.261538c0-45.292308 17.723077-90.584615 51.2-122.092308 33.476923-33.476923 76.8-49.230769 122.092308-51.2 27.569231 0 47.261538-21.661538 47.261538-47.261538 0-27.569231-19.692308-47.261538-47.261538-47.261539-70.892308 0-139.815385 27.569231-191.015385 76.8-7.876923 9.846154-80.738462 82.707692-76.8 191.015385z m665.6-204.8c-17.723077-23.630769-41.353846-51.2-45.292308-55.138462-5.907692-3.938462-13.784615-7.876923-21.661538-7.876923-7.876923 0-15.753846 1.969231-19.692308 7.876923L610.461538 441.107692c-47.261538-39.384615-118.153846-37.415385-163.446153 7.876923-45.292308 45.292308-47.261538 116.184615-7.876923 163.446154l-191.015385 191.015385c-5.907692 5.907692-9.846154 13.784615-9.846154 21.661538 0 9.846154 3.938462 19.692308 11.815385 25.6l53.16923 39.384616c72.861538 57.107692 163.446154 88.615385 259.938462 88.615384 232.369231 0 421.415385-189.046154 421.415385-421.415384 0-94.523077-33.476923-185.107692-88.615385-257.969231z" p-id="2219"></path></svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,18 @@
<!-- 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="1704964118567" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5167">
<path d="M900.032 646.016h-56.064V502.976a16 16 0 0 0-16-16H544v-96h62.976c22.144 0 40-17.92 40-40V161.024a40 40 0 0 0-40-40H417.024a40 40 0 0 0-40 40v189.952c0 22.144 17.92 40 40 40H480v96H195.968a16 16 0 0 0-16 16v143.04h-55.936a38.016 38.016 0 0 0-38.016 38.016v176c0 20.928 17.024 37.952 37.952 37.952h176a38.016 38.016 0 0 0 38.016-38.016v-176a38.016 38.016 0 0 0-37.952-37.952h-56V550.976H480v95.04h-56a38.016 38.016 0 0 0-38.016 38.016v176c0 20.928 17.024 37.952 38.016 37.952h176a38.016 38.016 0 0 0 38.016-38.016v-176a38.016 38.016 0 0 0-38.016-37.952H544V550.976h236.032v95.04h-56.064a38.016 38.016 0 0 0-37.952 38.016v176c0 20.928 17.024 37.952 38.016 37.952h176a38.016 38.016 0 0 0 37.952-38.016v-176a38.016 38.016 0 0 0-38.016-37.952zM440.96 184.96h141.952v141.952H441.024V185.024zM278.016 838.016H145.92V705.92h132.032v132.032z m299.968 0H446.08V705.92H577.92v132.032z m300.032 0h-132.032V705.92h132.032v132.032z" p-id="5168"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

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. -->
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400.000000 400.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,400.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1737 3725 c-379 -61 -696 -224 -968 -495 -249 -248 -403 -533 -481
-890 -20 -93 -22 -133 -22 -340 0 -207 2 -247 22 -339 48 -217 123 -407 231
-581 253 -406 658 -688 1141 -791 91 -20 135 -23 330 -24 172 0 246 4 315 17
361 69 669 231 926 487 271 272 438 604 494 983 23 154 16 448 -15 593 -147
694 -668 1214 -1366 1365 -130 29 -472 37 -607 15z m461 -131 c24 -3 52 -14
63 -25 23 -23 25 -76 3 -100 -15 -17 -15 -19 0 -34 25 -26 20 -82 -10 -112
-25 -25 -28 -25 -112 -19 -48 4 -88 8 -89 10 -1 1 4 60 12 131 8 72 15 137 15
145 0 17 14 17 118 4z m-359 -30 c30 -22 61 -89 61 -137 0 -48 -22 -102 -48
-116 -23 -12 -163 -43 -169 -38 -7 7 -53 280 -48 285 8 9 78 20 130 21 32 1
59 -5 74 -15z m564 -8 c7 -8 22 -44 31 -80 l18 -66 62 48 c48 38 66 47 85 43
23 -6 18 -12 -58 -71 -76 -61 -83 -69 -98 -122 -12 -43 -21 -58 -34 -58 -28 0
-30 14 -10 73 l19 56 -29 93 c-16 51 -29 94 -29 96 0 8 31 0 43 -12z m-858
-45 c0 -8 -30 -27 -69 -43 -38 -15 -71 -30 -74 -32 -4 -4 17 -76 22 -76 1 0
31 11 66 25 76 30 90 31 90 7 0 -13 -19 -24 -70 -42 -61 -21 -69 -27 -64 -45
15 -46 24 -65 32 -65 5 0 42 14 82 30 57 23 75 27 82 18 4 -7 8 -16 8 -19 0
-7 -180 -79 -197 -79 -6 0 -28 44 -49 98 -20 53 -43 112 -51 131 -7 19 -11 36
-9 38 10 11 173 71 186 70 8 -1 15 -9 15 -16z m-257 -157 c26 -29 28 -61 6
-92 -12 -17 -12 -22 -2 -22 7 0 42 -13 77 -29 62 -28 63 -29 41 -44 -21 -15
-26 -14 -59 2 -20 10 -53 24 -73 30 -37 12 -37 13 -96 -25 -2 -1 13 -27 32
-58 34 -52 35 -56 17 -65 -17 -10 -27 2 -92 101 -39 62 -74 119 -76 128 -2 9
14 26 44 44 119 73 138 76 181 30z m-292 -145 c3 -6 -19 -37 -52 -70 l-58 -59
29 -30 29 -30 56 55 c48 47 59 54 69 42 10 -12 2 -24 -44 -68 l-56 -52 32 -33
32 -33 64 59 c52 47 68 57 78 47 11 -10 -1 -27 -66 -92 l-80 -80 -99 105 c-55
58 -100 108 -100 111 0 10 139 139 150 139 5 0 12 -5 16 -11z m1197 -44 c494
-74 896 -467 982 -959 19 -113 19 -299 0 -410 -73 -416 -377 -773 -776 -910
-142 -48 -213 -59 -384 -59 -175 -1 -241 10 -400 65 -383 131 -680 481 -761
893 -22 115 -20 335 5 450 79 372 314 669 656 831 214 102 441 135 678 99z
m-1319 -258 c81 -91 95 -110 86 -126 -9 -18 -14 -18 -122 16 -62 19 -117 38
-123 41 -10 6 17 -27 109 -133 41 -46 48 -60 39 -73 -13 -20 0 -23 -160 28
-89 28 -133 47 -133 56 0 28 27 25 134 -15 73 -26 103 -34 94 -23 -7 9 -43 50
-80 91 -55 61 -65 78 -57 93 13 25 30 23 147 -15 l103 -33 -82 82 c-67 67 -80
85 -74 103 4 12 10 21 14 19 4 -2 51 -51 105 -111z m2502 -151 c38 -38 44 -77
19 -126 -19 -37 -43 -50 -95 -50 -61 0 -100 41 -100 105 0 40 5 52 31 76 46
42 99 41 145 -5z m-2677 -171 c16 -8 41 -29 56 -47 24 -29 27 -38 23 -91 -4
-54 -8 -63 -41 -93 -32 -30 -44 -34 -90 -34 -126 0 -200 102 -150 207 9 20 30
44 47 54 38 23 115 25 155 4z m-145 -306 c35 -16 50 -59 42 -124 l-6 -54 32
-5 c18 -3 44 -7 58 -8 19 -2 24 -8 22 -23 -2 -11 -5 -21 -6 -22 -5 -6 -272 30
-280 37 -5 5 -4 42 3 87 9 63 16 82 37 101 28 24 60 28 98 11z m2717 -590 c14
-7 18 -16 14 -27 -4 -9 -20 -52 -36 -95 l-29 -78 33 -13 c77 -32 72 -34 105
51 29 75 55 102 67 70 3 -7 -7 -48 -24 -90 l-29 -76 55 -21 c30 -11 58 -18 62
-15 5 2 23 45 41 95 31 87 41 99 68 82 10 -7 2 -36 -32 -126 -25 -65 -47 -120
-49 -123 -3 -2 -82 26 -177 62 -95 36 -175 65 -177 65 -9 0 1 33 39 137 24 62
44 113 46 113 2 0 12 -5 23 -11z m34 -411 c80 -57 145 -106 145 -109 0 -3 -6
-14 -14 -24 -13 -18 -17 -16 -79 28 -36 26 -69 47 -74 47 -10 0 -113 -145
-113 -158 0 -7 61 -53 122 -93 14 -9 -11 -52 -26 -46 -10 4 -288 198 -304 212
-6 5 18 45 26 45 5 0 36 -21 71 -46 34 -25 64 -44 65 -42 1 2 25 35 52 73 28
39 53 75 57 81 4 7 -18 29 -58 55 -35 24 -65 49 -65 55 0 16 23 35 38 29 7 -2
78 -51 157 -107z m-2350 -142 c35 -15 65 -59 65 -96 0 -54 -57 -110 -112 -110
-13 0 -37 9 -55 20 -69 41 -63 154 9 185 42 18 51 18 93 1z m2003 -75 c8 -5
12 -17 10 -27 -3 -14 -11 -18 -33 -17 -82 7 -145 -48 -145 -126 0 -89 73 -161
162 -161 43 0 54 4 84 34 27 27 34 42 34 74 0 32 4 41 22 45 29 8 38 1 38 -31
-1 -38 -32 -102 -66 -133 -75 -69 -196 -57 -276 28 -27 28 -58 99 -58 132 0
65 56 152 115 177 34 15 94 18 113 5z m-247 -340 c79 -74 145 -138 147 -142 2
-4 -7 -13 -19 -19 -19 -11 -26 -7 -65 31 l-43 42 -68 -31 -68 -32 -3 -64 c-2
-55 -6 -66 -23 -71 -11 -4 -23 -5 -25 -2 -3 2 -9 94 -14 203 -8 180 -7 200 8
212 9 7 19 11 23 10 4 -2 71 -63 150 -137z m-321 34 c58 -30 78 -120 40 -182
-19 -31 -48 -44 -151 -67 l-26 -6 14 -60 c15 -68 12 -80 -20 -80 -20 0 -24 12
-56 173 -19 94 -36 179 -38 188 -4 12 11 19 64 31 90 20 138 21 173 3z m-334
-207 c38 -105 71 -195 72 -200 2 -4 -10 -8 -26 -8 -28 0 -32 5 -49 58 l-20 57
-79 3 -79 3 -27 -54 c-24 -49 -29 -54 -53 -50 -14 3 -25 9 -23 13 2 3 41 88
87 188 112 243 106 244 197 -10z"/>
<path d="M2120 3537 c0 -14 -2 -32 -6 -40 -4 -11 6 -16 43 -20 54 -6 83 5 83
32 0 27 -24 43 -75 49 -43 4 -45 3 -45 -21z"/>
<path d="M2105 3399 c-4 -22 -5 -42 -2 -45 9 -10 77 -16 101 -10 52 13 44 70
-12 86 -68 19 -79 14 -87 -31z"/>
<path d="M1697 3533 c-13 -3 -17 -11 -14 -26 3 -12 11 -58 18 -102 7 -44 14
-81 15 -83 7 -10 96 13 113 29 39 36 21 165 -24 183 -18 7 -80 6 -108 -1z"/>
<path d="M1156 3310 l-47 -30 22 -33 c12 -17 24 -34 25 -36 6 -7 94 50 103 67
5 10 5 27 0 40 -12 31 -45 28 -103 -8z"/>
<path d="M1060 2137 c-20 -10 -25 -20 -25 -52 0 -37 4 -42 53 -75 79 -52 68
-97 -21 -88 -23 2 -32 -1 -32 -12 0 -11 14 -16 57 -18 48 -2 60 0 77 19 40 43
27 78 -46 125 -71 45 -70 68 5 72 35 2 52 7 52 16 0 26 -78 34 -120 13z"/>
<path d="M1230 2020 c0 -123 1 -130 20 -130 17 0 20 7 20 43 l1 42 20 -25 c12
-14 27 -33 34 -43 8 -11 22 -17 35 -15 21 3 20 4 -9 43 -38 50 -38 61 0 105
29 34 29 35 8 38 -12 2 -26 -4 -34 -15 -7 -10 -22 -29 -34 -43 l-20 -25 -1 78
c0 70 -2 77 -20 77 -19 0 -20 -7 -20 -130z"/>
<path d="M2030 1976 c0 -130 1 -136 20 -136 20 0 20 5 18 132 -3 117 -5 133
-20 136 -16 3 -18 -8 -18 -132z"/>
<path d="M2133 2103 c-10 -3 -13 -40 -13 -134 0 -121 1 -129 19 -129 16 0 20
8 23 43 l3 42 30 -42 c20 -28 38 -42 53 -43 12 0 22 3 22 6 0 3 -16 26 -35 51
l-35 46 35 43 c40 48 42 54 18 54 -10 0 -35 -19 -55 -42 l-37 -43 -1 78 c0 75
-2 81 -27 70z"/>
<path d="M2310 2090 c0 -13 7 -20 20 -20 13 0 20 7 20 20 0 13 -7 20 -20 20
-13 0 -20 -7 -20 -20z"/>
<path d="M1410 2073 c0 -5 7 -30 15 -58 9 -27 20 -67 26 -87 5 -22 16 -38 24
-38 8 0 15 -4 15 -8 0 -17 -25 -32 -52 -32 -18 0 -28 -5 -28 -15 0 -22 72 -20
92 3 9 9 28 60 42 112 15 52 29 103 32 113 5 15 1 18 -17 15 -17 -2 -25 -12
-32 -38 -23 -94 -29 -110 -36 -106 -5 3 -11 20 -15 38 -17 85 -27 108 -46 108
-11 0 -20 -3 -20 -7z"/>
<path d="M1766 2031 c-3 -4 -8 -35 -12 -67 -8 -75 -19 -96 -28 -58 -25 105
-26 105 -53 102 -23 -3 -29 -10 -41 -58 -15 -63 -32 -88 -32 -48 0 14 -3 40
-7 59 -5 28 -7 30 -14 14 -13 -35 -10 -112 6 -125 29 -24 49 -7 67 55 9 33 17
62 18 65 1 3 11 -24 23 -60 19 -57 25 -65 47 -65 22 0 27 7 37 50 7 28 16 71
19 98 5 39 4 47 -9 47 -9 0 -18 -4 -21 -9z"/>
<path d="M1853 2033 c-7 -2 -13 -12 -13 -20 0 -12 7 -14 31 -9 34 7 69 -8 69
-28 0 -8 -17 -16 -42 -20 -53 -9 -68 -21 -68 -57 0 -43 21 -57 90 -56 l60 0 0
68 c0 103 -19 130 -90 128 -14 0 -31 -3 -37 -6z m87 -128 c0 -18 -7 -26 -24
-31 -30 -7 -46 1 -46 25 0 24 9 31 42 31 23 0 28 -4 28 -25z"/>
<path d="M2317 2033 c-4 -3 -7 -48 -7 -100 0 -86 1 -93 20 -93 19 0 20 7 20
100 0 77 -3 100 -13 100 -8 0 -17 -3 -20 -7z"/>
<path d="M2400 1938 c0 -91 1 -98 20 -98 17 0 19 8 22 78 l3 77 35 0 35 0 3
-77 c3 -70 5 -78 22 -78 18 0 20 7 20 78 0 106 -9 117 -94 118 l-66 1 0 -99z"/>
<path d="M2642 2025 l-33 -14 4 -73 c2 -40 -1 -84 -7 -97 -9 -20 -6 -27 17
-47 51 -43 147 -23 147 30 0 31 -16 43 -78 57 -64 15 -67 29 -7 29 58 0 90 28
81 71 -3 15 -1 31 4 34 39 24 -74 32 -128 10z m74 -29 c27 -20 7 -51 -32 -51
-23 0 -30 5 -32 24 -6 38 29 52 64 27z m-8 -151 c14 -4 22 -13 20 -23 -4 -22
-60 -28 -77 -9 -15 19 -3 49 18 43 9 -2 26 -7 39 -11z"/>
<path d="M549 2531 c-50 -50 -35 -120 33 -151 52 -24 80 -25 117 -5 72 37 56
137 -26 171 -52 22 -91 18 -124 -15z"/>
<path d="M471 2222 c-10 -19 -18 -99 -11 -105 3 -2 24 -7 47 -10 l42 -5 7 54
c4 37 2 59 -6 69 -17 21 -67 19 -79 -3z"/>
<path d="M2571 750 c0 -25 4 -66 7 -91 l7 -46 57 27 57 27 -65 64 -64 63 1
-44z"/>
<path d="M2247 708 c-25 -7 -36 -15 -33 -23 2 -7 10 -41 17 -74 l12 -62 31 6
c84 17 103 26 114 50 17 37 15 59 -9 89 -23 29 -61 33 -132 14z"/>
<path d="M1927 590 l-36 -75 60 -9 c33 -5 62 -7 64 -4 4 4 -4 28 -47 148 -4 9
-19 -14 -41 -60z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -0,0 +1,17 @@
<!-- 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="1712402256302" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1578">
<path d="M640 885.589333a39.424 39.424 0 0 1-32.938667 46.634667A483.413333 483.413333 0 0 1 512 938.666667a295.68 295.68 0 0 1-92.117333-16.768 32.085333 32.085333 0 1 1 13.568-62.72 415.658667 415.658667 0 0 0 78.549333 15.36 295.722667 295.722667 0 0 0 75.434667-4.266667c20.181333-9.514667 47.317333-9.216 52.565333 15.36z m-397.824-307.968a160.597333 160.597333 0 0 1 156.842667 164.096 198.314667 198.314667 0 0 1-6.954667 48.426667 154.325333 154.325333 0 0 1-149.930667 115.712 164.266667 164.266667 0 0 1 0-328.192z m539.605333 0a164.266667 164.266667 0 1 1-156.842666 164.096 160.597333 160.597333 0 0 1 156.885333-164.096z m-30.122666-262.058666A344.917333 344.917333 0 0 1 853.333333 497.024c1.109333 16.768-1.749333 38.826667-30.08 41.258667a32.554667 32.554667 0 0 1-33.493333-26.325334 334.848 334.848 0 0 0-80.341333-146.304 34.517333 34.517333 0 0 1-4.992-54.613333 33.408 33.408 0 0 1 47.232 4.693333z m-417.706667-4.48c13.269333 16.64 4.821333 28.501333-8.789333 48.512A422.4 422.4 0 0 0 256 517.802667a33.152 33.152 0 0 1-36.821333 26.709333 31.573333 31.573333 0 0 1-27.52-32.512 89.6 89.6 0 0 1 3.797333-29.226667 402.773333 402.773333 0 0 1 93.312-177.536 30.592 30.592 0 0 1 45.184 5.845334zM512 85.333333a164.266667 164.266667 0 1 1-156.842667 164.096A160.597333 160.597333 0 0 1 512 85.333333z" p-id="1579"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
src/assets/img/technologies/ACTIVEMQ.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

BIN
src/assets/img/tools/MQ.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

View File

@@ -451,7 +451,7 @@ limitations under the License. -->
}
.calendar + .calendar {
border-left: solid 1px #eaeaea;
border-left: solid 1px var(--sw-border-color-light);
margin-left: 5px;
padding-left: 5px;
}
@@ -464,7 +464,7 @@ limitations under the License. -->
}
.calendar-head a {
color: #666;
color: var(--sw-topology-color);
cursor: pointer;
display: inline-block;
text-align: center;
@@ -568,7 +568,7 @@ limitations under the License. -->
.calendar-hour {
display: inline-block;
border: 1px solid #e6e5e5;
border: 1px solid var(--sw-border-color-light);
color: #9e9e9e;
}

View File

@@ -53,6 +53,7 @@ limitations under the License. -->
import { addResizeListener, removeResizeListener } from "@/utils/event";
import Trace from "@/views/dashboard/related/trace/Index.vue";
import associateProcessor from "@/hooks/useAssociateProcessor";
import { WidgetType } from "@/views/dashboard/data";
/*global Nullable, defineProps, defineEmits, Indexable*/
const emits = defineEmits(["select"]);
@@ -63,7 +64,7 @@ limitations under the License. -->
const currentParams = ref<Nullable<EventParams>>(null);
const showTrace = ref<boolean>(false);
const traceOptions = ref<{ type: string; filters?: unknown }>({
type: "Trace",
type: WidgetType.Trace,
});
const menuPos = reactive<{ x: number; y: number }>({ x: NaN, y: NaN });
const props = defineProps({
@@ -234,12 +235,10 @@ limitations under the License. -->
.no-data {
font-size: $font-size-smaller;
height: 100%;
box-sizing: border-box;
display: -webkit-box;
-webkit-box-orient: horizontal;
-webkit-box-pack: center;
-webkit-box-align: center;
color: #666;
align-items: center;
justify-content: center;
display: flex;
color: var(--text-color-placeholder);
}
.chart {

View File

@@ -85,7 +85,7 @@ limitations under the License. -->
.bar-select {
position: relative;
justify-content: space-between;
border: 1px solid #ddd;
border: 1px solid var(--el-border-color);
background: $theme-background;
border-radius: 3px;
color: $font-color;
@@ -97,8 +97,8 @@ limitations under the License. -->
border-radius: 3px;
margin: 3px;
color: $active-color;
background-color: #fafafa;
border: 1px solid #e8e8e8;
background-color: var(--theme-background);
border: 1px solid var(--el-color-primary);
text-align: center;
}
}
@@ -139,7 +139,7 @@ limitations under the License. -->
left: 0;
background-color: $theme-background;
box-shadow: 0 1px 6px rgb(99 99 99 / 20%);
border: 1px solid #ddd;
border: 1px solid var(--el-border-color);
width: 100%;
border-radius: 0 0 3px 3px;
border-right-width: 1px !important;
@@ -169,7 +169,7 @@ limitations under the License. -->
}
&:hover {
background-color: #f5f5f5;
background-color: var(--layout-background);
}
}
</style>

View File

@@ -31,7 +31,7 @@ limitations under the License. -->
</template>
<script lang="ts" setup>
import { nextTick, ref } from "vue";
import { nextTick, ref, watch } from "vue";
import type { PropType } from "vue";
import { ElInput } from "element-plus";
@@ -69,10 +69,17 @@ limitations under the License. -->
inputValue.value = "";
emits("change", dynamicTags.value);
};
watch(
() => props.tags,
() => {
dynamicTags.value = props.tags || [];
},
);
</script>
<style lang="scss" scoped>
.input-name {
width: 300px;
width: 250px;
}
.vertical {

View File

@@ -447,7 +447,7 @@ limitations under the License. -->
width: 100px;
height: 100%;
padding: 5px;
border-right: solid 1px #eaeaea;
border-right: solid 1px var(--sw-border-color-light);
}
&__shortcut {
@@ -457,7 +457,7 @@ limitations under the License. -->
background-color: transparent;
line-height: 34px;
font-size: $font-size-smaller;
color: #666;
color: var(--sw-topology-color);
text-align: left;
outline: none;
cursor: pointer;
@@ -532,6 +532,6 @@ limitations under the License. -->
}
.datepicker__buttons .datepicker__button-cancel {
background: #666;
background: var(--sw-topology-color);
}
</style>

View File

@@ -23,6 +23,7 @@ export const ServicesTopology = {
name
type
isReal
layers
}
calls {
id
@@ -99,3 +100,56 @@ export const ProcessTopology = {
}
`,
};
export const HierarchyServiceTopology = {
variable: "$serviceId: ID!, $layer: String!",
query: `
hierarchyServiceTopology: getServiceHierarchy(serviceId: $serviceId, layer: $layer) {
relations {
upperService {
id
name
layer
normal
}
lowerService {
id
name
layer
normal
}
}
}`,
};
export const HierarchyInstanceTopology = {
variable: "$instanceId: ID!, $layer: String!",
query: `
hierarchyInstanceTopology: getInstanceHierarchy(instanceId: $instanceId, layer: $layer) {
relations {
upperInstance {
id
name
layer
normal
serviceName
serviceId
}
lowerInstance {
id
name
layer
normal
serviceName
serviceId
}
}
}`,
};
export const ListLayerLevels = {
query: `
levels: listLayerLevels {
layer
level
}
`,
};

View File

@@ -14,9 +14,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { InstanceTopology, EndpointTopology, ServicesTopology, ProcessTopology } from "../fragments/topology";
import {
InstanceTopology,
EndpointTopology,
ServicesTopology,
ProcessTopology,
HierarchyServiceTopology,
HierarchyInstanceTopology,
ListLayerLevels,
} from "../fragments/topology";
export const getInstanceTopology = `query queryData(${InstanceTopology.variable}) {${InstanceTopology.query}}`;
export const getEndpointTopology = `query queryData(${EndpointTopology.variable}) {${EndpointTopology.query}}`;
export const getServicesTopology = `query queryData(${ServicesTopology.variable}) {${ServicesTopology.query}}`;
export const getProcessTopology = `query queryData(${ProcessTopology.variable}) {${ProcessTopology.query}}`;
export const getHierarchyInstanceTopology = `query queryData(${HierarchyInstanceTopology.variable}) {${HierarchyInstanceTopology.query}}`;
export const getHierarchyServiceTopology = `query queryData(${HierarchyServiceTopology.variable}) {${HierarchyServiceTopology.query}}`;
export const queryListLayerLevels = `query queryLayerLevels {${ListLayerLevels.query}}`;

View File

@@ -14,32 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export enum MetricQueryTypes {
ReadMetricsValue = "readMetricsValue",
ReadMetricsValues = "readMetricsValues",
SortMetrics = "sortMetrics",
ReadLabeledMetricsValues = "readLabeledMetricsValues",
READHEATMAP = "readHeatMap",
ReadSampledRecords = "readSampledRecords",
ReadRecords = "readRecords",
ReadNullableMetricsValue = "readNullableMetricsValue",
}
export enum Calculations {
Percentage = "percentage",
ByteToKB = "byteToKB",
ByteToMB = "byteToMB",
ByteToGB = "byteToGB",
Apdex = "apdex",
ConvertSeconds = "convertSeconds",
ConvertMilliseconds = "convertMilliseconds",
MsToS = "msTos",
Average = "average",
PercentageAvg = "percentageAvg",
ApdexAvg = "apdexAvg",
SecondToDay = "secondToDay",
NanosecondToMillisecond = "nanosecondToMillisecond",
}
export enum sizeEnum {
XS = "XS",
SM = "SM",
@@ -68,50 +43,6 @@ screenMap.set(sizeEnum.XL, screenEnum.XL);
screenMap.set(sizeEnum.XXL, screenEnum.XXL);
export const RespFields: Indexable = {
readMetricsValues: `{
label
values {
values {value isEmptyValue}
}
}`,
readMetricsValue: ``,
readNullableMetricsValue: `{
value
isEmptyValue
}`,
sortMetrics: `{
name
id
value
refId
}`,
readLabeledMetricsValues: `{
label
values {
values {value isEmptyValue}
}
}`,
readHeatMap: `{
values {
id
values
}
buckets {
min
max
}
}`,
readSampledRecords: `{
name
value
refId
}`,
readRecords: `{
id
name
value
refId
}`,
execExpression: `{
type
results {

View File

@@ -17,15 +17,25 @@
import { ElMessage } from "element-plus";
import { useDashboardStore } from "@/store/modules/dashboard";
import type { LayoutConfig } from "@/types/dashboard";
import { ConfigFieldTypes } from "@/views/dashboard/data";
export default function getDashboard(param?: { name: string; layer: string; entity: string }) {
export default function getDashboard(param?: { name?: string; layer: string; entity: string }, t?: string) {
const type = t || ConfigFieldTypes.NAME; // "NAME" or "ISDEFAULT"
const dashboardStore = useDashboardStore();
const opt = param || dashboardStore.currentDashboard;
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
const dashboard = list.find(
let dashboard: Recordable;
if (type === ConfigFieldTypes.NAME) {
dashboard = list.find(
(d: { name: string; layer: string; entity: string }) =>
d.name === opt.name && d.entity === opt.entity && d.layer === opt.layer,
);
} else {
dashboard = list.find(
(d: { name: string; layer: string; entity: string; isDefault: boolean }) =>
d.isDefault && d.entity === opt.entity && d.layer === opt.layer,
);
}
const all = dashboardStore.layout;
const widgets: LayoutConfig[] = [];
for (const item of all) {
@@ -52,6 +62,9 @@ export default function getDashboard(param?: { name: string; layer: string; enti
filters,
};
dashboardStore.setWidget(item);
if (widget.id === sourceId) {
return;
}
const targetTabIndex = (widget.id || "").split("-");
const sourceTabindex = (sourceId || "").split("-") || [];
let container: Nullable<Element>;

View File

@@ -112,27 +112,19 @@ export async function useExpressionsQueryProcessor(config: Indexable) {
tips.push(obj.error);
typesOfMQE.push(type);
if (!obj.error) {
if (type === ExpressionResultType.TIME_SERIES_VALUES) {
if (results.length === 1) {
const label = results[0].metric && results[0].metric.labels[0] && results[0].metric.labels[0].value;
source[c.label || label || name] = results[0].values.map((d: { value: unknown }) => d.value) || [];
} else {
const labels = (c.label || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
if ([ExpressionResultType.SINGLE_VALUE, ExpressionResultType.TIME_SERIES_VALUES].includes(type)) {
for (const item of results) {
const label =
item.metric &&
item.metric.labels.map((d: { key: string; value: string }) => `${d.key}=${d.value}`).join(",");
const values = item.values.map((d: { value: unknown }) => d.value) || [];
const index = item.metric.labels[0].value;
const indexNum = labels.findIndex((_, i: number) => i === Number(index));
if (labels[indexNum] && indexNum > -1) {
source[labels[indexNum]] = values;
if (results.length === 1) {
source[label || c.label || name] = values;
} else {
source[index] = values;
source[label] = values;
}
}
}
}
if (type === ExpressionResultType.SINGLE_VALUE) {
source[c.label || name] = (results[0].values[0] || {}).value;
}
if (([ExpressionResultType.RECORD_LIST, ExpressionResultType.SORTED_LIST] as string[]).includes(type)) {
source[name] = results[0].values;
}
@@ -141,6 +133,7 @@ export async function useExpressionsQueryProcessor(config: Indexable) {
return { source, tips, typesOfMQE };
}
const params = await expressionsGraphqlPods();
if (!params) {
return { source: {}, tips: [], typesOfMQE: [] };
@@ -152,9 +145,14 @@ export async function useExpressionsQueryProcessor(config: Indexable) {
ElMessage.error(json.errors);
return { source: {}, tips: [], typesOfMQE: [] };
}
try {
const data = expressionsSource(json);
return data;
} catch (error) {
console.error(error);
return { source: {}, tips: [], typesOfMQE: [] };
}
}
export async function useExpressionsQueryPodsMetrics(
@@ -301,6 +299,7 @@ export async function useExpressionsQueryPodsMetrics(
return { data, names, subNames, metricConfigArr, metricTypesArr, expressionsTips, subExpressionsTips };
}
const dashboardStore = useDashboardStore();
const params = await expressionsGraphqlPods();
const json = await dashboardStore.fetchMetricValue(params);
@@ -330,10 +329,14 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
let serviceInstanceName;
let destServiceInstanceName;
let destEndpointName;
let normal = false;
let destNormal;
if (d.sourceObj && d.targetObj) {
// instances = Calls
serviceName = d.sourceObj.serviceName || d.sourceObj.name;
destServiceName = d.targetObj.serviceName || d.targetObj.name;
normal = d.sourceObj.normal || d.sourceObj.isReal || false;
destNormal = d.targetObj.normal || d.targetObj.isReal || false;
if (EntityType[4].value === dashboardStore.entity) {
serviceInstanceName = d.sourceObj.name;
destServiceInstanceName = d.targetObj.name;
@@ -345,6 +348,10 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
} else {
// instances = Nodes
serviceName = d.serviceName || d.name;
normal = d.normal || d.isReal || false;
if (EntityType[3].value === dashboardStore.entity) {
serviceInstanceName = d.name;
}
if (EntityType[4].value === dashboardStore.entity) {
serviceInstanceName = d.name;
}
@@ -354,11 +361,11 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
}
const entity = {
serviceName,
normal: true,
normal,
serviceInstanceName,
endpointName,
destServiceName,
destNormal: destServiceName ? true : undefined,
destNormal: destServiceName ? destNormal : undefined,
destServiceInstanceName,
destEndpointName,
};

View File

@@ -1,41 +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 { MetricQueryTypes, Calculations } from "./data";
import { MetricModes } from "@/views/dashboard/data";
export function useListConfig(config: Indexable, index: number) {
if (config.metricModes === MetricModes.Expression) {
return {
isLinear: false,
isAvg: true,
};
}
const i = Number(index);
const types = [Calculations.Average, Calculations.ApdexAvg, Calculations.PercentageAvg];
const calculation = config.metricConfig && config.metricConfig[i] && config.metricConfig[i].calculation;
const isLinear =
[MetricQueryTypes.ReadMetricsValues, MetricQueryTypes.ReadLabeledMetricsValues].includes(config.metricTypes[i]) &&
!types.includes(calculation);
const isAvg =
[MetricQueryTypes.ReadMetricsValues, MetricQueryTypes.ReadLabeledMetricsValues].includes(config.metricTypes[i]) &&
types.includes(calculation);
return {
isLinear,
isAvg,
};
}

View File

@@ -1,437 +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 dayjs from "dayjs";
import { RespFields, MetricQueryTypes, Calculations } from "./data";
import { ElMessage } from "element-plus";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app";
import type { Instance, Endpoint, Service } from "@/types/selector";
import type { MetricConfigOpt } from "@/types/dashboard";
export function useQueryProcessor(config: Indexable) {
if (!(config.metrics && config.metrics[0])) {
return;
}
if (!(config.metricTypes && config.metricTypes[0])) {
return;
}
const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore();
if (!selectorStore.currentService && dashboardStore.entity !== "All") {
return;
}
const conditions: Recordable = {
duration: appStore.durationTime,
};
const variables: string[] = [`$duration: Duration!`];
const isRelation = ["ServiceRelation", "ServiceInstanceRelation", "EndpointRelation", "ProcessRelation"].includes(
dashboardStore.entity,
);
if (isRelation && !selectorStore.currentDestService) {
return;
}
const fragment = config.metrics.map((name: string, index: number) => {
const metricType = config.metricTypes[index] || "";
const c = (config.metricConfig && config.metricConfig[index]) || {};
if ([MetricQueryTypes.ReadSampledRecords, MetricQueryTypes.SortMetrics].includes(metricType)) {
variables.push(`$condition${index}: TopNCondition!`);
conditions[`condition${index}`] = {
name,
parentService: ["All"].includes(dashboardStore.entity) ? null : selectorStore.currentService.value,
normal: selectorStore.currentService ? selectorStore.currentService.normal : true,
topN: Number(c.topN) || 10,
order: c.sortOrder || "DES",
};
} else {
const entity = {
serviceName: dashboardStore.entity === "All" ? undefined : selectorStore.currentService.value,
normal: dashboardStore.entity === "All" ? undefined : selectorStore.currentService.normal,
serviceInstanceName: ["ServiceInstance", "ServiceInstanceRelation", "ProcessRelation"].includes(
dashboardStore.entity,
)
? selectorStore.currentPod && selectorStore.currentPod.value
: undefined,
endpointName: dashboardStore.entity.includes("Endpoint")
? selectorStore.currentPod && selectorStore.currentPod.value
: undefined,
processName: dashboardStore.entity.includes("Process")
? selectorStore.currentProcess && selectorStore.currentProcess.value
: undefined,
destNormal: isRelation ? selectorStore.currentDestService.normal : undefined,
destServiceName: isRelation ? selectorStore.currentDestService.value : undefined,
destServiceInstanceName: ["ServiceInstanceRelation", "ProcessRelation"].includes(dashboardStore.entity)
? selectorStore.currentDestPod && selectorStore.currentDestPod.value
: undefined,
destEndpointName:
dashboardStore.entity === "EndpointRelation"
? selectorStore.currentDestPod && selectorStore.currentDestPod.value
: undefined,
destProcessName: dashboardStore.entity.includes("ProcessRelation")
? selectorStore.currentDestProcess && selectorStore.currentDestProcess.value
: undefined,
};
if ([MetricQueryTypes.ReadRecords].includes(metricType)) {
variables.push(`$condition${index}: RecordCondition!`);
conditions[`condition${index}`] = {
name,
parentEntity: entity,
topN: Number(c.topN) || 10,
order: c.sortOrder || "DES",
};
} else {
if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) {
const labels = (c.labelsIndex || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
variables.push(`$labels${index}: [String!]!`);
conditions[`labels${index}`] = labels;
}
variables.push(`$condition${index}: MetricsCondition!`);
conditions[`condition${index}`] = {
name,
entity,
};
}
}
if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) {
return `${name}${index}: ${metricType}(condition: $condition${index}, labels: $labels${index}, duration: $duration)${RespFields[metricType]}`;
}
const t = metricType === MetricQueryTypes.ReadMetricsValue ? MetricQueryTypes.ReadNullableMetricsValue : metricType;
return `${name}${index}: ${t}(condition: $condition${index}, duration: $duration)${RespFields[t]}`;
});
const queryStr = `query queryData(${variables}) {${fragment}}`;
return {
queryStr,
conditions,
};
}
export function useSourceProcessor(
resp: { errors: string; data: Indexable },
config: {
metrics: string[];
metricTypes: string[];
metricConfig: MetricConfigOpt[];
},
) {
if (resp.errors) {
ElMessage.error(resp.errors);
return {};
}
if (!resp.data) {
ElMessage.error("The query is wrong");
return {};
}
const source: { [key: string]: unknown } = {};
const keys = Object.keys(resp.data);
config.metricTypes.forEach((type: string, index) => {
const m = config.metrics[index];
const c = (config.metricConfig && config.metricConfig[index]) || {};
if (type === MetricQueryTypes.ReadMetricsValues) {
source[c.label || m] = (resp.data[keys[index]] && calculateExp(resp.data[keys[index]].values.values, c)) || [];
}
if (type === MetricQueryTypes.ReadLabeledMetricsValues) {
const resVal = Object.values(resp.data)[0] || [];
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 (const item of resVal) {
const values = item.values.values.map((d: { value: number; isEmptyValue: boolean }) =>
d.isEmptyValue ? NaN : aggregation(Number(d.value), c),
);
const indexNum = labelsIdx.findIndex((d: string) => d === item.label);
if (labels[indexNum] && indexNum > -1) {
source[labels[indexNum]] = values;
} else {
source[item.label] = values;
}
}
}
if (type === MetricQueryTypes.ReadMetricsValue) {
const v = Object.values(resp.data)[0] || {};
source[m] = v.isEmptyValue ? NaN : aggregation(Number(v.value), c);
}
if (
(
[MetricQueryTypes.ReadRecords, MetricQueryTypes.ReadSampledRecords, MetricQueryTypes.SortMetrics] as string[]
).includes(type)
) {
source[m] = (Object.values(resp.data)[0] || []).map((d: { value: unknown; name: string }) => {
d.value = aggregation(Number(d.value), c);
return d;
});
}
if (type === MetricQueryTypes.READHEATMAP) {
const resVal = Object.values(resp.data)[0] || {};
const nodes = [] as Indexable[];
if (!(resVal && resVal.values)) {
source[m] = { nodes: [] };
return;
}
resVal.values.forEach((items: { values: number[] }, x: number) => {
const grids = items.values.map((val: number, y: number) => [x, y, val]);
nodes.push(...grids);
});
let buckets = [] as Indexable[];
if (resVal.buckets.length) {
buckets = [resVal.buckets[0].min, ...resVal.buckets.map((item: { min: string; max: string }) => item.max)];
}
source[m] = { nodes, buckets }; // nodes: number[][]
}
});
return source;
}
export function useQueryPodsMetrics(
pods: Array<(Instance | Endpoint | Service) & Indexable>,
config: {
metrics: string[];
metricTypes: string[];
metricConfig: MetricConfigOpt[];
},
scope: string,
) {
const metricTypes = (config.metricTypes || []).filter((m: string) => m);
if (!metricTypes.length) {
return;
}
const metrics = (config.metrics || []).filter((m: string) => m);
if (!metrics.length) {
return;
}
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const conditions: { [key: string]: unknown } = {
duration: appStore.durationTime,
};
const variables: string[] = [`$duration: Duration!`];
const currentService = selectorStore.currentService || {};
const fragmentList = pods.map((d: (Instance | Endpoint | Service) & Indexable, index: number) => {
const param = {
serviceName: scope === "Service" ? d.label : currentService.label,
serviceInstanceName: scope === "ServiceInstance" ? d.label : undefined,
endpointName: scope === "Endpoint" ? d.label : undefined,
normal: scope === "Service" ? d.normal : currentService.normal,
};
const f = metrics.map((name: string, idx: number) => {
const metricType = metricTypes[idx] || "";
variables.push(`$condition${index}${idx}: MetricsCondition!`);
conditions[`condition${index}${idx}`] = {
name,
entity: param,
};
let labelStr = "";
if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) {
const c = config.metricConfig[idx] || {};
variables.push(`$labels${index}${idx}: [String!]!`);
labelStr = `labels: $labels${index}${idx}, `;
const labels = (c.labelsIndex || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
conditions[`labels${index}${idx}`] = labels;
}
const t =
metricType === MetricQueryTypes.ReadMetricsValue ? MetricQueryTypes.ReadNullableMetricsValue : metricType;
return `${name}${index}${idx}: ${t}(condition: $condition${index}${idx}, ${labelStr}duration: $duration)${RespFields[t]}`;
});
return f;
});
const fragment = fragmentList.flat(1).join(" ");
const queryStr = `query queryData(${variables}) {${fragment}}`;
return { queryStr, conditions };
}
export function usePodsSource(
pods: Array<Instance | Endpoint>,
resp: { errors: string; data: Indexable },
config: {
metrics: string[];
metricTypes: string[];
metricConfig: MetricConfigOpt[];
},
): Indexable {
if (resp.errors) {
ElMessage.error(resp.errors);
return {};
}
const names: string[] = [];
const metricConfigArr: MetricConfigOpt[] = [];
const metricTypesArr: string[] = [];
const data = pods.map((d: any, idx: number) => {
config.metrics.map((name: string, index: number) => {
const c: any = (config.metricConfig && config.metricConfig[index]) || {};
const key = name + idx + index;
if (config.metricTypes[index] === MetricQueryTypes.ReadMetricsValue) {
const v = resp.data[key];
d[name] = v.isEmptyValue ? NaN : aggregation(v.value, c);
if (idx === 0) {
names.push(name);
metricConfigArr.push(c);
metricTypesArr.push(config.metricTypes[index]);
}
}
if (config.metricTypes[index] === MetricQueryTypes.ReadMetricsValues) {
d[name] = {};
if ([Calculations.Average, Calculations.ApdexAvg, Calculations.PercentageAvg].includes(c.calculation)) {
d[name]["avg"] = calculateExp(resp.data[key].values.values, c);
}
d[name]["values"] = resp.data[key].values.values.map((val: { value: number; isEmptyValue: boolean }) =>
val.isEmptyValue ? NaN : 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; isEmptyValue: boolean }) =>
d.isEmptyValue ? NaN : aggregation(Number(d.value), c),
);
const indexNum = labelsIdx.findIndex((d: string) => d === item.label);
let key = item.label;
if (labels[indexNum] && indexNum > -1) {
key = labels[indexNum];
}
if (!d[key]) {
d[key] = {};
}
if ([Calculations.Average, Calculations.ApdexAvg, Calculations.PercentageAvg].includes(c.calculation)) {
d[key]["avg"] = calculateExp(item.values.values, c);
}
d[key]["values"] = values;
if (idx === 0) {
names.push(key);
metricConfigArr.push({ ...c, index: i });
metricTypesArr.push(config.metricTypes[index]);
}
}
}
});
return d;
});
return { data, names, metricConfigArr, metricTypesArr };
}
export function useQueryTopologyMetrics(metrics: string[], ids: string[]) {
const appStore = useAppStoreWithOut();
const conditions: { [key: string]: unknown } = {
duration: appStore.durationTime,
ids,
};
const variables: string[] = [`$duration: Duration!`, `$ids: [ID!]!`];
const fragmentList = metrics.map((d: string, index: number) => {
conditions[`m${index}`] = d;
variables.push(`$m${index}: String!`);
return `${d}: getValues(metric: {
name: $m${index}
ids: $ids
}, duration: $duration) {
values {
id
value
}
}`;
});
const queryStr = `query queryData(${variables}) {${fragmentList.join(" ")}}`;
return { queryStr, conditions };
}
export function calculateExp(
list: { value: number; isEmptyValue: boolean }[],
config: { calculation?: string },
): (number | string)[] {
const arr = list.filter((d: { value: number; isEmptyValue: boolean }) => !d.isEmptyValue);
const sum = arr.length ? arr.map((d: { value: number }) => Number(d.value)).reduce((a, b) => a + b) : 0;
let data: (number | string)[] = [];
switch (config.calculation) {
case Calculations.Average:
data = [(sum / arr.length).toFixed(2)];
break;
case Calculations.PercentageAvg:
data = [(sum / arr.length / 100).toFixed(2)];
break;
case Calculations.ApdexAvg:
data = [(sum / arr.length / 10000).toFixed(2)];
break;
default:
data = list.map((d: { value: number; isEmptyValue: boolean }) =>
d.isEmptyValue ? NaN : aggregation(d.value, config),
);
break;
}
return data;
}
export function aggregation(val: number, config: { calculation?: string }): number | string {
let data: number | string = Number(val);
switch (config.calculation) {
case Calculations.Percentage:
data = (val / 100).toFixed(2);
break;
case Calculations.PercentageAvg:
data = (val / 100).toFixed(2);
break;
case Calculations.ByteToKB:
data = (val / 1024).toFixed(2);
break;
case Calculations.ByteToMB:
data = (val / 1024 / 1024).toFixed(2);
break;
case Calculations.ByteToGB:
data = (val / 1024 / 1024 / 1024).toFixed(2);
break;
case Calculations.Apdex:
data = (val / 10000).toFixed(2);
break;
case Calculations.ConvertSeconds:
data = dayjs(val * 1000).format("YYYY-MM-DD HH:mm:ss");
break;
case Calculations.ConvertMilliseconds:
data = dayjs(val).format("YYYY-MM-DD HH:mm:ss");
break;
case Calculations.MsToS:
data = (val / 1000).toFixed(2);
break;
case Calculations.SecondToDay:
data = (val / 86400).toFixed(2);
break;
case Calculations.NanosecondToMillisecond:
data = (val / 1000 / 1000).toFixed(2);
break;
case Calculations.ApdexAvg:
data = (val / 10000).toFixed(2);
break;
default:
data;
break;
}
return data;
}

View File

@@ -48,8 +48,14 @@ limitations under the License. -->
@input="changeTimeRange"
/>
<span> UTC{{ appStore.utcHour >= 0 ? "+" : "" }}{{ `${appStore.utcHour}:${appStore.utcMin}` }} </span>
<span class="ml-5">
<el-switch v-model="theme" :active-icon="Moon" :inactive-icon="Sunny" inline-prompt @change="changeTheme" />
<span class="ml-5" ref="themeSwitchRef">
<el-switch
v-model="theme"
:active-icon="Moon"
:inactive-icon="Sunny"
inline-prompt
@change="handleChangeTheme"
/>
</span>
<span title="refresh" class="ghost ml-5 cp" @click="handleReload">
<Icon iconName="retry" :loading="appStore.autoRefresh" class="middle" />
@@ -67,18 +73,18 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
import timeFormat from "@/utils/timeFormat";
import { Themes } from "@/constants/data";
import router from "@/router";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useDashboardStore } from "@/store/modules/dashboard";
import { ElMessage } from "element-plus";
import { MetricCatalog } from "@/views/dashboard/data";
import type { DashboardItem } from "@/types/dashboard";
import router from "@/router";
import timeFormat from "@/utils/timeFormat";
import { MetricCatalog } from "@/views/dashboard/data";
import { ArrowRight, Moon, Sunny } from "@element-plus/icons-vue";
import { Themes } from "@/constants/data";
import { ElMessage } from "element-plus";
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
/*global Indexable */
const { t, te } = useI18n();
@@ -89,11 +95,13 @@ limitations under the License. -->
const timeRange = ref<number>(0);
const pageTitle = ref<string>("");
const theme = ref<boolean>(true);
const themeSwitchRef = ref<HTMLElement>();
const savedTheme = window.localStorage.getItem("theme-is-dark");
if (savedTheme === "false") {
theme.value = false;
} else if (savedTheme === "") {
}
if (savedTheme === "") {
// read the theme preference from system setting if there is no user setting
theme.value = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
}
@@ -118,6 +126,35 @@ limitations under the License. -->
window.localStorage.setItem("theme-is-dark", String(theme.value));
}
function handleChangeTheme() {
const x = themeSwitchRef.value?.offsetLeft ?? 0;
const y = themeSwitchRef.value?.offsetTop ?? 0;
const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y));
// compatibility handling
if (!document.startViewTransition) {
changeTheme();
return;
}
// api: https://developer.chrome.com/docs/web-platform/view-transitions
const transition = document.startViewTransition(() => {
changeTheme();
});
transition.ready.then(() => {
const clipPath = [`circle(0px at ${x}px ${y}px)`, `circle(${endRadius}px at ${x}px ${y}px)`];
document.documentElement.animate(
{
clipPath: !theme.value ? clipPath.reverse() : clipPath,
},
{
duration: 500,
easing: "ease-in",
pseudoElement: !theme.value ? "::view-transition-old(root)" : "::view-transition-new(root)",
},
);
});
}
function getName(list: any[]) {
return list.find((d: any) => d.selected) || {};
}

View File

@@ -94,14 +94,13 @@ const msg = {
editTab: "Enable editing tab names",
label: "Service Name",
id: "Service ID",
setRoot: "Set this to root",
setNormal: "Set this to normal",
setRoot: "Set Normal to Root",
setNormal: "Set Root to Normal",
export: "Export Dashboard Templates",
import: "Import Dashboard Templates",
yes: "Yes",
no: "No",
tableHeaderCol1: "Name of the first column of the table",
tableHeaderCol2: "Name of the second column of the table",
tableHeaderCol2: "Name of the last column of the table",
showXAxis: "Show X Axis",
showYAxis: "Show Y Axis",
nameError: "The dashboard name cannot be duplicate",
@@ -378,11 +377,13 @@ const msg = {
menus: "Menus",
saveReload: "Save and reload the page",
document: "Documentation",
metricMode: "Metric Mode",
addExpressions: "Add Expressions",
expressions: "Expression",
unhealthyExpression: "Unhealthy Expression",
traceDesc:
"The trace segment serves as a representation of a trace portion executed within one single OS process, such as a JVM. It comprises a collection of spans, typically associated with and collected from a single request or execution context.",
tabExpressions: "Tab Expressions",
hierarchyNodeMetrics: "Metrics for Hierarchy Graph Node",
hierarchyNodeDashboard: "As dashboard for Hierarchy Graph Node",
};
export default msg;

View File

@@ -101,8 +101,7 @@ const msg = {
import: "Importar Plantilla Panel",
yes: "Sí",
no: "No",
tableHeaderCol1: "Nombre de la primera columna de la tabla",
tableHeaderCol2: "Nombre de la segunda columna de la tabla",
tableHeaderCol2: "Nombre de la Último columna de la tabla",
showXAxis: "Mostrar Eje X",
showYAxis: "Mostrar Eje Y",
nameError: "El nombre del panel no puede ser duplicado",
@@ -378,11 +377,13 @@ const msg = {
menus: "Menus",
saveReload: "Save and reload the page",
document: "Documentation",
metricMode: "Metric Mode",
addExpressions: "Add Expressions",
expressions: "Expression",
unhealthyExpression: "Unhealthy Expression",
traceDesc:
"The trace segment serves as a representation of a trace portion executed within one single OS process, such as a JVM. It comprises a collection of spans, typically associated with and collected from a single request or execution context.",
tabExpressions: "Tab Expressions",
hierarchyNodeMetrics: "Metrics for Hierarchy Graph Node",
hierarchyNodeDashboard: "As dashboard for Hierarchy Graph Node",
};
export default msg;

View File

@@ -30,6 +30,11 @@ const titles = {
general_service_virtual_mq: "Virtual MQ",
general_service_virtual_mq_desc:
"Observe the virtual message queue servers which are conjectured by language agents through various plugins.",
// Workflow Scheduler
workflow_scheduler: "Workflow Scheduler",
workflow_scheduler_desc: "Provide monitoring for workflow scheduling systems.",
workflow_scheduler_airflow: "Airflow",
workflow_scheduler_airflow_desc: "Observe tasks through telemetry data collected from Apache Airflow.",
// Service Mesh
service_mesh: "Service Mesh",
service_mesh_desc:
@@ -108,6 +113,8 @@ const titles = {
mq_kafka_desc: "Provide Kafka monitoring through OpenTelemetry's Prometheus Receiver.",
mq_pulsar: "Pulsar",
mq_pulsar_desc: "Provide Pulsar monitoring through OpenTelemetry's Prometheus Receiver.",
mq_rocketmq: "RocketMQ",
mq_rocketmq_desc: "Provide RocketMQ monitoring through OpenTelemetry's Prometheus Receiver.",
// self observability
self_observability: "Self Observability",
self_observability_desc:

View File

@@ -30,6 +30,12 @@ const titles = {
general_service_virtual_mq: "MQ virtual",
general_service_virtual_mq_desc:
"Observe the virtual message queue servers which are conjectured by language agents through various plugins.",
// Workflow Scheduler
workflow_scheduler: "Flujo de trabajo",
workflow_scheduler_desc: "Proporcionar monitoreo para sistemas de programación de flujos de trabajo.",
workflow_scheduler_airflow: "Airflow",
workflow_scheduler_airflow_desc:
"Observando tareas a través de los datos de telemetría recopilados desde Apache Airflow.",
// Service Mesh
service_mesh: "Malla de Servicios",
service_mesh_desc:
@@ -108,6 +114,8 @@ const titles = {
mq_kafka_desc: "Provide Kafka monitoring through OpenTelemetry's Prometheus Receiver.",
mq_pulsar: "Pulsar",
mq_pulsar_desc: "Provide Pulsar monitoring through OpenTelemetry's Prometheus Receiver.",
mq_rocketmq: "RocketMQ",
mq_rocketmq_desc: "Provide RocketMQ monitoring through OpenTelemetry's Prometheus Receiver.",
// self observability
self_observability: "Self Observability",
self_observability_desc:

View File

@@ -26,6 +26,11 @@ const titles = {
general_service_virtual_cache_desc: "观察语言代理通过各种插件推测的虚拟缓存服务器。",
general_service_virtual_mq: "虚拟消息队列",
general_service_virtual_mq_desc: "观察语言代理通过各种插件推测的虚拟消息队列服务器。",
// Workflow Scheduler
workflow_scheduler: "工作流调度",
workflow_scheduler_desc: "提供工作流调度系统监控。",
workflow_scheduler_airflow: "Airflow",
workflow_scheduler_airflow_desc: "通过从Apache Airflow收集的遥测数据观察任务。",
// Service Mesh
service_mesh: "服务网格",
service_mesh_desc: "服务网格Istio通过分布式或微服务架构解决了开发人员和运营商面临的挑战。",
@@ -96,6 +101,8 @@ const titles = {
mq_Kafka_desc: "通过OpenTelemetry的Prometheus接收器提供Kafka监控。",
mq_pulsar: "Pulsar",
mq_Pulsar_desc: "通过OpenTelemetry的Prometheus接收器提供Pulsar监控。",
mq_rocketmq: "RocketMQ",
mq_rocketmq_desc: "通过OpenTelemetry的Prometheus接收器提供RocketMQ监控。",
// self observability
self_observability: "自监控",
self_observability_desc: "自观察性为运行SkyWalking生态系统中的组件和服务器提供了可观察性。",

View File

@@ -99,8 +99,7 @@ const msg = {
import: "导入仪表板模板",
yes: "是",
no: "否",
tableHeaderCol1: "表格的一列的名称",
tableHeaderCol2: "表格的第二列的名称",
tableHeaderCol2: "表格的最后一列的名称",
showXAxis: "显示X轴",
showYAxis: "显示Y轴",
nameError: "仪表板名称不能重复",
@@ -376,11 +375,13 @@ const msg = {
menusManagement: "菜单",
saveReload: "保存并重新加载页面",
document: "文档",
metricMode: "指标模式",
addExpressions: "添加表达式",
expressions: "表达式",
unhealthyExpression: "非健康表达式",
traceDesc:
"Trace Segment代表在单一操作系统进程例如JVM中执行的追踪部分。它包含了一组跨度spans这些跨度通常与单一请求或执行上下文关联。",
tabExpressions: "Tab表达式",
hierarchyNodeMetrics: "层次图节点的指标",
hierarchyNodeDashboard: "作为层次图节点的dashboard",
};
export default msg;

View File

@@ -21,6 +21,7 @@ import { routesMarketplace } from "./marketplace";
import { routesAlarm } from "./alarm";
import routesLayers from "./layer";
import { routesSettings } from "./settings";
import { routesNotFound } from "./notFound";
const routes: RouteRecordRaw[] = [
...routesMarketplace,
@@ -28,6 +29,7 @@ const routes: RouteRecordRaw[] = [
...routesAlarm,
...routesDashboard,
...routesSettings,
...routesNotFound,
];
const router = createRouter({
@@ -47,7 +49,23 @@ router.beforeEach((to, from, next) => {
}
if (to.path === "/") {
const defaultPath = (routesLayers[0] && routesLayers[0].children[0].path) || "";
let defaultPath = "";
for (const route of routesLayers) {
for (const child of route.children) {
if (child.meta.activate) {
defaultPath = child.path;
break;
}
}
if (defaultPath) {
break;
}
}
if (!defaultPath) {
defaultPath = "/marketplace";
}
next({ path: defaultPath });
} else {
next();

26
src/router/notFound.ts Normal file
View File

@@ -0,0 +1,26 @@
/**
* 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 type { RouteRecordRaw } from "vue-router";
import NotFound from "@/views/NotFound.vue";
export const routesNotFound: Array<RouteRecordRaw> = [
{
path: "/:pathMatch(.*)*",
name: "NotFound",
component: NotFound,
},
];

View File

@@ -14,13 +14,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { WidgetType } from "@/views/dashboard/data";
export const NewControl = {
x: 0,
y: 0,
w: 24,
h: 12,
i: "0",
type: "Widget",
type: WidgetType.Widget,
};
export const TextConfig = {
fontColor: "white",
@@ -39,15 +41,15 @@ export const TimeRangeConfig = {
};
export const ControlsTypes = [
"Trace",
"Profile",
"Log",
"DemandLog",
"Ebpf",
"NetworkProfiling",
"ThirdPartyApp",
"ContinuousProfiling",
"TaskTimeline",
WidgetType.Trace,
WidgetType.Profile,
WidgetType.Log,
WidgetType.DemandLog,
WidgetType.Ebpf,
WidgetType.NetworkProfiling,
WidgetType.ThirdPartyApp,
WidgetType.ContinuousProfiling,
WidgetType.TaskTimeline,
];
export enum EBPFProfilingTriggerType {
FIXED_TIME = "FIXED_TIME",

View File

@@ -24,8 +24,7 @@ import { useSelectorStore } from "@/store/modules/selectors";
import { NewControl, TextConfig, TimeRangeConfig, ControlsTypes } from "../data";
import type { AxiosResponse } from "axios";
import { ElMessage } from "element-plus";
import { useI18n } from "vue-i18n";
import { EntityType, MetricModes } from "@/views/dashboard/data";
import { EntityType, WidgetType } from "@/views/dashboard/data";
interface DashboardState {
showConfig: boolean;
layout: LayoutConfig[];
@@ -78,7 +77,7 @@ export const dashboardStore = defineStore({
setCurrentDashboard(item: DashboardItem) {
this.currentDashboard = item;
},
addControl(type: string) {
addControl(type: WidgetType) {
const arr = this.layout.map((d: Recordable) => Number(d.i));
let index = String(Math.max(...arr) + 1);
if (!this.layout.length) {
@@ -89,14 +88,8 @@ export const dashboardStore = defineStore({
i: index,
id: index,
type,
metricTypes: [""],
metrics: [""],
};
if (type === "Widget") {
newItem.metricMode = MetricModes.Expression;
}
if (type === "Tab") {
if (type === WidgetType.Tab) {
newItem.h = 36;
newItem.activedTabIndex = 0;
newItem.children = [
@@ -110,7 +103,7 @@ export const dashboardStore = defineStore({
},
];
}
if (type === "Topology") {
if (type === WidgetType.Topology) {
newItem.h = 36;
newItem.graph = {
showDepth: true,
@@ -120,11 +113,11 @@ export const dashboardStore = defineStore({
if (ControlsTypes.includes(type)) {
newItem.h = 36;
}
if (type === "Text") {
if (type === WidgetType.Text) {
newItem.h = 6;
newItem.graph = TextConfig;
}
if (type === "TimeRange") {
if (type === WidgetType.TimeRange) {
newItem.w = 8;
newItem.h = 6;
newItem.graph = TimeRangeConfig;
@@ -149,7 +142,7 @@ export const dashboardStore = defineStore({
};
this.layout[idx].children?.push(i);
},
addTabControls(type: string) {
addTabControls(type: WidgetType) {
const activedGridItem = this.activedGridItem.split("-")[0];
const idx = this.layout.findIndex((d: LayoutConfig) => d.i === activedGridItem);
if (idx < 0) {
@@ -168,27 +161,21 @@ export const dashboardStore = defineStore({
i: index,
id,
type,
metricTypes: [""],
metrics: [""],
};
if (type === "Widget") {
newItem.metricMode = MetricModes.Expression;
}
if (type === "Topology") {
if (type === WidgetType.Topology) {
newItem.h = 32;
newItem.graph = {
showDepth: true,
};
newItem.metricMode = MetricModes.Expression;
}
if (ControlsTypes.includes(type)) {
newItem.h = 32;
}
if (type === "Text") {
if (type === WidgetType.Text) {
newItem.h = 6;
newItem.graph = TextConfig;
}
if (type === "TimeRange") {
if (type === WidgetType.TimeRange) {
newItem.w = 8;
newItem.h = 6;
newItem.graph = TextConfig;
@@ -292,7 +279,7 @@ export const dashboardStore = defineStore({
},
setWidget(param: LayoutConfig) {
for (let i = 0; i < this.layout.length; i++) {
if (this.layout[i].type === "Tab") {
if (this.layout[i].type === WidgetType.Tab) {
if ((this.layout[i].children || []).length) {
for (const child of this.layout[i].children || []) {
if (child.children && child.children.length) {
@@ -344,11 +331,9 @@ export const dashboardStore = defineStore({
const key = [c.layer, c.entity, c.name].join("_");
list.push({
...c,
id: t.id,
layer: c.layer,
entity: c.entity,
name: c.name,
isRoot: c.isRoot,
children: undefined,
});
sessionStorage.setItem(key, JSON.stringify({ id: t.id, configuration: c }));
}
@@ -429,8 +414,7 @@ export const dashboardStore = defineStore({
d.layer === this.currentDashboard?.layer,
);
if (index > -1) {
const { t } = useI18n();
ElMessage.error(t("nameError"));
ElMessage.error("The dashboard name cannot be duplicate");
return;
}
res = await graphql.query("addNewTemplate").params({ setting: { configuration: JSON.stringify(c) } });

View File

@@ -16,14 +16,13 @@
*/
import { defineStore } from "pinia";
import { store } from "@/store";
import type { Service } from "@/types/selector";
import type { Node, Call } from "@/types/topology";
import type { Node, Call, HierarchyNode, ServiceHierarchy, InstanceHierarchy } from "@/types/topology";
import graphql from "@/graphql";
import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import type { AxiosResponse } from "axios";
import query from "@/graphql/fetch";
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
import { ElMessage } from "element-plus";
@@ -35,9 +34,15 @@ interface TopologyState {
call: Nullable<Call>;
calls: Call[];
nodes: Node[];
hierarchyServiceCalls: Call[];
hierarchyServiceNodes: HierarchyNode[];
hierarchyInstanceCalls: Call[];
hierarchyInstanceNodes: HierarchyNode[];
nodeMetricValue: MetricVal;
linkServerMetrics: MetricVal;
linkClientMetrics: MetricVal;
hierarchyNodeMetrics: { [key: string]: MetricVal };
hierarchyInstanceNodeMetrics: { [key: string]: MetricVal };
}
export const topologyStore = defineStore({
@@ -45,11 +50,17 @@ export const topologyStore = defineStore({
state: (): TopologyState => ({
calls: [],
nodes: [],
hierarchyServiceCalls: [],
hierarchyServiceNodes: [],
hierarchyInstanceCalls: [],
hierarchyInstanceNodes: [],
node: null,
call: null,
nodeMetricValue: {},
linkServerMetrics: {},
linkClientMetrics: {},
hierarchyNodeMetrics: {},
hierarchyInstanceNodeMetrics: {},
}),
actions: {
setNode(node: Node) {
@@ -75,12 +86,9 @@ export const topologyStore = defineStore({
},
setTopology(data: { nodes: Node[]; calls: Call[] }) {
const obj = {} as Recordable;
const services = useSelectorStore().services;
const nodes = (data.nodes || []).reduce((prev: Node[], next: Node) => {
if (!obj[next.id]) {
obj[next.id] = true;
const s = services.filter((d: Service) => d.id === next.id)[0] || {};
next.layer = s.layers ? s.layers[0] : null;
prev.push(next);
}
return prev;
@@ -106,8 +114,107 @@ export const topologyStore = defineStore({
this.calls = calls;
this.nodes = nodes;
},
setNodeMetricValue(m: MetricVal) {
this.nodeMetricValue = m;
setHierarchyInstanceTopology(data: InstanceHierarchy, levels: { layer: string; level: number }[]) {
const relations = data.relations || [];
const nodesMap = new Map();
const callList = [];
for (const relation of relations) {
const upperId = relation.upperInstance.id;
const lowerId = relation.lowerInstance.id;
const lowerKey = `${lowerId}-${relation.lowerInstance.layer}`;
const upperKey = `${upperId}-${relation.upperInstance.layer}`;
const lowerLevel = levels.find(
(l: { layer: string; level: number }) => l.layer === relation.lowerInstance.layer,
) || { level: undefined };
const upperLevel = levels.find(
(l: { layer: string; level: number }) => l.layer === relation.upperInstance.layer,
) || { level: undefined };
const lowerObj = {
...relation.lowerInstance,
key: lowerId,
id: lowerKey,
l: lowerLevel.level,
};
const upperObj = {
...relation.upperInstance,
key: upperId,
id: upperKey,
l: upperLevel.level,
};
if (!nodesMap.get(upperKey)) {
nodesMap.set(upperKey, upperObj);
}
if (!nodesMap.get(lowerKey)) {
nodesMap.set(lowerKey, lowerObj);
}
callList.push({
target: lowerKey,
source: upperKey,
id: `${lowerKey}->${upperKey}`,
sourceObj: upperObj,
targetObj: lowerObj,
});
}
this.hierarchyInstanceCalls = callList;
this.hierarchyInstanceNodes = [];
for (const d of nodesMap.values()) {
this.hierarchyInstanceNodes.push(d);
}
},
setHierarchyServiceTopology(data: ServiceHierarchy, levels: { layer: string; level: number }[]) {
const relations = data.relations || [];
const nodesMap = new Map();
const callList = [];
for (const relation of relations) {
const upperId = relation.upperService.id;
const lowerId = relation.lowerService.id;
const lowerKey = `${lowerId}-${relation.lowerService.layer}`;
const upperKey = `${upperId}-${relation.upperService.layer}`;
const lowerLevel = levels.find(
(l: { layer: string; level: number }) => l.layer === relation.lowerService.layer,
) || { level: undefined };
const upperLevel = levels.find(
(l: { layer: string; level: number }) => l.layer === relation.upperService.layer,
) || { level: undefined };
const lowerObj = {
...relation.lowerService,
key: lowerId,
id: lowerKey,
l: lowerLevel.level,
};
const upperObj = {
...relation.upperService,
key: upperId,
id: upperKey,
l: upperLevel.level,
};
if (!nodesMap.get(upperKey)) {
nodesMap.set(upperKey, upperObj);
}
if (!nodesMap.get(lowerKey)) {
nodesMap.set(lowerKey, lowerObj);
}
callList.push({
target: lowerKey,
source: upperKey,
id: `${lowerKey}->${upperKey}`,
sourceObj: upperObj,
targetObj: lowerObj,
});
}
this.hierarchyServiceCalls = callList;
this.hierarchyServiceNodes = [];
for (const d of nodesMap.values()) {
this.hierarchyServiceNodes.push(d);
}
},
setHierarchyNodeMetricValue(m: MetricVal, layer: string) {
this.hierarchyNodeMetrics[layer] = m;
},
setHierarchyInstanceNodeMetricValue(m: MetricVal, layer: string) {
this.hierarchyInstanceNodeMetrics[layer] = m;
},
setLinkServerMetrics(m: MetricVal) {
this.linkServerMetrics = m;
@@ -115,12 +222,16 @@ export const topologyStore = defineStore({
setLinkClientMetrics(m: MetricVal) {
this.linkClientMetrics = m;
},
setNodeMetricValue(m: MetricVal) {
this.nodeMetricValue = m;
},
setLegendValues(expressions: string, data: { [key: string]: any }) {
for (let idx = 0; idx < this.nodes.length; idx++) {
const nodeArr = this.nodes.filter((d: Node) => d.isReal);
for (let idx = 0; idx < nodeArr.length; idx++) {
for (let index = 0; index < expressions.length; index++) {
const k = "expression" + idx + index;
if (expressions[index]) {
this.nodes[idx][expressions[index]] = Number(data[k].results[0].values[0].value);
nodeArr[idx][expressions[index]] = Number(data[k].results[0].values[0].value);
}
}
}
@@ -332,15 +443,6 @@ export const topologyStore = defineStore({
return { calls, nodes };
},
async getNodeMetricValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const res: AxiosResponse = await query(param);
if (res.data.errors) {
return res.data;
}
this.setNodeMetricValue(res.data.data);
return res.data;
},
async getNodeExpressionValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const res: AxiosResponse = await query(param);
@@ -350,38 +452,6 @@ export const topologyStore = defineStore({
return res.data;
},
async getLinkClientMetrics(linkClientMetrics: string[]) {
if (!linkClientMetrics.length) {
this.setLinkClientMetrics({});
return;
}
const idsC = this.calls.filter((i: Call) => i.detectPoints.includes("CLIENT")).map((b: Call) => b.id);
if (!idsC.length) {
return;
}
const param = await useQueryTopologyMetrics(linkClientMetrics, idsC);
const res = await this.getCallClientMetrics(param);
if (res.errors) {
ElMessage.error(res.errors);
}
},
async getLinkServerMetrics(linkServerMetrics: string[]) {
if (!linkServerMetrics.length) {
this.setLinkServerMetrics({});
return;
}
const idsS = this.calls.filter((i: Call) => i.detectPoints.includes("SERVER")).map((b: Call) => b.id);
if (!idsS.length) {
return;
}
const param = await useQueryTopologyMetrics(linkServerMetrics, idsS);
const res = await this.getCallServerMetrics(param);
if (res.errors) {
ElMessage.error(res.errors);
}
},
async getLinkExpressions(expressions: string[], type: string) {
if (!expressions.length) {
this.setLinkServerMetrics({});
@@ -405,22 +475,6 @@ export const topologyStore = defineStore({
this.setLinkClientMetrics(metrics);
}
},
async queryNodeMetrics(nodeMetrics: string[]) {
if (!nodeMetrics.length) {
this.setNodeMetricValue({});
return;
}
const ids = this.nodes.map((d: Node) => d.id);
if (!ids.length) {
return;
}
const param = await useQueryTopologyMetrics(nodeMetrics, ids);
const res = await this.getNodeMetricValue(param);
if (res.errors) {
ElMessage.error(res.errors);
}
},
async queryNodeExpressions(expressions: string[]) {
if (!expressions.length) {
this.setNodeMetricValue({});
@@ -432,7 +486,7 @@ export const topologyStore = defineStore({
}
const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor(
expressions,
this.nodes,
this.nodes.filter((d: Node) => d.isReal),
);
const param = getExpressionQuery();
const res = await this.getNodeExpressionValue(param);
@@ -443,44 +497,97 @@ export const topologyStore = defineStore({
const metrics = handleExpressionValues(res.data);
this.setNodeMetricValue(metrics);
},
async getLegendMetrics(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const res: AxiosResponse = await query(param);
async getHierarchyServiceTopology() {
const dashboardStore = useDashboardStore();
const { currentService } = useSelectorStore();
const id = this.node ? this.node.id : (currentService || {}).id;
let layer = dashboardStore.layerId;
if (this.node) {
layer = this.node.layers.includes(dashboardStore.layerId)
? dashboardStore.layerId
: this.node.layers.filter((d: string) => d !== dashboardStore.layerId)[0];
}
if (!(id && layer)) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql
.query("getHierarchyServiceTopology")
.params({ serviceId: id, layer: layer });
if (res.data.errors) {
return res.data;
}
const data = res.data.data;
const metrics = Object.keys(data);
this.nodes = this.nodes.map((d: Node & Recordable) => {
for (const m of metrics) {
for (const val of data[m].values) {
if (d.id === val.id) {
d[m] = val.value;
const resp = await this.getListLayerLevels();
if (resp.errors) {
return resp;
}
}
}
return d;
});
const levels = resp.data.levels || [];
this.setHierarchyServiceTopology(res.data.data.hierarchyServiceTopology || {}, levels);
return res.data;
},
async getCallServerMetrics(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const res: AxiosResponse = await query(param);
async getListLayerLevels() {
const res: AxiosResponse = await graphql.query("queryListLayerLevels").params({});
if (res.data.errors) {
return res.data;
}
this.setLinkServerMetrics(res.data.data);
return res.data;
},
async getCallClientMetrics(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const res: AxiosResponse = await query(param);
async getHierarchyInstanceTopology() {
const { currentPod } = useSelectorStore();
const dashboardStore = useDashboardStore();
if (!(currentPod && dashboardStore.layerId)) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql
.query("getHierarchyInstanceTopology")
.params({ instanceId: currentPod.id, layer: dashboardStore.layerId });
if (res.data.errors) {
return res.data;
}
this.setLinkClientMetrics(res.data.data);
const resp = await this.getListLayerLevels();
if (resp.errors) {
return resp;
}
const levels = resp.data.levels || [];
this.setHierarchyInstanceTopology(res.data.data.hierarchyInstanceTopology || {}, levels);
return res.data;
},
async queryHierarchyExpressions(expressions: string[], nodes: Node[]) {
const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor(expressions, nodes);
const param = getExpressionQuery();
const res = await this.getNodeExpressionValue(param);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
const metrics = handleExpressionValues(res.data);
return metrics;
},
async queryHierarchyNodeExpressions(expressions: string[], layer: string) {
const nodes = this.hierarchyServiceNodes.filter((n: HierarchyNode) => n.layer === layer);
if (!nodes.length) {
this.setHierarchyNodeMetricValue({}, layer);
return;
}
if (!expressions.length) {
this.setHierarchyNodeMetricValue({}, layer);
return;
}
const metrics = await this.queryHierarchyExpressions(expressions, nodes);
this.setHierarchyNodeMetricValue(metrics, layer);
},
async queryHierarchyInstanceNodeExpressions(expressions: string[], layer: string) {
const nodes = this.hierarchyInstanceNodes.filter((n: HierarchyNode) => n.layer === layer);
if (!expressions.length) {
this.setHierarchyInstanceNodeMetricValue({}, layer);
return;
}
if (!nodes.length) {
this.setHierarchyInstanceNodeMetricValue({}, layer);
return;
}
const metrics = await this.queryHierarchyExpressions(expressions, nodes);
this.setHierarchyInstanceNodeMetricValue(metrics, layer);
},
},
});

View File

@@ -23,7 +23,6 @@ import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { QueryOrders } from "@/views/dashboard/data";
interface TraceState {
services: Service[];
instances: Instance[];
@@ -168,6 +167,7 @@ export const traceStore = defineStore({
return res.data;
}
const data = res.data.data.trace.spans;
this.setTraceSpans(data || []);
return res.data;
},

View File

@@ -15,9 +15,19 @@
* limitations under the License.
*/
@use "element-plus/theme-chalk/src/dark/css-vars.scss" as *;
@keyframes topo-dash {
from {
stroke-dashoffset: 10;
}
to {
stroke-dashoffset: 0;
}
}
:root {
--sw-green: #70c877;
--sw-orange: #e6a23c;
--sw-topo-animation: topo-dash 0.3s linear infinite;
}
html {
@@ -58,14 +68,14 @@ html {
--sw-time-axis-text: #4d4d4d;
--sw-drawer-header: #72767b;
--sw-marketplace-border: #dedfe0;
--sw-grid-item-active: #79bbff;
--sw-grid-item-active: #d4d7de;
}
html.dark {
--el-color-primary: #409eff;
--theme-background: #212224;
--font-color: #fafbfc;
--disabled-color: #ccc;
--disabled-color: #999;
--dashboard-tool-bg: #000;
--text-color-placeholder: #ccc;
--border-color: #262629;
@@ -82,7 +92,7 @@ html.dark {
--sw-config-header: #303133;
--sw-topology-color: #ccc;
--vis-tooltip-bg: #414243;
--sw-topology-switch-icon: #aaa;
--sw-topology-switch-icon: #999;
--sw-topology-box-shadow: 0 0 2px 0 #444;
--sw-topology-setting-bg: #333;
--sw-topology-border: 1px solid #666;
@@ -250,3 +260,27 @@ div:has(> a.menu-title) {
.el-input-number .el-input__inner {
text-align: left !important;
}
html {
&::view-transition-old(root),
&::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
}
&.dark {
&::view-transition-old(root) {
z-index: 1;
}
&::view-transition-new(root) {
z-index: 999;
}
}
&::view-transition-old(root) {
z-index: 999;
}
&::view-transition-new(root) {
z-index: 1;
}
}

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

@@ -59,7 +59,7 @@ export interface SubItem {
icon: string;
title: string;
activate: boolean;
name?: string;
name: string;
path?: string;
notShow?: boolean;
id?: string;

View File

@@ -21,6 +21,9 @@ export type DashboardItem = {
layer: string;
isRoot: boolean;
name: string;
isDefault: boolean;
expressions?: string[];
expressionsConfig?: MetricConfigOpt[];
};
export interface LayoutConfig {
x: number;
@@ -29,14 +32,11 @@ export interface LayoutConfig {
h: number;
i: string;
type: string;
metricMode?: string;
widget?: WidgetConfig;
graph?: GraphConfig;
metrics?: string[];
expressions?: string[];
metricTypes?: string[];
typesOfMQE?: string[];
children?: { name: string; children: LayoutConfig[] }[];
children?: { name: string; children: LayoutConfig[]; expression?: string; enable?: boolean }[];
activedTabIndex?: number;
metricConfig?: MetricConfigOpt[];
id?: string;
@@ -74,7 +74,6 @@ export type Filters = {
export type MetricConfigOpt = {
unit?: string;
label?: string;
calculation?: string;
labelsIndex?: string;
sortOrder?: string;
topN?: number;
@@ -137,7 +136,6 @@ export interface TextConfig {
export interface TableConfig {
type?: string;
showTableValues: boolean;
tableHeaderCol1: string;
tableHeaderCol2: string;
}

View File

@@ -15,11 +15,11 @@
* limitations under the License.
*/
import type {
ComponentPublicInstance,
ComponentRenderProxy,
FunctionalComponent,
VNode,
VNodeChild,
ComponentPublicInstance,
FunctionalComponent,
PropType as VuePropType,
} from "vue";
@@ -39,6 +39,11 @@ declare global {
lastBuildTime: string;
};
// Document
interface Document {
startViewTransition(callback: () => void): any;
}
// vue
declare type PropType<T> = VuePropType<T>;
declare type VueNode = VNodeChild | JSX.Element;

View File

@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface Call {
source: string | any;
target: string | any;
@@ -31,15 +32,48 @@ export interface Call {
targetY?: number;
targetX?: number;
}
export interface HierarchyNode {
id: string;
name: string;
layer: string;
level?: number;
key: string;
}
export interface Node {
id: string;
name: string;
type: string;
isReal: boolean;
layer?: string;
layers: string[];
serviceName?: string;
height?: number;
width?: number;
x?: number;
y?: number;
level?: number;
l?: number;
}
export interface ServiceHierarchy {
relations: HierarchyServiceRelation[];
}
export interface HierarchyServiceRelation {
upperService: HierarchyRelated;
lowerService: HierarchyRelated;
}
type HierarchyRelated = {
id: string;
name: string;
layer: string;
};
export interface InstanceHierarchy {
relations: HierarchyInstanceRelation[];
}
export interface HierarchyInstanceRelation {
upperInstance: HierarchyRelated;
lowerInstance: HierarchyRelated;
}

29
src/utils/debounce.ts Normal file
View File

@@ -0,0 +1,29 @@
/**
* 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 function debounce(callback: Function, dur: number) {
let timer: any;
return function () {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function () {
callback();
}, dur);
};
}

44
src/utils/mutation.ts Normal file
View File

@@ -0,0 +1,44 @@
/**
* 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 class mutationObserver {
private static mutationObserverMap: Map<string, MutationObserver> = new Map<string, MutationObserver>();
static create(key: string, callback: MutationCallback) {
const observer = new MutationObserver(callback);
mutationObserver.mutationObserverMap.set(key, observer);
}
static observe(key: string, target: Node, options?: MutationObserverInit) {
const observer = mutationObserver.mutationObserverMap.get(key);
if (observer) {
observer.observe(target, options);
}
}
static deleteObserve(key: string): void {
this.disconnect(key);
this.mutationObserverMap.delete(key);
}
static disconnect(key: string): void {
const observer = this.mutationObserverMap.get(key);
if (observer) {
observer.disconnect();
}
}
}

View File

@@ -14,11 +14,20 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="menus flex-v">
<div>
<el-input v-model="searchText" placeholder="Please input name" class="input-with-search" @change="searchMenus">
<template #append>
<el-button size="small">
<Icon size="sm" iconName="search" />
</el-button>
</template>
</el-input>
</div>
<div class="category-body flex-h">
<div class="mr-20 mt-10 flex-h category">
<el-card
class="item"
v-for="(menu, index) in appStore.allMenus"
v-for="(menu, index) in menus"
:key="index"
@click="handleItems(menu)"
:class="currentItems.name === menu.name ? 'active' : ''"
@@ -54,15 +63,36 @@ limitations under the License. -->
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useAppStoreWithOut } from "@/store/modules/app";
import type { MenuOptions } from "@/types/app";
import type { MenuOptions, SubItem } from "@/types/app";
const { t, te } = useI18n();
const appStore = useAppStoreWithOut();
const currentItems = ref<MenuOptions>(appStore.allMenus[0] || {});
const searchText = ref<string>("");
const menus = ref<MenuOptions[]>(appStore.allMenus || []);
function handleItems(item: MenuOptions) {
currentItems.value = item;
}
function searchMenus() {
if (!searchText.value) {
menus.value = appStore.allMenus;
currentItems.value = menus.value[0] || {};
return;
}
menus.value = appStore.allMenus.filter(
(item: MenuOptions) =>
(te(item.i18nKey) ? t(item.i18nKey) : item.title).toLowerCase().includes(searchText.value.toLowerCase()) ||
!!item.subItems.find((subItem: SubItem) =>
(te(subItem.i18nKey) ? t(subItem.i18nKey) : item.title)
.toLowerCase()
.includes(searchText.value.toLowerCase()),
),
);
currentItems.value = menus.value[0] || {};
}
</script>
<style lang="scss" scoped>
.menus {
@@ -111,11 +141,16 @@ limitations under the License. -->
cursor: pointer;
}
.input-with-search {
width: 300px;
}
.category {
flex-wrap: wrap;
border-right: 1px solid var(--sw-marketplace-border);
align-content: flex-start;
height: 100%;
width: 100%;
overflow: auto;
}

33
src/views/NotFound.vue Normal file
View File

@@ -0,0 +1,33 @@
<!-- 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="not-found flex-h">
<Icon size="largest" iconName="logo-light" />
<h1 class="ml-20">404 Page Not Found</h1>
</div>
</template>
<style lang="scss" scoped>
.not-found {
height: 100%;
width: 100%;
align-items: center;
justify-content: center;
}
.icon {
width: 160px;
height: 160px;
}
</style>

View File

@@ -57,14 +57,23 @@ limitations under the License. -->
</el-table-column>
<el-table-column prop="layer" label="Layer" width="160" />
<el-table-column prop="entity" label="Entity" width="200" />
<el-table-column prop="isRoot" label="Root" width="60">
<el-table-column prop="isRoot" label="Root" width="150">
<template #default="scope">
<span>
{{ scope.row.isRoot ? t("yes") : t("no") }}
</span>
<el-popconfirm
:title="t('rootTitle')"
@confirm="setRoot(scope.row)"
v-if="[EntityType[0].value, EntityType[1].value].includes(scope.row.entity)"
>
<template #reference>
<el-button size="small" style="width: 110px">
{{ scope.row.isRoot ? t("setNormal") : t("setRoot") }}
</el-button>
</template>
</el-popconfirm>
<span v-else> -- </span>
</template>
</el-table-column>
<el-table-column label="Operations" width="350">
<el-table-column label="Operations" width="400">
<template #default="scope">
<el-button size="small" @click="handleEdit(scope.row)">
{{ t("edit") }}
@@ -72,6 +81,14 @@ limitations under the License. -->
<el-button size="small" @click="handleRename(scope.row)">
{{ t("rename") }}
</el-button>
<el-button
v-if="[EntityType[0].value, EntityType[3].value].includes(scope.row.entity)"
size="small"
@click="handleEditMQE(scope.row)"
>
Hierarchy Graph
</el-button>
<span v-else class="placeholder"></span>
<el-popconfirm :title="t('deleteTitle')" @confirm="handleDelete(scope.row)">
<template #reference>
<el-button size="small" type="danger">
@@ -79,17 +96,6 @@ limitations under the License. -->
</el-button>
</template>
</el-popconfirm>
<el-popconfirm
:title="t('rootTitle')"
@confirm="setRoot(scope.row)"
v-if="[EntityType[0].value, EntityType[1].value].includes(scope.row.entity)"
>
<template #reference>
<el-button size="small" style="width: 110px" type="danger">
{{ scope.row.isRoot ? t("setNormal") : t("setRoot") }}
</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
@@ -125,6 +131,43 @@ limitations under the License. -->
@next-click="changePage"
/>
</div>
<el-dialog v-model="MQEVisible" title="Hierarchy Graph" width="400px">
<div class="mb-10">
<span class="label mr-10">{{ t("hierarchyNodeDashboard") }}</span>
<el-switch v-model="currentRow.isDefault" style="height: 25px" />
</div>
<div>
<span class="label">{{ t("hierarchyNodeMetrics") }}</span>
<el-popover placement="left" :width="400" trigger="click">
<template #reference>
<span>
<Icon class="cp ml-5" iconName="mode_edit" size="middle" />
</span>
</template>
<Metrics
:type="'hierarchyServicesConfig'"
:expressions="currentRow.expressions || []"
:layer="currentRow.layer"
:entity="currentRow.entity"
@update="updateSettings"
/>
</el-popover>
</div>
<div class="mt-10 expressions">
<Tags
:tags="currentRow.expressions || []"
:vertical="true"
:text="t('addExpressions')"
@change="(param: string[]) => changeExpressions(param)"
/>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="MQEVisible = false"> Cancel </el-button>
<el-button type="primary" @click="saveMQE"> Confirm </el-button>
</span>
</template>
</el-dialog>
</div>
</div>
</template>
@@ -139,8 +182,10 @@ limitations under the License. -->
import { saveFile, readFile } from "@/utils/file";
import { EntityType } from "./data";
import { isEmptyObject } from "@/utils/is";
import { WidgetType } from "@/views/dashboard/data";
import Metrics from "@/views/dashboard/related/topology/config/Metrics.vue";
/*global Nullable*/
/*global Nullable, Recordable*/
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const pageSize = 20;
@@ -152,6 +197,8 @@ limitations under the License. -->
const multipleTableRef = ref<InstanceType<typeof ElTable>>();
const multipleSelection = ref<DashboardItem[]>([]);
const dashboardFile = ref<Nullable<HTMLDivElement>>(null);
const MQEVisible = ref<boolean>(false);
const currentRow = ref<DashboardItem | Recordable>({});
const handleSelectionChange = (val: DashboardItem[]) => {
multipleSelection.value = val;
@@ -161,8 +208,103 @@ limitations under the License. -->
await dashboardStore.setDashboards();
searchDashboards(1);
}
function changeExpressions(params: string[]) {
currentRow.value.expressions = params;
}
function updateSettings(config: Record<string, never>) {
currentRow.value = {
...currentRow.value,
expressionsConfig: config.hierarchyServicesConfig,
};
}
function handleEditMQE(row: DashboardItem) {
MQEVisible.value = !MQEVisible.value;
currentRow.value = row;
}
async function saveMQE() {
if (!currentRow.value.expressionsConfig) {
return;
}
const items: DashboardItem[] = [];
loading.value = true;
for (const d of dashboardStore.dashboards) {
if (d.id === currentRow.value.id) {
d.isDefault = currentRow.value.isDefault || false;
d.expressions = currentRow.value.expressions;
d.expressionsConfig = currentRow.value.expressionsConfig;
const key = [d.layer, d.entity, d.name].join("_");
const layout = sessionStorage.getItem(key) || "{}";
const c = {
...JSON.parse(layout).configuration,
...d,
};
delete c.id;
const setting = {
id: d.id,
configuration: JSON.stringify(c),
};
const res = await dashboardStore.updateDashboard(setting);
if (res.data.changeTemplate.status) {
sessionStorage.setItem(
key,
JSON.stringify({
id: d.id,
configuration: c,
}),
);
} else {
loading.value = false;
return;
}
} else {
if (
d.layer === currentRow.value.layer &&
[EntityType[0].value].includes(d.entity) &&
!currentRow.value.isDefault &&
d.isDefault
) {
d.isDefault = false;
const key = [d.layer, d.entity, d.name].join("_");
const layout = sessionStorage.getItem(key) || "{}";
const c = {
...JSON.parse(layout).configuration,
...d,
};
const setting = {
id: d.id,
configuration: JSON.stringify(c),
};
const res = await dashboardStore.updateDashboard(setting);
if (res.data.changeTemplate.status) {
sessionStorage.setItem(
key,
JSON.stringify({
id: d.id,
configuration: c,
}),
);
} else {
loading.value = false;
return;
}
}
}
items.push(d);
}
dashboardStore.resetDashboards(items);
searchDashboards(currentPage.value);
loading.value = false;
MQEVisible.value = false;
}
async function importTemplates(event: any) {
const arr: any = await readFile(event);
for (const item of arr) {
const { layer, name, entity } = item.configuration;
const index = dashboardStore.dashboards.findIndex(
@@ -174,17 +316,19 @@ limitations under the License. -->
}
loading.value = true;
for (const item of arr) {
const { layer, name, entity, isRoot, children } = item.configuration;
const { layer, name, entity, isRoot, children, isDefault, expressions, expressionsConfig } = item.configuration;
const index = dashboardStore.dashboards.findIndex((d: DashboardItem) => d.id === item.id);
const p: DashboardItem = {
name: name.split(" ").join("-"),
layer: layer,
entity: entity,
isRoot: false,
isRoot: isRoot || false,
isDefault: isDefault || false,
expressions: expressions,
expressionsConfig: expressionsConfig,
};
if (index > -1) {
p.id = item.id;
p.isRoot = isRoot;
}
dashboardStore.setCurrentDashboard(p);
dashboardStore.setLayout(children);
@@ -193,6 +337,7 @@ limitations under the License. -->
dashboards.value = dashboardStore.dashboards;
loading.value = false;
dashboardFile.value = null;
searchDashboards(currentPage.value);
}
function exportTemplates() {
if (!multipleSelection.value.length) {
@@ -207,6 +352,7 @@ limitations under the License. -->
return layout;
});
for (const item of templates) {
delete item.configuration.id;
optimizeTemplate(item.configuration.children);
}
const name = `dashboards.json`;
@@ -215,6 +361,20 @@ limitations under the License. -->
multipleTableRef.value!.clearSelection();
}, 2000);
}
function removeUnusedConfig(config: any) {
// remove `General` metrics config
delete config.metrics;
delete config.metricTypes;
delete config.metricMode;
delete config.linkServerMetrics;
delete config.linkClientMetrics;
delete config.nodeMetric;
if (([WidgetType.Topology] as string[]).includes(config.type)) {
delete config.legend;
}
}
function optimizeTemplate(
children: (LayoutConfig & {
moved?: boolean;
@@ -232,6 +392,8 @@ limitations under the License. -->
delete child.label;
delete child.value;
delete child.filters;
delete child.typesOfMQE;
delete child.subTypesOfMQE;
if (isEmptyObject(child.graph)) {
delete child.graph;
}
@@ -246,17 +408,8 @@ limitations under the License. -->
if (isEmptyObject(child.widget)) {
delete child.widget;
}
if (!(child.metrics && child.metrics.length && child.metrics[0])) {
delete child.metrics;
}
if (!(child.metricTypes && child.metricTypes.length && child.metricTypes[0])) {
delete child.metricTypes;
}
if (child.metricConfig && child.metricConfig.length) {
child.metricConfig.forEach((c, index) => {
if (!c.calculation) {
delete c.calculation;
}
if (!c.unit) {
delete c.unit;
}
@@ -271,12 +424,24 @@ limitations under the License. -->
if (!(child.metricConfig && child.metricConfig.length)) {
delete child.metricConfig;
}
if (child.type === "Tab") {
removeUnusedConfig(child);
if (child.type === WidgetType.Tab) {
for (const item of child.children || []) {
optimizeTemplate(item.children);
}
}
if (["Trace", "Topology", "Tab", "Profile", "Ebpf", "Log"].includes(child.type)) {
if (
(
[
WidgetType.Trace,
WidgetType.Topology,
WidgetType.Tab,
WidgetType.Profile,
WidgetType.Ebpf,
WidgetType.Log,
] as string[]
).includes(child.type)
) {
delete child.widget;
}
}
@@ -316,7 +481,7 @@ limitations under the License. -->
configuration: JSON.stringify(c),
};
const res = await dashboardStore.updateDashboard(setting);
if (res.data.changeTemplate.id) {
if (res.data.changeTemplate.status) {
sessionStorage.setItem(
key,
JSON.stringify({
@@ -324,6 +489,9 @@ limitations under the License. -->
configuration: c,
}),
);
} else {
loading.value = false;
return;
}
} else {
if (
@@ -344,7 +512,7 @@ limitations under the License. -->
configuration: JSON.stringify(c),
};
const res = await dashboardStore.updateDashboard(setting);
if (res.data.changeTemplate.id) {
if (res.data.changeTemplate.status) {
sessionStorage.setItem(
key,
JSON.stringify({
@@ -352,15 +520,19 @@ limitations under the License. -->
configuration: c,
}),
);
} else {
loading.value = false;
return;
}
}
}
items.push(d);
}
dashboardStore.resetDashboards(items);
searchDashboards(1);
searchDashboards(currentPage.value);
loading.value = false;
}
function handleRename(row: DashboardItem) {
ElMessageBox.prompt("Please input dashboard name", "Edit", {
confirmButtonText: "OK",
@@ -405,7 +577,7 @@ limitations under the License. -->
...d,
name: value,
});
dashboards.value = dashboardStore.dashboards.map((item: any) => {
dashboards.value = dashboardStore.dashboards.map((item: DashboardItem) => {
if (dashboardStore.currentDashboard.id === item.id) {
item = dashboardStore.currentDashboard;
}
@@ -436,8 +608,9 @@ limitations under the License. -->
loading.value = false;
sessionStorage.setItem("dashboards", JSON.stringify(dashboards.value));
sessionStorage.removeItem(`${row.layer}_${row.entity}_${row.name}`);
searchDashboards(currentPage.value);
}
function searchDashboards(pageIndex?: any) {
function searchDashboards(pageIndex: number) {
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
const arr = list.filter((d: { name: string }) => d.name.includes(searchText.value));
@@ -520,4 +693,17 @@ limitations under the License. -->
display: inline-block;
margin-left: 10px;
}
.placeholder {
display: inline-block;
width: 136px;
}
.expressions {
height: 300px;
}
.label {
font-size: 12px;
}
</style>

View File

@@ -15,9 +15,9 @@ limitations under the License. -->
<template>
<div class="content">
<div class="header">
<span>{{ decodeURIComponent(title) }}</span>
<span>{{ title }}</span>
<div class="tips" v-show="tips">
<el-tooltip :content="decodeURIComponent(tips) || ''">
<el-tooltip :content="tips || ''">
<span>
<Icon iconName="info_outline" size="sm" />
</span>
@@ -32,10 +32,7 @@ limitations under the License. -->
:config="{
i: 0,
...graph,
metrics: config.metrics,
metricTypes: config.metricTypes,
metricConfig: config.metricConfig,
metricMode: config.metricMode,
expressions: config.expressions || [],
typesOfMQE: typesOfMQE || [],
subExpressions: config.subExpressions || [],
@@ -56,12 +53,10 @@ limitations under the License. -->
import { useRoute } from "vue-router";
import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useQueryProcessor, useSourceProcessor } from "@/hooks/useMetricsProcessor";
import { useExpressionsQueryProcessor } from "@/hooks/useExpressionsProcessor";
import graphs from "./graphs";
import { EntityType } from "./data";
import timeFormat from "@/utils/timeFormat";
import { MetricModes } from "./data";
export default defineComponent({
name: "WidgetPage",
@@ -132,8 +127,6 @@ limitations under the License. -->
}
}
async function queryMetrics() {
const isExpression = config.value.metricMode === MetricModes.Expression;
if (isExpression) {
loading.value = true;
const params = await useExpressionsQueryProcessor({
metrics: config.value.expressions || [],
@@ -144,25 +137,6 @@ limitations under the License. -->
loading.value = false;
source.value = params.source || {};
typesOfMQE.value = params.typesOfMQE;
return;
}
const params = await useQueryProcessor({ ...config.value });
if (!params) {
source.value = {};
return;
}
loading.value = true;
const json = await dashboardStore.fetchMetricValue(params);
loading.value = false;
if (!json) {
return;
}
const d = {
metrics: config.value.metrics || [],
metricTypes: config.value.metricTypes || [],
metricConfig: config.value.metricConfig || [],
};
source.value = await useSourceProcessor(json, d);
}
watch(
() => appStoreWithOut.durationTime,
@@ -209,7 +183,7 @@ limitations under the License. -->
height: 25px;
line-height: 25px;
text-align: center;
background-color: aliceblue;
background-color: var(--sw-config-header);
font-size: $font-size-smaller;
position: relative;
}

View File

@@ -54,7 +54,6 @@ limitations under the License. -->
import copy from "@/utils/copy";
import { RefreshOptions } from "@/views/dashboard/data";
import { TimeType } from "@/constants/data";
import { MetricModes } from "../data";
const { t } = useI18n();
const appStore = useAppStoreWithOut();
@@ -88,8 +87,7 @@ limitations under the License. -->
step: appStore.durationRow.step,
utc: appStore.utc,
});
const { widget, graph, metrics, metricTypes, metricConfig, metricMode, expressions, typesOfMQE, subExpressions } =
dashboardStore.selectedGrid;
const { widget, graph, metricConfig, expressions, typesOfMQE, subExpressions } = dashboardStore.selectedGrid;
const c = (metricConfig || []).map((d: any) => {
const t: any = {};
if (d.label) {
@@ -103,20 +101,14 @@ limitations under the License. -->
const opt: any = {
type: dashboardStore.selectedGrid.type,
graph: graph,
metricMode,
metricConfig: c,
height: dashboardStore.selectedGrid.h * 20 + 60,
};
if (metricMode === MetricModes.Expression) {
opt.expressions = expressions;
opt.typesOfMQE = typesOfMQE;
if (subExpressions && subExpressions.length) {
opt.subExpressions = subExpressions;
}
} else {
opt.metrics = metrics;
opt.metricTypes = metricTypes;
}
if (widget) {
opt.widget = {
title: encodeURIComponent(widget.title || ""),

View File

@@ -0,0 +1,110 @@
<!-- 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="item">
<span class="label">{{ t("tabExpressions") }}</span>
<div class="mt-10" v-for="(child, index) in widgetTabs || []" :key="index">
<span class="name">{{ child.name }}</span>
<el-input class="input" size="small" v-model="expressions[child.name]" @change="changeExpression(child.name)" />
</div>
</div>
<div class="footer">
<el-button size="small" @click="cancelConfig">
{{ t("cancel") }}
</el-button>
<el-button size="small" type="primary" @click="applyConfig">
{{ t("apply") }}
</el-button>
</div>
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { reactive, computed } from "vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import { ElMessage } from "element-plus";
import { WidgetType, ListEntity } from "@/views/dashboard/data";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const originConfig = dashboardStore.selectedGrid;
const expressions = reactive<{ [key: string]: string }>({});
const widgetTabs = computed(() =>
(dashboardStore.selectedGrid.children || []).filter((child: any) =>
child.children.find(
(item: any) =>
item.type === WidgetType.Widget &&
!(Object.keys(ListEntity).includes(item.graph.type as string) && child.children.length === 1),
),
),
);
for (const child of originConfig.children || []) {
expressions[child.name] = child.expression || "";
}
function changeExpression(name: string) {
if (expressions[name] && !expressions[name].includes("is_present")) {
ElMessage.error("Only support the is_present function");
return;
}
const children = JSON.parse(JSON.stringify(dashboardStore.selectedGrid.children || []));
for (const item of children) {
if (item.name === name) {
item.expression = expressions[name];
}
}
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, children });
}
function applyConfig() {
dashboardStore.setConfigPanel(false);
dashboardStore.setConfigs(dashboardStore.selectedGrid);
}
function cancelConfig() {
dashboardStore.selectWidget(originConfig);
dashboardStore.setConfigPanel(false);
}
</script>
<style lang="scss" scoped>
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.item {
margin-bottom: 10px;
}
.input {
width: 500px;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid $border-color-primary;
padding: 10px;
text-align: right;
width: 100%;
background-color: $theme-background;
}
.name {
width: 180px;
display: inline-block;
}
</style>

View File

@@ -34,11 +34,8 @@ limitations under the License. -->
...graph,
legend: (dashboardStore.selectedGrid.graph || {}).legend,
i: dashboardStore.selectedGrid.i,
metrics: dashboardStore.selectedGrid.metrics,
metricTypes: dashboardStore.selectedGrid.metricTypes,
metricConfig: dashboardStore.selectedGrid.metricConfig,
relatedTrace: dashboardStore.selectedGrid.relatedTrace,
metricMode: dashboardStore.selectedGrid.metricMode,
expressions: dashboardStore.selectedGrid.expressions || [],
typesOfMQE: dashboardStore.selectedGrid.typesOfMQE || [],
subExpressions: dashboardStore.selectedGrid.subExpressions || [],
@@ -89,7 +86,6 @@ limitations under the License. -->
import type { Option } from "@/types/app";
import graphs from "../graphs";
import CustomOptions from "./widget/index";
import { MetricModes } from "../data";
export default defineComponent({
name: "WidgetEdit",
@@ -142,23 +138,6 @@ limitations under the License. -->
function applyConfig() {
dashboardStore.setConfigPanel(false);
const { metricMode } = dashboardStore.selectedGrid;
let p = {};
if (metricMode === MetricModes.Expression) {
p = {
metrics: [],
metricTypes: [],
};
} else {
p = {
expressions: [],
typesOfMQE: [],
};
}
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
...p,
});
dashboardStore.setConfigs(dashboardStore.selectedGrid);
}

View File

@@ -22,8 +22,10 @@ import Event from "./Event.vue";
import TimeRange from "./TimeRange.vue";
import ThirdPartyApp from "./ThirdPartyApp.vue";
import ContinuousProfiling from "./ContinuousProfiling.vue";
import Tab from "./Tab.vue";
export default {
Tab,
Text,
Widget,
Topology,

View File

@@ -22,16 +22,6 @@ limitations under the License. -->
@change="updateConfig({ showTableValues })"
/>
</div>
<div class="item">
<span class="label">{{ t("tableHeaderCol1") }}</span>
<el-input
class="input"
v-model="tableHeaderCol1"
size="small"
placeholder="none"
@change="updateConfig({ tableHeaderCol1 })"
/>
</div>
<div class="item">
<span class="label">{{ t("tableHeaderCol2") }}</span>
<el-input
@@ -52,7 +42,6 @@ limitations under the License. -->
const dashboardStore = useDashboardStore();
const graph = dashboardStore.selectedGrid.graph || {};
const showTableValues = ref(graph.showTableValues);
const tableHeaderCol1 = ref(graph.tableHeaderCol1);
const tableHeaderCol2 = ref(graph.tableHeaderCol2);
function updateConfig(param: { [key: string]: unknown }) {

View File

@@ -25,28 +25,20 @@ limitations under the License. -->
:clearable="true"
/>
</div>
<div>{{ t("metrics") }}</div>
<div class="flex-h">
<el-switch
v-model="isExpression"
class="mb-5"
active-text="Expressions"
inactive-text="General"
size="small"
@change="changeMetricMode"
/>
<div class="ml-5 link">
<div>{{ t("metrics") }}</div>
<div class="link">
<a target="_blank" href="https://skywalking.apache.org/docs/main/next/en/api/metrics-query-expression/">
<Icon iconName="info_outline" size="middle" />
</a>
</div>
</div>
<div v-if="isExpression && states.isList">
<div v-if="states.isList">
<span class="title">{{ t("summary") }}</span>
<span>{{ t("detail") }}</span>
</div>
<div v-for="(metric, index) in states.metrics" :key="index" class="mb-10">
<span v-if="isExpression">
<span>
<div class="expression-param" contenteditable="true" @blur="changeExpression($event, index)">
{{ metric }}
</div>
@@ -59,24 +51,6 @@ limitations under the License. -->
{{ states.subMetrics[index] }}
</div>
</span>
<span v-else>
<Selector
:value="metric"
:options="states.metricList"
size="small"
placeholder="Select a metric"
@change="changeMetrics(index, $event)"
class="selectors"
/>
<Selector
:value="states.metricTypes[index]"
:options="states.metricTypeList[index]"
size="small"
:disabled="graph.type && !states.isList && index !== 0"
@change="changeMetricType(index, $event)"
class="selectors"
/>
</span>
<el-popover placement="top" :width="400" trigger="click">
<template #reference>
<span @click="setMetricConfig(index)">
@@ -88,7 +62,7 @@ limitations under the License. -->
<span
v-show="
states.isList ||
[ProtocolTypes.ReadMetricsValues, ExpressionResultType.TIME_SERIES_VALUES as string].includes(states.metricTypes[0])
[ExpressionResultType.TIME_SERIES_VALUES as string].includes(states.metricTypes[0])
"
>
<Icon
@@ -100,13 +74,13 @@ limitations under the License. -->
/>
<Icon class="cp" iconName="remove_circle_outline" size="middle" @click="deleteMetric(index)" />
</span>
<div v-if="(states.tips || [])[index] && isExpression" class="ml-10 red sm">
<div v-if="(states.tips || [])[index]" class="ml-10 red sm">
{{ states.tips[index] }}
</div>
<div v-if="(errors || [])[index] && isExpression" class="ml-10 red sm">
<div v-if="(errors || [])[index]" class="ml-10 red sm">
{{ (errors || [])[index] }}
</div>
<div v-if="(subErrors || [])[index] && isExpression" class="ml-10 red sm">
<div v-if="(subErrors || [])[index]" class="ml-10 red sm">
{{ (subErrors || [])[index] }}
</div>
</div>
@@ -128,20 +102,14 @@ limitations under the License. -->
import type { Option } from "@/types/app";
import { useDashboardStore } from "@/store/modules/dashboard";
import {
MetricTypes,
ListChartTypes,
DefaultGraphConfig,
EntityType,
ChartTypes,
PodsChartTypes,
MetricsType,
ProtocolTypes,
ExpressionResultType,
MetricModes,
} from "../../../data";
import { ElMessage } from "element-plus";
} from "@/views/dashboard/data";
import Icon from "@/components/Icon.vue";
import { useQueryProcessor, useSourceProcessor } from "@/hooks/useMetricsProcessor";
import { useExpressionsQueryProcessor } from "@/hooks/useExpressionsProcessor";
import { useI18n } from "vue-i18n";
import type { DashboardItem, MetricConfigOpt } from "@/types/dashboard";
@@ -160,16 +128,11 @@ limitations under the License. -->
},
});
const dashboardStore = useDashboardStore();
const isExpression = ref<boolean>(dashboardStore.selectedGrid.metricMode === MetricModes.Expression);
const metrics = computed(
() => (isExpression.value ? dashboardStore.selectedGrid.expressions : dashboardStore.selectedGrid.metrics) || [],
);
const subMetrics = computed(() => (isExpression.value ? dashboardStore.selectedGrid.subExpressions : []) || []);
const subMetricTypes = computed(() => (isExpression.value ? dashboardStore.selectedGrid.subTypesOfMQE : []) || []);
const metrics = computed(() => dashboardStore.selectedGrid.expressions || []);
const subMetrics = computed(() => dashboardStore.selectedGrid.subExpressions || []);
const subMetricTypes = computed(() => dashboardStore.selectedGrid.subTypesOfMQE || []);
const graph = computed(() => dashboardStore.selectedGrid.graph || {});
const metricTypes = computed(
() => (isExpression.value ? dashboardStore.selectedGrid.typesOfMQE : dashboardStore.selectedGrid.metricTypes) || [],
);
const typesOfMQE = computed(() => dashboardStore.selectedGrid.typesOfMQE || []);
const states = reactive<{
metrics: string[];
subMetrics: string[];
@@ -177,17 +140,15 @@ limitations under the License. -->
metricTypes: string[];
metricTypeList: Option[][];
isList: boolean;
metricList: (Option & { type: string })[];
dashboardName: string;
dashboardList: ((DashboardItem & { label: string; value: string }) | any)[];
tips: string[];
subTips: string[];
}>({
metrics: metrics.value.length ? metrics.value : [""],
metricTypes: metricTypes.value.length ? metricTypes.value : [""],
metricTypes: typesOfMQE.value.length ? typesOfMQE.value : [""],
metricTypeList: [],
isList: false,
metricList: [],
dashboardName: graph.value.dashboardName,
dashboardList: [{ label: "", value: "" }],
tips: [],
@@ -199,16 +160,13 @@ limitations under the License. -->
unit: "",
label: "",
labelsIndex: "",
calculation: "",
sortOrder: "DES",
});
states.isList = ListChartTypes.includes(graph.value.type);
const defaultLen = ref<number>(states.isList ? 5 : 20);
const backupMetricConfig = ref<MetricConfigOpt[]>([]);
setDashboards();
setMetricType();
const setVisTypes = computed(() => {
let graphs = [];
@@ -223,70 +181,6 @@ limitations under the License. -->
return graphs;
});
async function setMetricType(chart?: any) {
const g = chart || dashboardStore.selectedGrid.graph || {};
let arr: any[] = states.metricList;
if (!chart) {
const json = await dashboardStore.fetchMetricList();
if (json.errors) {
ElMessage.error(json.errors);
return;
}
arr = json.data.metrics;
}
states.metricList = (arr || []).filter((d: { type: string }) => {
if (states.isList) {
if (d.type === MetricsType.REGULAR_VALUE || d.type === MetricsType.LABELED_VALUE) {
return d;
}
} else if (g.type === "Table") {
if (d.type === MetricsType.LABELED_VALUE || d.type === MetricsType.REGULAR_VALUE) {
return d;
}
} else {
return d;
}
});
if (isExpression.value) {
if (states.metrics && states.metrics[0]) {
queryMetrics();
} else {
emit("update", {});
}
return;
}
const metrics: any = states.metricList.filter((d: { value: string; type: string }) =>
states.metrics.includes(d.value),
);
if (metrics.length) {
// keep states.metrics index
const m = metrics.map((d: { value: string }) => d.value);
states.metrics = states.metrics.filter((d) => m.includes(d));
} else {
states.metrics = [""];
states.metricTypes = [""];
}
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
metrics: states.metrics,
metricTypes: states.metricTypes,
graph: g,
});
states.metricTypeList = [];
for (const metric of metrics) {
if (states.metrics.includes(metric.value)) {
const arr = setMetricTypeList(metric.type);
states.metricTypeList.push(arr);
}
}
if (states.metrics && states.metrics[0]) {
queryMetrics();
} else {
emit("update", {});
}
}
function setDashboards(type?: string) {
const chart = type || (dashboardStore.selectedGrid.graph && dashboardStore.selectedGrid.graph.type);
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
@@ -308,6 +202,11 @@ limitations under the License. -->
}, []);
states.dashboardList = arr.length ? arr : [{ label: "", value: "" }];
if (states.metrics && states.metrics[0]) {
queryMetrics();
} else {
emit("update", {});
}
}
function changeChartType(item: Option) {
@@ -316,8 +215,6 @@ limitations under the License. -->
if (states.isList) {
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
metrics: [""],
metricTypes: [""],
expressions: [""],
typesOfMQE: [""],
});
@@ -326,97 +223,20 @@ limitations under the License. -->
defaultLen.value = 5;
}
if (isExpression.value) {
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
graph: chart,
});
} else {
setMetricType(chart);
}
setDashboards(chart.type);
states.dashboardName = "";
defaultLen.value = 10;
}
function changeMetrics(index: number, arr: (Option & { type: string })[] | any) {
if (!arr.length) {
states.metricTypeList = [];
states.metricTypes = [];
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
...{ metricTypes: states.metricTypes, metrics: states.metrics },
});
return;
}
states.metrics[index] = arr[0].value;
const typeOfMetrics = arr[0].type;
states.metricTypeList[index] = setMetricTypeList(typeOfMetrics);
states.metricTypes[index] = MetricTypes[typeOfMetrics][0].value;
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
...{ metricTypes: states.metricTypes, metrics: states.metrics },
});
if (states.isList) {
return;
}
queryMetrics();
}
function changeMetricType(index: number, opt: Option[] | any) {
const metric = states.metricList.filter((d: Option) => states.metrics[index] === d.value)[0] || {};
const l = setMetricTypeList(metric.type);
if (states.isList) {
states.metricTypes[index] = opt[0].value;
states.metricTypeList[index] = l;
} else {
states.metricTypes = states.metricTypes.map((d: string) => {
d = opt[0].value;
return d;
});
states.metricTypeList = states.metricTypeList.map((d: Option[]) => {
d = l;
return d;
});
}
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
...{ metricTypes: states.metricTypes },
});
if (states.isList) {
return;
}
queryMetrics();
}
async function queryMetrics() {
if (states.isList) {
return;
}
if (isExpression.value) {
queryMetricsWithExpressions();
return;
}
const { metricConfig, metricTypes, metrics } = dashboardStore.selectedGrid;
if (!(metrics && metrics[0] && metricTypes && metricTypes[0])) {
return emit("update", {});
}
const params = useQueryProcessor({ ...states, metricConfig });
if (!params) {
emit("update", {});
return;
}
emit("loading", true);
const json = await dashboardStore.fetchMetricValue(params);
emit("loading", false);
if (json.errors) {
ElMessage.error(json.errors);
return;
}
const source = useSourceProcessor(json, { ...states, metricConfig });
emit("update", source);
}
async function queryMetricsWithExpressions() {
@@ -432,7 +252,6 @@ limitations under the License. -->
...dashboardStore.selectedGrid,
typesOfMQE: states.metricTypes,
});
emit("update", params.source || {});
}
@@ -454,31 +273,23 @@ limitations under the License. -->
function addMetric() {
states.metrics.push("");
states.tips.push("");
if (isExpression.value && states.isList) {
if (states.isList) {
states.subMetrics.push("");
states.subTips.push("");
}
if (!states.isList) {
states.metricTypes.push(states.metricTypes[0]);
if (!isExpression.value) {
states.metricTypeList.push(states.metricTypeList[0]);
}
return;
}
states.metricTypes.push("");
if (isExpression.value && states.isList) {
states.subMetricTypes.push("");
}
}
function deleteMetric(index: number) {
if (states.metrics.length === 1) {
states.metrics = [""];
states.metricTypes = [""];
states.tips = [""];
let v = {};
if (isExpression.value) {
v = { typesOfMQE: states.metricTypes, expressions: states.metrics };
let v: any = { typesOfMQE: states.metricTypes, expressions: states.metrics };
if (states.isList) {
states.subMetrics = [""];
states.subMetricTypes = [""];
@@ -489,9 +300,6 @@ limitations under the License. -->
subExpressions: states.subMetrics,
};
}
} else {
v = { metricTypes: states.metricTypes, metrics: states.metrics };
}
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
...v,
@@ -505,7 +313,6 @@ limitations under the License. -->
const config = dashboardStore.selectedGrid.metricConfig || [];
const metricConfig = config[index] ? config.splice(index, 1) : config;
let p = {};
if (isExpression.value) {
if (states.isList) {
states.subMetrics.splice(index, 1);
states.subMetricTypes.splice(index, 1);
@@ -518,24 +325,14 @@ limitations under the License. -->
subExpressions: states.subMetrics,
};
}
} else {
p = { metricTypes: states.metricTypes, metrics: states.metrics };
}
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
...p,
metricConfig,
});
queryMetrics();
}
function setMetricTypeList(type: string) {
if (type !== MetricsType.REGULAR_VALUE) {
return MetricTypes[type];
}
if (states.isList || graph.value.type === "Table") {
return [MetricTypes.REGULAR_VALUE[0], MetricTypes.REGULAR_VALUE[1]];
}
return MetricTypes[type];
}
function setMetricConfig(index: number) {
const n = {
unit: "",
@@ -553,20 +350,6 @@ limitations under the License. -->
...dashboardStore.selectedGrid.metricConfig[index],
};
}
function changeMetricMode() {
states.metrics = metrics.value.length ? metrics.value : [""];
states.subMetrics = subMetrics.value.length ? subMetrics.value : [""];
states.metricTypes = metricTypes.value.length ? metricTypes.value : [""];
states.subMetricTypes = subMetricTypes.value.length ? subMetricTypes.value : [""];
const config = dashboardStore.selectedGrid.metricConfig;
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
metricMode: isExpression.value ? MetricModes.Expression : MetricModes.General,
metricConfig: backupMetricConfig.value,
});
backupMetricConfig.value = config;
queryMetrics();
}
async function changeExpression(event: any, index: number) {
const params = (event.target.textContent || "").replace(/\s+/g, "");
@@ -651,5 +434,6 @@ limitations under the License. -->
.link {
cursor: pointer;
color: $active-color;
padding-left: 2px;
}
</style>

View File

@@ -28,7 +28,7 @@ limitations under the License. -->
"
/>
</div>
<div class="item mb-10" v-if="hasLabel || isExpression">
<div class="item mb-10">
<span class="label">{{ t("labels") }}</span>
<el-input
class="input"
@@ -42,7 +42,7 @@ limitations under the License. -->
"
/>
</div>
<div class="item mb-10" v-if="isList && isExpression">
<div class="item mb-10" v-if="isList">
<span class="label">{{ t("detailLabel") }}</span>
<el-input
class="input"
@@ -56,30 +56,6 @@ limitations under the License. -->
"
/>
</div>
<div class="item mb-10" v-if="[ProtocolTypes.ReadLabeledMetricsValues].includes(metricType) && !isExpression">
<span class="label">{{ t("labelsIndex") }}</span>
<el-input
class="input"
v-model="currentMetric.labelsIndex"
size="small"
placeholder="auto"
@change="
updateConfig(index, {
labelsIndex: encodeURIComponent(currentMetric.labelsIndex || ''),
})
"
/>
</div>
<div class="item mb-10" v-show="!isExpression">
<span class="label">{{ t("aggregation") }}</span>
<SelectSingle
:value="currentMetric.calculation"
:options="CalculationOpts"
@change="changeConfigs(index, { calculation: $event })"
class="selectors"
:clearable="true"
/>
</div>
<div class="item mb-10" v-show="isTopn">
<span class="label">{{ t("sortOrder") }}</span>
<SelectSingle
@@ -108,10 +84,9 @@ limitations under the License. -->
import { ref, watch, computed } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { SortOrder, CalculationOpts, MetricModes } from "../../../data";
import { SortOrder, ExpressionResultType, ListChartTypes } from "@/views/dashboard/data";
import { useDashboardStore } from "@/store/modules/dashboard";
import type { MetricConfigOpt } from "@/types/dashboard";
import { ListChartTypes, ProtocolTypes } from "../../../data";
/*global defineEmits, defineProps */
const props = defineProps({
@@ -124,30 +99,17 @@ limitations under the License. -->
const { t } = useI18n();
const emit = defineEmits(["update"]);
const dashboardStore = useDashboardStore();
const isExpression = ref<boolean>(dashboardStore.selectedGrid.metricMode === MetricModes.Expression);
const currentMetric = ref<MetricConfigOpt>({
...props.currentMetricConfig,
topN: props.currentMetricConfig.topN || 10,
});
const metricTypes = computed(
() => (isExpression.value ? dashboardStore.selectedGrid.typesOfMQE : dashboardStore.selectedGrid.metricTypes) || [],
);
const metricType = computed(() => metricTypes.value[props.index]);
const hasLabel = computed(() => {
const graph = dashboardStore.selectedGrid.graph || {};
return (
ListChartTypes.includes(graph.type) ||
[ProtocolTypes.ReadLabeledMetricsValues, ProtocolTypes.ReadMetricsValues].includes(metricType.value)
);
});
const metricTypes = computed(() => dashboardStore.selectedGrid.typesOfMQE || []);
const isList = computed(() => {
const graph = dashboardStore.selectedGrid.graph || {};
return ListChartTypes.includes(graph.type);
});
const isTopn = computed(() =>
[ProtocolTypes.SortMetrics, ProtocolTypes.ReadSampledRecords, ProtocolTypes.ReadRecords].includes(
metricTypes.value[props.index],
),
[ExpressionResultType.RECORD_LIST, ExpressionResultType.SORTED_LIST].includes(metricTypes.value[props.index]),
);
function updateConfig(index: number, param: { [key: string]: string }) {
@@ -170,7 +132,6 @@ limitations under the License. -->
watch(
() => props.currentMetricConfig,
() => {
isExpression.value = dashboardStore.selectedGrid.metricMode === MetricModes.Expression;
currentMetric.value = {
...props.currentMetricConfig,
topN: Number(props.currentMetricConfig.topN) || 10,

View File

@@ -15,11 +15,12 @@ limitations under the License. -->
<template>
<div class="flex-h tab-header">
<div class="tabs scroll_bar_style" @click="handleClick">
<template v-for="(child, idx) in data.children || []">
<span
v-for="(child, idx) in data.children || []"
:key="idx"
:class="{ active: activeTabIndex === idx }"
@click="clickTabs($event, idx)"
v-if="child.enable !== false"
>
<input
@click="editTabName($event, idx)"
@@ -38,6 +39,17 @@ limitations under the License. -->
v-if="dashboardStore.editMode && canEditTabName"
/>
</span>
</template>
<template v-for="(child, idx) in data.children || []">
<span
:key="idx"
:style="{ width: getStringWidth(child.name) + 'px' }"
v-if="child.enable === false"
class="tab-diabled"
>
{{ child.name }}
</span>
</template>
<span class="tab-icons">
<el-tooltip content="Copy Link" placement="bottom">
<i @click="copyLink">
@@ -56,10 +68,13 @@ limitations under the License. -->
<div class="operations" v-if="dashboardStore.editMode">
<el-dropdown placement="bottom" trigger="click" :width="200">
<span class="icon-operation">
<Icon iconName="ellipsis_v" size="middle" />
<Icon class="icon-tool" iconName="ellipsis_v" size="middle" />
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="editConfig">
<span>{{ t("edit") }}</span>
</el-dropdown-item>
<el-dropdown-item @click="canEditTabName = true">
<span class="edit-tab">{{ t("editTab") }}</span>
</el-dropdown-item>
@@ -105,15 +120,16 @@ limitations under the License. -->
</div>
</template>
<script lang="ts">
import { ref, watch, defineComponent, toRefs } from "vue";
import { ref, watch, defineComponent, toRefs, onUnmounted } from "vue";
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
import type { PropType } from "vue";
import type { LayoutConfig } from "@/types/dashboard";
import { useDashboardStore } from "@/store/modules/dashboard";
import controls from "./tab";
import { dragIgnoreFrom } from "../data";
import { dragIgnoreFrom, WidgetType } from "../data";
import copy from "@/utils/copy";
import { useExpressionsQueryProcessor } from "@/hooks/useExpressionsProcessor";
const props = {
data: {
@@ -137,13 +153,17 @@ limitations under the License. -->
const editTabIndex = ref<number>(NaN); // edit tab item name
const canEditTabName = ref<boolean>(false);
const needQuery = ref<boolean>(false);
dashboardStore.setActiveTabIndex(activeTabIndex);
const l = dashboardStore.layout.findIndex((d: LayoutConfig) => d.i === props.data.i);
dashboardStore.setActiveTabIndex(activeTabIndex.value);
if (dashboardStore.layout[l].children.length) {
dashboardStore.setCurrentTabItems(dashboardStore.layout[l].children[activeTabIndex.value].children);
const tab = dashboardStore.layout[l].children[activeTabIndex.value];
dashboardStore.setCurrentTabItems(
tab.enable === false ? [] : dashboardStore.layout[l].children[activeTabIndex.value].children,
);
dashboardStore.setActiveTabIndex(activeTabIndex.value, props.data.i);
}
queryExpressions();
function clickTabs(e: Event, idx: number) {
e.stopPropagation();
@@ -224,6 +244,57 @@ limitations under the License. -->
copy(path);
}
document.body.addEventListener("click", handleClick, false);
function editConfig() {
dashboardStore.setConfigPanel(true);
dashboardStore.selectWidget(props.data);
}
async function queryExpressions() {
const tabsProps = props.data;
const metrics = [];
for (const child of tabsProps.children || []) {
child.expression && metrics.push(child.expression);
}
if (!metrics.length) {
return;
}
const params: { [key: string]: any } = (await useExpressionsQueryProcessor({ metrics })) || {};
for (const child of tabsProps.children || []) {
if (params.source[child.expression || ""]) {
child.enable =
!!Number(params.source[child.expression || ""]) &&
!!child.children.find((item: { type: string }) => item.type === WidgetType.Widget);
} else {
child.enable = true;
}
}
dashboardStore.setConfigs(tabsProps);
if (((props.data.children || [])[activeTabIndex.value] || {}).enable === false) {
const index = (props.data.children || []).findIndex((tab: any) => tab.enable !== false) || 0;
const items = ((props.data.children || [])[index] || {}).children;
dashboardStore.setCurrentTabItems(items || []);
dashboardStore.activeGridItem(0);
activeTabIndex.value = index;
dashboardStore.setActiveTabIndex(activeTabIndex.value);
needQuery.value = true;
}
}
onUnmounted(() => {
document.body.removeEventListener("click", handleClick, false);
});
watch(
() => (props.data.children || []).map((d: any) => d.expression),
(old: string[], data: string[]) => {
if (JSON.stringify(data) === JSON.stringify(old)) {
return;
}
queryExpressions();
},
);
watch(
() => dashboardStore.activedGridItem,
(data) => {
@@ -266,6 +337,7 @@ limitations under the License. -->
clickTabs,
copyLink,
getStringWidth,
editConfig,
...toRefs(props),
activeTabWidget,
dashboardStore,
@@ -288,6 +360,7 @@ limitations under the License. -->
white-space: nowrap;
overflow-y: hidden;
padding: 0 10px;
display: inline-flex;
span {
display: inline-block;
@@ -310,6 +383,18 @@ limitations under the License. -->
background-color: $theme-background;
}
.tab-diabled {
max-width: 150px;
outline: none;
color: $disabled-color;
font-style: normal;
overflow: hidden;
text-overflow: ellipsis;
margin-right: 20px;
background-color: $theme-background;
cursor: not-allowed;
}
.tab-icons {
i {
margin-right: 3px;

View File

@@ -25,24 +25,36 @@ limitations under the License. -->
</div>
</el-popover>
<div class="header">
<Filter :needQuery="needQuery" :data="data" />
<Filter :needQuery="needQuery" :data="data" @get="getService" @search="popSegmentList" />
</div>
<div class="trace flex-h">
<TraceList />
<TraceDetail />
<TraceList class="trace-list" :style="`width: ${currentWidth}px;`" />
<div
@mouseover="showIcon = true"
@mouseout="showIcon = false"
@mousedown="mousedown($event)"
@mouseup="mouseup($event)"
>
<div class="trace-line" />
<span class="trace-icon" v-show="showIcon" @mousedown="triggerArrow" @mouseup="stopObserve($event)">
<Icon class="trace-arrow" :icon-name="isLeft ? 'chevron-left' : 'chevron-right'" size="lg" />
</span>
</div>
<TraceDetail :serviceId="serviceId" />
</div>
</div>
</template>
<script lang="ts" setup>
import { provide } from "vue";
import { provide, ref, onMounted, onUnmounted } from "vue";
import type { PropType } from "vue";
import Filter from "../related/trace/Filter.vue";
import TraceList from "../related/trace/TraceList.vue";
import TraceDetail from "../related/trace/Detail.vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { mutationObserver } from "@/utils/mutation";
/*global defineProps */
/* global defineProps */
const props = defineProps({
data: {
type: Object as PropType<any>,
@@ -52,11 +64,76 @@ limitations under the License. -->
needQuery: { type: Boolean, default: true },
});
provide("options", props.data);
const defaultWidth = 280,
minArrowLeftWidth = 120;
const serviceId = ref<string>("");
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const isLeft = ref<boolean>(true);
const showIcon = ref<boolean>(false);
const currentWidth = ref<number>(defaultWidth);
const isDrag = ref<boolean>(false);
function removeWidget() {
dashboardStore.removeControls(props.data);
}
function getService(id: string) {
serviceId.value = id;
}
// When click the arrow, the width of the segment list is determined by the direction it points to.
function triggerArrow() {
currentWidth.value = isLeft.value ? 0 : defaultWidth;
isLeft.value = !isLeft.value;
startObserve();
}
function popSegmentList() {
if (currentWidth.value >= defaultWidth) {
return;
}
currentWidth.value = defaultWidth;
isLeft.value = true;
}
function startObserve() {
mutationObserver.observe("trigger-resize", document.querySelector(".trace-list")!, {
attributes: true,
attributeFilter: ["style"],
});
}
function stopObserve(event: MouseEvent) {
mutationObserver.disconnect("trigger-resize");
event.stopPropagation();
}
const mousemove = (event: MouseEvent) => {
if (!isDrag.value) {
return;
}
const diffX = event.clientX;
let leftWidth = document.querySelector(".trace-list")!.getBoundingClientRect();
currentWidth.value = diffX - leftWidth.left;
isLeft.value = currentWidth.value >= minArrowLeftWidth;
};
const mouseup = (event: MouseEvent) => {
showIcon.value = false;
isDrag.value = false;
stopObserve(event);
};
const mousedown = (event: MouseEvent) => {
if ((event.target as HTMLDivElement)?.className === "trace-line") {
isDrag.value = true;
startObserve();
event.stopPropagation();
}
};
onMounted(() => {
document.addEventListener("mousedown", mousedown);
document.addEventListener("mousemove", mousemove);
document.addEventListener("mouseup", mouseup);
});
onUnmounted(() => {
document.removeEventListener("mousedown", mousedown);
document.removeEventListener("mousemove", mousemove);
document.removeEventListener("mouseup", mouseup);
});
</script>
<style lang="scss" scoped>
.trace-wrapper {
@@ -64,7 +141,6 @@ limitations under the License. -->
height: 100%;
font-size: $font-size-smaller;
position: relative;
overflow: auto;
}
.delete {
@@ -77,7 +153,7 @@ limitations under the License. -->
padding: 10px;
font-size: $font-size-smaller;
border-bottom: 1px solid $border-color;
min-width: 1200px;
min-width: 1000px;
}
.tools {
@@ -97,6 +173,42 @@ limitations under the License. -->
min-height: calc(100% - 150px);
width: 100%;
overflow: auto;
min-width: 1200px;
min-width: 1000px;
}
.trace-list {
max-width: 480px;
}
.trace-line {
position: relative;
width: 2px;
height: 100%;
background-color: #e8e8e8;
cursor: ew-resize;
&:hover {
color: $active-color;
background-color: $active-background;
}
}
.trace-icon {
position: absolute;
cursor: pointer;
top: calc(50% - 15px);
text-align: center;
width: 24px;
height: 24px;
transform: translateX(-11px);
line-height: 24px;
border-radius: 50%;
background-color: $layout-background;
box-shadow: 0 3px 5px rgb(45 60 80 / 20%);
}
.trace-arrow {
padding-bottom: 1px;
color: $active-color;
}
</style>

View File

@@ -51,15 +51,12 @@ limitations under the License. -->
:data="state.source"
:config="{
...data.graph,
metrics: data.metrics || [''],
metricTypes: data.metricTypes || [''],
i: data.i,
id: data.id,
metricConfig: data.metricConfig || [],
filters: data.filters || {},
relatedTrace: data.relatedTrace || {},
associate: data.associate || [],
metricMode: data.metricMode,
expressions: data.expressions || [],
typesOfMQE: typesOfMQE || [],
subExpressions: data.subExpressions || [],
@@ -81,12 +78,10 @@ limitations under the License. -->
import { useSelectorStore } from "@/store/modules/selectors";
import graphs from "../graphs";
import { useI18n } from "vue-i18n";
import { useQueryProcessor, useSourceProcessor } from "@/hooks/useMetricsProcessor";
import { useExpressionsQueryProcessor } from "@/hooks/useExpressionsProcessor";
import { EntityType, ListChartTypes } from "../data";
import type { EventParams } from "@/types/dashboard";
import getDashboard from "@/hooks/useDashboardsSession";
import { MetricModes } from "../data";
const props = {
data: {
@@ -121,9 +116,6 @@ limitations under the License. -->
}
async function queryMetrics() {
const isExpression = props.data.metricMode === MetricModes.Expression;
if (isExpression) {
loading.value = true;
const e = {
metrics: props.data.expressions || [],
@@ -133,26 +125,6 @@ limitations under the License. -->
loading.value = false;
state.source = params.source || {};
typesOfMQE.value = params.typesOfMQE;
return;
}
const params = await useQueryProcessor({ ...props.data });
if (!params) {
state.source = {};
return;
}
loading.value = true;
const json = await dashboardStore.fetchMetricValue(params);
loading.value = false;
if (!json) {
return;
}
const d = {
metrics: props.data.metrics || [],
metricTypes: props.data.metricTypes || [],
metricConfig: props.data.metricConfig || [],
};
state.source = await useSourceProcessor(json, d);
}
function removeWidget() {
@@ -188,7 +160,7 @@ limitations under the License. -->
dashboardStore.selectWidget(props.data);
}
watch(
() => [props.data.metricTypes, props.data.metrics, props.data.expressions],
() => props.data.expressions,
() => {
if (!dashboardStore.selectedGrid) {
return;

View File

@@ -34,29 +34,6 @@ export const ChartTypes = [
{ label: "Endpoint List", value: "EndpointList" },
{ label: "Instance List", value: "InstanceList" },
];
export const MetricChartType: any = {
readMetricsValue: [{ label: "Card", value: "Card" }],
readMetricsValues: [
{ label: "Bar", value: "Bar" },
{ label: "Line", value: "Line" },
{ label: "Area", value: "Area" },
],
sortMetrics: [{ label: "Top List", value: "TopList" }],
readLabeledMetricsValues: [{ label: "Line", value: "Line" }],
readHeatMap: [{ label: "Heat Map", value: "HeatMap" }],
readSampledRecords: [{ label: "Top List", value: "TopList" }],
readRecords: [{ label: "Top List", value: "TopList" }],
};
export enum ProtocolTypes {
ReadRecords = "readRecords",
ReadSampledRecords = "readSampledRecords",
SortMetrics = "sortMetrics",
ReadLabeledMetricsValues = "readLabeledMetricsValues",
ReadHeatMap = "readHeatMap",
ReadMetricsValues = "readMetricsValues",
ReadMetricsValue = "readMetricsValue",
}
export enum ExpressionResultType {
UNKNOWN = "UNKNOWN",
SINGLE_VALUE = "SINGLE_VALUE",
@@ -93,7 +70,6 @@ export const DefaultGraphConfig: { [key: string]: any } = {
Table: {
type: "Table",
showTableValues: true,
tableHeaderCol1: "",
tableHeaderCol2: "",
},
TopList: {
@@ -125,34 +101,6 @@ export const DefaultGraphConfig: { [key: string]: any } = {
},
};
export enum MetricsType {
UNKNOWN = "UNKNOWN",
REGULAR_VALUE = "REGULAR_VALUE",
LABELED_VALUE = "LABELED_VALUE",
HEATMAP = "HEATMAP",
SAMPLED_RECORD = "SAMPLED_RECORD",
}
export const MetricTypes: {
[key: string]: Array<{ label: string; value: string }>;
} = {
REGULAR_VALUE: [
{ label: "read all values in the duration", value: "readMetricsValues" },
{
label: "read the single value in the duration",
value: "readMetricsValue",
},
{ label: "get sorted top N values", value: "sortMetrics" },
],
LABELED_VALUE: [
{
label: "read all values of labels in the duration",
value: "readLabeledMetricsValues",
},
],
HEATMAP: [{ label: "read heatmap values in the duration", value: "readHeatMap" }],
SAMPLED_RECORD: [{ label: "get sorted topN values", value: "readRecords" }],
};
export enum MetricCatalog {
SERVICE = "Service",
SERVICE_INSTANCE = "ServiceInstance",
@@ -188,87 +136,104 @@ export const SortOrder = [
{ label: "DES", value: "DES" },
{ label: "ASC", value: "ASC" },
];
export enum WidgetType {
Widget = "Widget",
Topology = "Topology",
Tab = "Tab",
Text = "Text",
TimeRange = "TimeRange",
Trace = "Trace",
Log = "Log",
Profile = "Profile",
Ebpf = "Ebpf",
DemandLog = "DemandLog",
Event = "Event",
NetworkProfiling = "NetworkProfiling",
ContinuousProfiling = "ContinuousProfiling",
ThirdPartyApp = "ThirdPartyApp",
TaskTimeline = "TaskTimeline",
}
export const AllTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "assignment", content: "Add Log", id: "addLog" },
{ name: "add_iframe", content: "Add Iframe", id: "addIframe" },
{ name: "playlist_add", content: "Add Widget", id: WidgetType.Widget },
{ name: "all_inbox", content: "Add Tabs", id: WidgetType.Tab },
{ name: "library_books", content: "Add Text", id: WidgetType.Text },
{ name: "device_hub", content: "Add Topology", id: WidgetType.Topology },
{ name: "merge", content: "Add Trace", id: WidgetType.Trace },
{ name: "assignment", content: "Add Log", id: WidgetType.Log },
{ name: "add_iframe", content: "Add Iframe", id: WidgetType.ThirdPartyApp },
];
export const ServiceTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "timeline", content: "Add Trace Profiling", id: "addProfile" },
{ name: "insert_chart", content: "Add eBPF Profiling", id: "addEbpf" },
{ name: "continuous_profiling", content: "Add Continuous Profiling", id: "addContinuousProfiling" },
{ name: "assignment", content: "Add Log", id: "addLog" },
{ name: "demand", content: "Add On Demand Log", id: "addDemandLog" },
{ name: "event", content: "Add Event", id: "addEvent" },
{ name: "add_iframe", content: "Add Iframe", id: "addIframe" },
{ name: "playlist_add", content: "Add Widget", id: WidgetType.Widget },
{ name: "all_inbox", content: "Add Tabs", id: WidgetType.Tab },
{ name: "library_books", content: "Add Text", id: WidgetType.Text },
{ name: "device_hub", content: "Add Topology", id: WidgetType.Topology },
{ name: "merge", content: "Add Trace", id: WidgetType.Trace },
{ name: "timeline", content: "Add Trace Profiling", id: WidgetType.Profile },
{ name: "insert_chart", content: "Add eBPF Profiling", id: WidgetType.Ebpf },
{ name: "continuous_profiling", content: "Add Continuous Profiling", id: WidgetType.ContinuousProfiling },
{ name: "assignment", content: "Add Log", id: WidgetType.Log },
{ name: "demand", content: "Add On Demand Log", id: WidgetType.DemandLog },
{ name: "event", content: "Add Event", id: WidgetType.Event },
{ name: "add_iframe", content: "Add Iframe", id: WidgetType.ThirdPartyApp },
];
export const InstanceTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "assignment", content: "Add Log", id: "addLog" },
{ name: "demand", content: "Add On Demand Log", id: "addDemandLog" },
{ name: "event", content: "Add Event", id: "addEvent" },
{ name: "add_iframe", content: "Add Iframe", id: "addIframe" },
{ name: "playlist_add", content: "Add Widget", id: WidgetType.Widget },
{ name: "all_inbox", content: "Add Tabs", id: WidgetType.Tab },
{ name: "library_books", content: "Add Text", id: WidgetType.Text },
{ name: "merge", content: "Add Trace", id: WidgetType.Trace },
{ name: "assignment", content: "Add Log", id: WidgetType.Log },
{ name: "demand", content: "Add On Demand Log", id: WidgetType.DemandLog },
{ name: "event", content: "Add Event", id: WidgetType.Event },
{ name: "add_iframe", content: "Add Iframe", id: WidgetType.ThirdPartyApp },
{
name: "timeline",
content: "Add Network Profiling",
id: "addNetworkProfiling",
id: WidgetType.NetworkProfiling,
},
];
export const EndpointTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "assignment", content: "Add Log", id: "addLog" },
{ name: "event", content: "Add Event", id: "c" },
{ name: "add_iframe", content: "Add Iframe", id: "addIframe" },
{ name: "playlist_add", content: "Add Widget", id: WidgetType.Widget },
{ name: "all_inbox", content: "Add Tabs", id: WidgetType.Tab },
{ name: "library_books", content: "Add Text", id: WidgetType.Text },
{ name: "device_hub", content: "Add Topology", id: WidgetType.Topology },
{ name: "merge", content: "Add Trace", id: WidgetType.Trace },
{ name: "assignment", content: "Add Log", id: WidgetType.Log },
{ name: "event", content: "Add Event", id: WidgetType.Event },
{ name: "add_iframe", content: "Add Iframe", id: WidgetType.ThirdPartyApp },
];
export const ProcessTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "task_timeline", content: "Add Task Timeline", id: "addTaskTimeline" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "add_iframe", content: "Add Iframe", id: "addIframe" },
{ name: "playlist_add", content: "Add Widget", id: WidgetType.Widget },
{ name: "all_inbox", content: "Add Tabs", id: WidgetType.Tab },
{ name: "task_timeline", content: "Add Task Timeline", id: WidgetType.TaskTimeline },
{ name: "library_books", content: "Add Text", id: WidgetType.Text },
{ name: "add_iframe", content: "Add Iframe", id: WidgetType.ThirdPartyApp },
];
export const ProcessRelationTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "add_iframe", content: "Add Iframe", id: "addIframe" },
{ name: "playlist_add", content: "Add Widget", id: WidgetType.Widget },
{ name: "all_inbox", content: "Add Tabs", id: WidgetType.Tab },
{ name: "library_books", content: "Add Text", id: WidgetType.Text },
{ name: "add_iframe", content: "Add Iframe", id: WidgetType.ThirdPartyApp },
];
export const ServiceRelationTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "add_iframe", content: "Add Iframe", id: "addIframe" },
{ name: "playlist_add", content: "Add Widget", id: WidgetType.Widget },
{ name: "all_inbox", content: "Add Tabs", id: WidgetType.Tab },
{ name: "library_books", content: "Add Text", id: WidgetType.Text },
{ name: "device_hub", content: "Add Topology", id: WidgetType.Topology },
{ name: "add_iframe", content: "Add Iframe", id: WidgetType.ThirdPartyApp },
];
export const EndpointRelationTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "add_iframe", content: "Add Iframe", id: "addIframe" },
{ name: "playlist_add", content: "Add Widget", id: WidgetType.Widget },
{ name: "all_inbox", content: "Add Tabs", id: WidgetType.Tab },
{ name: "library_books", content: "Add Text", id: WidgetType.Text },
{ name: "add_iframe", content: "Add Iframe", id: WidgetType.ThirdPartyApp },
];
export const InstanceRelationTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "add_iframe", content: "Add Iframe", id: "addIframe" },
{ name: "playlist_add", content: "Add Widget", id: WidgetType.Widget },
{ name: "all_inbox", content: "Add Tabs", id: WidgetType.Tab },
{ name: "library_books", content: "Add Text", id: WidgetType.Text },
{ name: "device_hub", content: "Add Topology", id: WidgetType.Topology },
{ name: "add_iframe", content: "Add Iframe", id: WidgetType.ThirdPartyApp },
];
export const ScopeType = [
@@ -276,19 +241,6 @@ export const ScopeType = [
{ value: "Endpoint", label: "Endpoint", key: 3 },
{ value: "ServiceInstance", label: "Service Instance", key: 3 },
];
export const LegendConditions = [
{ label: "&&", value: "and" },
{ label: "||", value: "or" },
];
export const MetricConditions = [
{ label: ">", value: ">" },
{ label: "<", value: "<" },
];
export enum LegendOpt {
NAME = "name",
VALUE = "value",
CONDITION = "condition",
}
export const DepthList = [1, 2, 3, 4, 5].map((item: number) => ({
value: item,
label: String(item),
@@ -313,24 +265,6 @@ export const TextColors: { [key: string]: string } = {
purple: "#bf99f8",
};
export const CalculationOpts = [
{ label: "Percentage", value: "percentage" },
{ label: "Apdex", value: "apdex" },
{ label: "Avg-preview", value: "average" },
{ label: "Percentage + Avg-preview", value: "percentageAvg" },
{ label: "Apdex + Avg-preview", value: "apdexAvg" },
{ label: "Byte to KB", value: "byteToKB" },
{ label: "Byte to MB", value: "byteToMB" },
{ label: "Byte to GB", value: "byteToGB" },
{
label: "Milliseconds to YYYY-MM-DD HH:mm:ss",
value: "convertMilliseconds",
},
{ label: "Seconds to YYYY-MM-DD HH:mm:ss", value: "convertSeconds" },
{ label: "Milliseconds to seconds", value: "msTos" },
{ label: "Seconds to days", value: "secondToDay" },
{ label: "Nanoseconds to milliseconds", value: "nanosecondToMillisecond" },
];
export const RefIdTypes = [
{ label: "Trace ID", value: "traceId" },
{ label: "None", value: "none" },
@@ -341,11 +275,11 @@ export const RefreshOptions = [
{ label: "Last 7 days", value: "7", step: "DAY" },
];
export enum MetricModes {
Expression = "Expression",
General = "General",
}
export enum CallTypes {
Server = "SERVER",
Client = "CLIENT",
}
export enum ConfigFieldTypes {
ISDEFAULT = "ISDEFAULT",
NAME = "NAME",
}

View File

@@ -40,8 +40,7 @@ limitations under the License. -->
:config="{
...config,
metricConfig,
metricTypes,
metricMode,
typesOfMQE,
}"
v-if="colMetrics.length"
/>
@@ -58,9 +57,8 @@ limitations under the License. -->
import type { EndpointListConfig } from "@/types/dashboard";
import type { Endpoint } from "@/types/selector";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useQueryPodsMetrics, usePodsSource } from "@/hooks/useMetricsProcessor";
import { useExpressionsQueryPodsMetrics } from "@/hooks/useExpressionsProcessor";
import { EntityType, MetricModes } from "../data";
import { EntityType } from "../data";
import router from "@/router";
import getDashboard from "@/hooks/useDashboardsSession";
import type { MetricConfigOpt } from "@/types/dashboard";
@@ -75,9 +73,6 @@ limitations under the License. -->
type: Object as PropType<
EndpointListConfig & {
i: string;
metrics: string[];
metricTypes: string[];
metricMode: string;
expressions: string[];
typesOfMQE: string[];
subExpressions: string[];
@@ -85,8 +80,6 @@ limitations under the License. -->
} & { metricConfig: MetricConfigOpt[] }
>,
default: () => ({
metrics: [],
metricTypes: [],
dashboardName: "",
fontSize: 12,
i: "",
@@ -106,8 +99,7 @@ limitations under the License. -->
const colMetrics = ref<string[]>([]);
const colSubMetrics = ref<string[]>([]);
const metricConfig = ref<MetricConfigOpt[]>(props.config.metricConfig || []);
const metricTypes = ref<string[]>(props.config.metricTypes || []);
const metricMode = ref<string>(props.config.metricMode);
const typesOfMQE = ref<string[]>(props.config.typesOfMQE || []);
if (props.needQuery) {
queryEndpoints();
@@ -138,34 +130,7 @@ limitations under the License. -->
merge: d.merge,
};
});
if (props.config.metricMode === MetricModes.Expression) {
queryEndpointExpressions(currentPods);
return;
}
const metrics = props.config.metrics || [];
const types = props.config.metricTypes || [];
if (metrics.length && metrics[0] && types.length && types[0]) {
const params = await useQueryPodsMetrics(currentPods, props.config, EntityType[2].value);
const json = await dashboardStore.fetchMetricValue(params);
if (json.errors) {
ElMessage.error(json.errors);
return;
}
const { data, names, metricConfigArr, metricTypesArr } = usePodsSource(currentPods, json, {
...props.config,
metricConfig: metricConfig.value,
});
endpoints.value = data;
colMetrics.value = names;
metricTypes.value = metricTypesArr;
metricConfig.value = metricConfigArr;
return;
}
endpoints.value = currentPods;
colMetrics.value = [];
metricTypes.value = [];
metricConfig.value = [];
}
async function queryEndpointExpressions(currentPods: Endpoint[]) {
const expressions = props.config.expressions || [];
@@ -180,8 +145,8 @@ limitations under the License. -->
endpoints.value = params.data;
colMetrics.value = params.names;
colSubMetrics.value = params.subNames;
metricTypes.value = params.metricTypesArr;
metricConfig.value = params.metricConfigArr;
typesOfMQE.value = params.metricTypesArr;
emit("expressionTips", { tips: params.expressionsTips, subTips: params.subExpressionsTips });
return;
@@ -189,8 +154,8 @@ limitations under the License. -->
endpoints.value = currentPods;
colMetrics.value = [];
colSubMetrics.value = [];
metricTypes.value = [];
metricConfig.value = [];
typesOfMQE.value = [];
emit("expressionTips", [], []);
}
function clickEndpoint(scope: any) {
@@ -212,19 +177,15 @@ limitations under the License. -->
}
watch(
() => [
...(props.config.metricTypes || []),
...(props.config.metrics || []),
...(props.config.metricConfig || []),
...(props.config.expressions || []),
...(props.config.subExpressions || []),
props.config.metricMode,
],
(data, old) => {
if (JSON.stringify(data) === JSON.stringify(old)) {
return;
}
metricConfig.value = props.config.metricConfig;
metricMode.value = props.config.metricMode;
queryEndpointMetrics(endpoints.value);
},
);

View File

@@ -39,8 +39,7 @@ limitations under the License. -->
:config="{
...config,
metricConfig,
metricTypes,
metricMode,
typesOfMQE,
}"
v-if="colMetrics.length"
/>
@@ -85,9 +84,8 @@ limitations under the License. -->
import { useDashboardStore } from "@/store/modules/dashboard";
import type { InstanceListConfig } from "@/types/dashboard";
import type { Instance } from "@/types/selector";
import { useQueryPodsMetrics, usePodsSource } from "@/hooks/useMetricsProcessor";
import { useExpressionsQueryPodsMetrics } from "@/hooks/useExpressionsProcessor";
import { EntityType, MetricModes } from "../data";
import { EntityType } from "../data";
import router from "@/router";
import getDashboard from "@/hooks/useDashboardsSession";
import type { MetricConfigOpt } from "@/types/dashboard";
@@ -100,9 +98,7 @@ limitations under the License. -->
InstanceListConfig & {
i: string;
metrics: string[];
metricTypes: string[];
isEdit: boolean;
metricMode: string;
expressions: string[];
typesOfMQE: string[];
subExpressions: string[];
@@ -114,7 +110,7 @@ limitations under the License. -->
fontSize: 12,
i: "",
metrics: [],
metricTypes: [],
typesOfMQE: [],
}),
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
@@ -131,9 +127,9 @@ limitations under the License. -->
const colMetrics = ref<string[]>([]);
const colSubMetrics = ref<string[]>([]);
const metricConfig = ref<MetricConfigOpt[]>(props.config.metricConfig || []);
const metricTypes = ref<string[]>(props.config.metricTypes || []);
const pods = ref<Instance[]>([]); // all instances
const metricMode = ref<string>(props.config.metricMode);
const typesOfMQE = ref<string[]>(props.config.typesOfMQE || []);
if (props.needQuery) {
queryInstance();
}
@@ -169,36 +165,7 @@ limitations under the License. -->
attributes: d.attributes,
};
});
if (props.config.metricMode === MetricModes.Expression) {
queryInstanceExpressions(currentInstances);
return;
}
const metrics = props.config.metrics || [];
const types = props.config.metricTypes || [];
if (metrics.length && metrics[0] && types.length && types[0]) {
const params = await useQueryPodsMetrics(currentInstances, props.config, EntityType[3].value);
const json = await dashboardStore.fetchMetricValue(params);
if (json.errors) {
ElMessage.error(json.errors);
return;
}
const { data, names, metricConfigArr, metricTypesArr } = usePodsSource(currentInstances, json, {
...props.config,
metricConfig: metricConfig.value,
});
instances.value = data;
colMetrics.value = names;
metricTypes.value = metricTypesArr;
metricConfig.value = metricConfigArr;
return;
}
instances.value = currentInstances;
colMetrics.value = [];
metricTypes.value = [];
metricConfig.value = [];
}
async function queryInstanceExpressions(currentInstances: Instance[]) {
@@ -214,7 +181,7 @@ limitations under the License. -->
instances.value = params.data;
colMetrics.value = params.names;
colSubMetrics.value = params.subNames;
metricTypes.value = params.metricTypesArr;
typesOfMQE.value = params.metricTypesArr;
metricConfig.value = params.metricConfigArr;
emit("expressionTips", { tips: params.expressionsTips, subTips: params.subExpressionsTips });
@@ -223,7 +190,7 @@ limitations under the License. -->
instances.value = currentInstances;
colSubMetrics.value = [];
colMetrics.value = [];
metricTypes.value = [];
typesOfMQE.value = [];
metricConfig.value = [];
emit("expressionTips", [], []);
}
@@ -262,19 +229,15 @@ limitations under the License. -->
watch(
() => [
...(props.config.metricTypes || []),
...(props.config.metrics || []),
...(props.config.metricConfig || []),
...(props.config.expressions || []),
...(props.config.subExpressions || []),
props.config.metricMode,
],
(data, old) => {
if (JSON.stringify(data) === JSON.stringify(old)) {
return;
}
metricConfig.value = props.config.metricConfig;
metricMode.value = props.config.metricMode;
queryInstanceMetrics(instances.value);
},
);

View File

@@ -51,8 +51,7 @@ limitations under the License. -->
:config="{
...config,
metricConfig,
metricTypes,
metricMode,
typesOfMQE,
}"
v-if="colMetrics.length"
/>
@@ -78,9 +77,8 @@ limitations under the License. -->
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import type { Service } from "@/types/selector";
import { useQueryPodsMetrics, usePodsSource } from "@/hooks/useMetricsProcessor";
import { useExpressionsQueryPodsMetrics } from "@/hooks/useExpressionsProcessor";
import { EntityType, MetricModes } from "../data";
import { EntityType } from "../data";
import router from "@/router";
import getDashboard from "@/hooks/useDashboardsSession";
import type { MetricConfigOpt } from "@/types/dashboard";
@@ -95,12 +93,9 @@ limitations under the License. -->
type: Object as PropType<
ServiceListConfig & {
i: string;
metrics: string[];
metricTypes: string[];
isEdit: boolean;
names: string[];
metricConfig: MetricConfigOpt[];
metricMode: string;
expressions: string[];
typesOfMQE: string[];
subExpressions: string[];
@@ -125,8 +120,7 @@ limitations under the License. -->
const groups = ref<any>({});
const sortServices = ref<(Service & { merge: boolean })[]>([]);
const metricConfig = ref<MetricConfigOpt[]>(props.config.metricConfig || []);
const metricTypes = ref<string[]>(props.config.metricTypes || []);
const metricMode = ref<string>(props.config.metricMode);
const typesOfMQE = ref<string[]>(props.config.typesOfMQE || []);
queryServices();
@@ -211,43 +205,7 @@ limitations under the License. -->
shortName: d.shortName,
};
});
if (props.config.metricMode === MetricModes.Expression) {
queryServiceExpressions(currentServices);
return;
}
const metrics = props.config.metrics || [];
const types = props.config.metricTypes || [];
if (metrics.length && metrics[0] && types.length && types[0]) {
const params = await useQueryPodsMetrics(
currentServices,
{ ...props.config, metricConfig: metricConfig.value || [] },
EntityType[0].value,
);
const json = await dashboardStore.fetchMetricValue(params);
if (json.errors) {
ElMessage.error(json.errors);
return;
}
const { data, names, metricConfigArr, metricTypesArr } = usePodsSource(currentServices, json, {
...props.config,
metricConfig: metricConfig.value || [],
});
services.value = data;
colMetrics.value = names;
metricTypes.value = metricTypesArr;
metricConfig.value = metricConfigArr;
return;
}
services.value = currentServices;
colMetrics.value = [];
colMetrics.value = [];
metricTypes.value = [];
metricConfig.value = [];
}
async function queryServiceExpressions(currentServices: Service[]) {
const expressions = props.config.expressions || [];
@@ -262,16 +220,16 @@ limitations under the License. -->
services.value = params.data;
colMetrics.value = params.names;
colSubMetrics.value = params.subNames;
metricTypes.value = params.metricTypesArr;
metricConfig.value = params.metricConfigArr;
typesOfMQE.value = params.metricTypesArr;
emit("expressionTips", { tips: params.expressionsTips, subTips: params.subExpressionsTips });
return;
}
services.value = currentServices;
colMetrics.value = [];
colSubMetrics.value = [];
metricTypes.value = [];
metricConfig.value = [];
typesOfMQE.value = [];
emit("expressionTips", [], []);
}
function objectSpanMethod(param: any): any {
@@ -306,19 +264,15 @@ limitations under the License. -->
watch(
() => [
...(props.config.metricTypes || []),
...(props.config.metrics || []),
...(props.config.metricConfig || []),
...(props.config.expressions || []),
...(props.config.subExpressions || []),
props.config.metricMode,
],
(data, old) => {
if (JSON.stringify(data) === JSON.stringify(old)) {
return;
}
metricConfig.value = props.config.metricConfig;
metricMode.value = props.config.metricMode;
queryServiceMetrics(services.value);
},
);

View File

@@ -14,22 +14,34 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="chart-table">
<div class="chart-table" v-if="dataKeys.length">
<div class="row header flex-h">
<div class="name" :style="`width: ${nameWidth}`">
{{ config.tableHeaderCol1 || t("name") }}
<div
v-for="key in dataKeys[0]"
:key="key"
class="name"
:style="`width: ${dataKeys[0].length > 1 ? (nameWidth as number) / (dataKeys[0].length || 1) : nameWidth}%`"
>
{{ key.split("=")[0] || t("name") }}
</div>
<div class="value-col" v-if="config.showTableValues">
{{ config.tableHeaderCol2 || t("value") }}
</div>
</div>
<div class="row flex-h" v-for="key in dataKeys" :key="key">
<div class="name" :style="`width: ${nameWidth}`">{{ key }}</div>
<div class="row flex-h" v-for="(keys, index) in dataKeys" :key="index">
<div
v-for="k in keys"
class="name"
:style="`width: ${keys.length > 1 ? (nameWidth as number) / (keys.length || 1) : nameWidth}%`"
:key="k"
>{{ k.split("=")[1] }}</div
>
<div class="value-col" v-if="config.showTableValues">
{{ config.metricTypes[0] === "readMetricsValue" ? data[key] : data[key][data[key].length - 1 || 0] }}
{{ data[(keys as string[]).join(",")][data[(keys as string[]).join(",")].length - 1 || 0] }}
</div>
</div>
</div>
<div class="table-no-data" v-else>No Data</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
@@ -45,24 +57,21 @@ limitations under the License. -->
type: Object as PropType<{
showTableValues: boolean;
tableHeaderCol2: string;
tableHeaderCol1: string;
metricTypes: string[];
typesOfMQE: string[];
}>,
default: () => ({ showTableValues: true }),
},
});
const { t } = useI18n();
const nameWidth = computed(() => (props.config.showTableValues ? "80%" : "100%"));
const nameWidth = computed(() => (props.config.showTableValues ? 80 : 100));
const dataKeys = computed(() => {
if (props.config.metricTypes[0] === "readMetricsValue") {
const keys = Object.keys(props.data || {});
return keys;
}
const keys = Object.keys(props.data || {}).filter(
(i: string) => Array.isArray(props.data[i]) && props.data[i].length,
);
return keys;
const list = keys.map((d: string) => d.split(","));
return list;
});
</script>
<style lang="scss" scoped>
@@ -94,6 +103,10 @@ limitations under the License. -->
border-bottom: 1px solid $disabled-color;
}
div:first-child {
border-bottom: 1px solid $disabled-color;
}
div:nth-last-child(2) {
border-bottom: 1px solid $disabled-color;
}
@@ -119,4 +132,13 @@ limitations under the License. -->
width: 50%;
}
}
.table-no-data {
font-size: $font-size-smaller;
height: 100%;
align-items: center;
justify-content: center;
display: flex;
color: var(--text-color-placeholder);
}
</style>

View File

@@ -64,7 +64,7 @@ limitations under the License. -->
import copy from "@/utils/copy";
import { TextColors } from "@/views/dashboard/data";
import Trace from "@/views/dashboard/related/trace/Index.vue";
import { QueryOrders, Status, RefIdTypes, ProtocolTypes, ExpressionResultType } from "../data";
import { WidgetType, QueryOrders, Status, RefIdTypes, ExpressionResultType } from "@/views/dashboard/data";
/*global defineProps */
const props = defineProps({
@@ -76,10 +76,8 @@ limitations under the License. -->
},
config: {
type: Object as PropType<{
metricMode: string;
color: string;
metrics: string[];
metricTypes: string[];
expressions: string[];
typesOfMQE: string[];
relatedTrace: any;
}>,
@@ -90,7 +88,7 @@ limitations under the License. -->
const { t } = useI18n();
const showTrace = ref<boolean>(false);
const traceOptions = ref<{ type: string; filters?: unknown }>({
type: "Trace",
type: WidgetType.Trace,
});
const refIdType = computed(
() => (props.config.relatedTrace && props.config.relatedTrace.refIdType) || RefIdTypes[0].value,
@@ -115,11 +113,8 @@ limitations under the License. -->
queryOrder: QueryOrders[1].value,
status: Status[2].value,
id: item.refId,
metricValue: [{ label: props.config.metrics[0], data: item.value, value: item.name }],
isReadRecords:
props.config.typesOfMQE.includes(ExpressionResultType.RECORD_LIST) ||
props.config.metricTypes.includes(ProtocolTypes.ReadRecords) ||
undefined,
metricValue: [{ label: props.config.expressions[0], data: item.value, value: item.name }],
isReadRecords: props.config.typesOfMQE.includes(ExpressionResultType.RECORD_LIST) || undefined,
};
traceOptions.value = {
...traceOptions.value,

View File

@@ -22,23 +22,7 @@ limitations under the License. -->
>
<template #default="scope">
<div class="chart">
<Line
v-if="useListConfig(config, index).isLinear && config.metricMode !== MetricModes.Expression"
:data="{
[metric]: scope.row[metric] && scope.row[metric].values,
}"
:intervalTime="intervalTime"
:config="{
showXAxis: false,
showYAxis: false,
smallTips: true,
showlabels: false,
}"
/>
<span
class="item flex-h"
v-else-if="useListConfig(config, index).isAvg || config.metricMode === MetricModes.Expression"
>
<span class="item flex-h">
<el-popover placement="left" :width="400" trigger="click">
<template #reference>
<span class="trend">
@@ -70,7 +54,6 @@ limitations under the License. -->
/>
</span>
</span>
<Card v-else :data="{ [metric]: scope.row[metric] }" :config="{ textAlign: 'left' }" />
</div>
</template>
</el-table-column>
@@ -79,11 +62,9 @@ limitations under the License. -->
<script lang="ts" setup>
import type { PropType } from "vue";
import type { MetricConfigOpt } from "@/types/dashboard";
import { useListConfig } from "@/hooks/useListConfig";
import Line from "../Line.vue";
import Card from "../Card.vue";
import { MetricQueryTypes } from "@/hooks/data";
import { ExpressionResultType, MetricModes } from "@/views/dashboard/data";
import { ExpressionResultType } from "@/views/dashboard/data";
/*global defineProps */
const props = defineProps({
@@ -92,9 +73,8 @@ limitations under the License. -->
config: {
type: Object as PropType<{
i: string;
metricTypes: string[];
typesOfMQE: string[];
metricConfig: MetricConfigOpt[];
metricMode: string;
}>,
default: () => ({}),
},
@@ -120,13 +100,9 @@ limitations under the License. -->
}
if (label) {
if (
(
[
MetricQueryTypes.ReadLabeledMetricsValues,
ExpressionResultType.TIME_SERIES_VALUES,
ExpressionResultType.SINGLE_VALUE,
] as string[]
).includes(props.config.metricTypes[i])
([ExpressionResultType.TIME_SERIES_VALUES, ExpressionResultType.SINGLE_VALUE] as string[]).includes(
props.config.typesOfMQE[i],
)
) {
const name = (label || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""))[
props.config.metricConfig[i].index || 0

View File

@@ -58,7 +58,12 @@ limitations under the License. -->
function clickGrid(item: LayoutConfig, event: Event) {
dashboardStore.activeGridItem(item.i);
dashboardStore.selectWidget(item);
if (item.type === "Tab" && (event.target as HTMLDivElement)?.className !== "tab-layout") {
if (
item.type === "Tab" &&
!["operations", "tab-layout"].includes((event.target as HTMLDivElement)?.className) &&
(event.target as HTMLDivElement)?.classList[2] !== "icon-tool" &&
(event.target as HTMLDivElement)?.nodeName !== "use"
) {
dashboardStore.setActiveTabIndex(0);
}
}

View File

@@ -26,6 +26,9 @@ limitations under the License. -->
@change="changeService"
class="selectors"
/>
<span class="ml-5 cp hierarchy-btn" v-if="dashboardStore.entity === 'Service'" @click="viewTopology">
<Icon size="small" iconName="hierarchy_topology" />
</span>
</div>
<div class="selectors-item" v-if="key === 3 || key === 4 || key === 5 || key === 6">
<span class="label">
@@ -41,6 +44,13 @@ limitations under the License. -->
class="selectorPod"
:isRemote="['EndpointRelation', 'Endpoint'].includes(dashboardStore.entity)"
/>
<span
class="ml-5 cp hierarchy-btn"
v-if="dashboardStore.entity === 'ServiceInstance'"
@click="showHierarchy = true"
>
<Icon size="small" iconName="hierarchy_topology" />
</span>
</div>
<div class="selectors-item" v-if="key === 5 || key === 6">
<span class="label"> $Process </span>
@@ -126,10 +136,17 @@ limitations under the License. -->
</div>
</div>
</div>
<el-dialog v-model="showHierarchy" :destroy-on-close="true" @closed="showHierarchy = false" width="640px">
<div class="hierarchy-related">
<instance-map v-if="dashboardStore.entity === 'ServiceInstance'" />
<hierarchy-map v-else />
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { reactive, ref, computed, watch } from "vue";
import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import {
@@ -143,19 +160,24 @@ limitations under the License. -->
ServiceRelationTools,
ProcessTools,
ProcessRelationTools,
} from "../data";
WidgetType,
} from "@/views/dashboard/data";
import { useSelectorStore } from "@/store/modules/selectors";
import { useTopologyStore } from "@/store/modules/topology";
import { ElMessage } from "element-plus";
import type { Option } from "@/types/app";
import { useI18n } from "vue-i18n";
import InstanceMap from "@/views/dashboard/related/topology/pod/InstanceMap.vue";
import HierarchyMap from "@/views/dashboard/related/topology/service/HierarchyMap.vue";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore();
const topologyStore = useTopologyStore();
const appStore = useAppStoreWithOut();
const params = useRoute().params;
const toolIcons = ref<{ name: string; content: string; id: string }[]>(AllTools);
const toolIcons = ref<{ name: string; content: string; id: WidgetType }[]>(AllTools);
const loading = ref<boolean>(false);
const showHierarchy = ref<boolean>(false);
const states = reactive<{
destService: string;
destPod: string;
@@ -397,7 +419,7 @@ limitations under the License. -->
loading.value = false;
}
async function clickIcons(t: { id: string; content: string; name: string }) {
async function clickIcons(t: { id: WidgetType; content: string; name: string }) {
if (dashboardStore.selectedGrid && dashboardStore.selectedGrid.type === "Tab") {
setTabControls(t.id);
return;
@@ -409,106 +431,20 @@ limitations under the License. -->
setControls(t.id);
}
function setTabControls(id: string) {
switch (id) {
case "addWidget":
dashboardStore.addTabControls("Widget");
break;
case "addTrace":
dashboardStore.addTabControls("Trace");
break;
case "addLog":
dashboardStore.addTabControls("Log");
break;
case "addProfile":
dashboardStore.addTabControls("Profile");
break;
case "addEbpf":
dashboardStore.addTabControls("Ebpf");
break;
case "addTopology":
dashboardStore.addTabControls("Topology");
break;
case "addText":
dashboardStore.addTabControls("Text");
break;
case "addDemandLog":
dashboardStore.addTabControls("DemandLog");
break;
case "addEvent":
dashboardStore.addTabControls("Event");
break;
case "addNetworkProfiling":
dashboardStore.addTabControls("NetworkProfiling");
break;
case "addContinuousProfiling":
dashboardStore.addTabControls("ContinuousProfiling");
break;
case "addTimeRange":
dashboardStore.addTabControls("TimeRange");
break;
case "addIframe":
dashboardStore.addTabControls("ThirdPartyApp");
break;
case "addTaskTimeline":
dashboardStore.addTabControls("TaskTimeline");
break;
default:
function setTabControls(id: WidgetType) {
if (!WidgetType[id]) {
ElMessage.info("Don't support this control");
break;
return;
}
dashboardStore.addTabControls(id);
}
function setControls(id: string) {
switch (id) {
case "addWidget":
dashboardStore.addControl("Widget");
break;
case "addTab":
dashboardStore.addControl("Tab");
break;
case "addTrace":
dashboardStore.addControl("Trace");
break;
case "addProfile":
dashboardStore.addControl("Profile");
break;
case "addEbpf":
dashboardStore.addControl("Ebpf");
break;
case "addLog":
dashboardStore.addControl("Log");
break;
case "addTopology":
dashboardStore.addControl("Topology");
break;
case "addText":
dashboardStore.addControl("Text");
break;
case "addDemandLog":
dashboardStore.addControl("DemandLog");
break;
case "addEvent":
dashboardStore.addControl("Event");
break;
case "addNetworkProfiling":
dashboardStore.addControl("NetworkProfiling");
break;
case "addContinuousProfiling":
dashboardStore.addControl("ContinuousProfiling");
break;
case "addTimeRange":
dashboardStore.addControl("TimeRange");
break;
case "addIframe":
dashboardStore.addControl("ThirdPartyApp");
break;
case "addTaskTimeline":
dashboardStore.addControl("TaskTimeline");
break;
default:
dashboardStore.addControl("Widget");
function setControls(id: WidgetType) {
if (!WidgetType[id]) {
ElMessage.info("Don't support this control");
return;
}
dashboardStore.addControl(id);
}
async function fetchPods(type: string, serviceId: string, setPod: boolean, param?: { keyword?: string }) {
@@ -722,6 +658,10 @@ limitations under the License. -->
};
fetchPods(EntityType[6].value, selectorStore.currentDestService.id, false, param);
}
function viewTopology() {
showHierarchy.value = true;
topologyStore.setNode(null);
}
watch(
() => dashboardStore.entity,
(newVal, oldVal) => {
@@ -790,4 +730,19 @@ limitations under the License. -->
.relation {
margin-top: 5px;
}
.hierarchy-btn {
display: inline-block;
padding: 0 2px 2px;
border: 1px solid #666;
border-radius: 4px;
text-align: center;
color: #aaa;
}
.hierarchy-related {
height: 600px;
width: 600px;
overflow: hidden;
}
</style>

View File

@@ -25,11 +25,14 @@ limitations under the License. -->
<script lang="ts" setup>
import { onMounted, ref, onUnmounted, watch, toRaw } from "vue";
import { useDemandLogStore } from "@/store/modules/demand-log";
import { useAppStoreWithOut } from "@/store/modules/app";
import { Themes } from "@/constants/data";
/*global Nullable */
const demandLogStore = useDemandLogStore();
const monacoInstance = ref();
const logContent = ref<Nullable<HTMLDivElement>>(null);
const appStore = useAppStoreWithOut();
onMounted(() => {
init();
@@ -50,6 +53,7 @@ limitations under the License. -->
wordWrap: true,
minimap: { enabled: false },
readonly: true,
theme: getTheme(),
});
toRaw(monacoInstance.value).updateOptions({ readOnly: true });
editorLayout();
@@ -64,6 +68,9 @@ limitations under the License. -->
width: width,
});
}
function getTheme() {
return appStore.theme === Themes.Dark ? "vs-dark" : "vs";
}
onUnmounted(() => {
if (!toRaw(monacoInstance.value)) {
return;
@@ -72,6 +79,12 @@ limitations under the License. -->
monacoInstance.value = null;
demandLogStore.setLogs("");
});
watch(
() => appStore.theme,
() => {
toRaw(monacoInstance.value).updateOptions({ theme: getTheme() });
},
);
watch(
() => demandLogStore.logs,
() => {

View File

@@ -29,6 +29,7 @@ limitations under the License. -->
import { useAppStoreWithOut } from "@/store/modules/app";
import dateFormatStep, { dateFormatTime } from "@/utils/dateFormat";
import getLocalTime from "@/utils/localtime";
import { WidgetType } from "@/views/dashboard/data";
const eventStore = useEventStore();
/*global defineProps, Nullable */
@@ -122,7 +123,9 @@ limitations under the License. -->
}[],
dashboard: LayoutConfig[],
) {
const widgets = dashboard.filter((d: { type: string }) => ["Trace", "Log"].includes(d.type));
const widgets = dashboard.filter((d: { type: string }) =>
([WidgetType.Trace, WidgetType.Log] as string[]).includes(d.type),
);
const index = items[0];
const i = events[index - 1 || 0];
for (const widget of widgets) {

View File

@@ -199,7 +199,14 @@ limitations under the License. -->
ElMessage.error(resp.errors);
return;
}
if (!logStore.services.length) {
return;
}
if (props.data.filters && props.data.filters.id) {
state.service = logStore.services.find((item: { id: string }) => item.id === props.data.filters?.id);
} else {
state.service = logStore.services[0];
}
getInstances(state.service.id);
getEndpoints(state.service.id);
}

View File

@@ -42,14 +42,14 @@ limitations under the License. -->
@closed="showDetail = false"
:title="t('logDetail')"
>
<LogDetail :currentLog="currentLog" />
<LogDetail :currentLog="currentLog" :logTemplate="type === 'browser' ? BrowserLogConstants : ServiceLogDetail" />
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { ServiceLogConstants, BrowserLogConstants } from "./data";
import { ServiceLogConstants, BrowserLogConstants, ServiceLogDetail } from "./data";
import LogBrowser from "./LogBrowser.vue";
import LogService from "./LogService.vue";
import LogDetail from "./LogDetail.vue";

View File

@@ -44,13 +44,6 @@ limitations under the License. -->
const logItem = ref<any>(null);
function showSelectSpan() {
const items: NodeListOf<any> = document.querySelectorAll(".log-item");
for (const item of items) {
item.style.background = "#fff";
}
logItem.value.style.background = "rgba(0, 0, 0, 0.1)";
emit("select", props.data);
}
</script>
@@ -62,15 +55,15 @@ limitations under the License. -->
}
.log-item.selected {
background: rgba(0, 0, 0, 0.04);
background: rgb(0 0 0 / 4%);
}
.log-item:not(.level0):hover {
background: rgba(0, 0, 0, 0.04);
background: rgb(0 0 0 / 4%);
}
.log-item:hover {
background: rgba(0, 0, 0, 0.04) !important;
background: rgb(0 0 0 / 4%) !important;
}
.log-item > div {

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="log-detail">
<div class="mb-10 clear rk-flex" v-for="(item, index) in ServiceLogDetail" :key="index">
<div class="mb-10 clear rk-flex" v-for="(item, index) in logTemplate" :key="index">
<span class="g-sm-4 grey">{{ t(item.value) }}:</span>
<span v-if="['timestamp', 'time'].includes(item.label)" class="g-sm-8 mb-10">
{{ dateFormat(currentLog[item.label]) }}
@@ -38,11 +38,11 @@ limitations under the License. -->
import { useI18n } from "vue-i18n";
import { dateFormat } from "@/utils/dateFormat";
import { formatJson } from "@/utils/formatJson";
import { ServiceLogDetail } from "./data";
/*global defineProps */
const props = defineProps({
currentLog: { type: Object as PropType<any>, default: () => ({}) },
logTemplate: { type: Object as PropType<any>, default: () => ({}) },
});
const { t } = useI18n();
const logTags = computed(() => {

View File

@@ -25,11 +25,11 @@ limitations under the License. -->
{{ dateFormat(data.timestamp) }}
</span>
<span v-else-if="item.label === 'tags'" :class="level.toLowerCase()"> > </span>
<el-tooltip v-else-if="item.label === 'traceId' && !noLink" content="Trace Link">
<span :class="noLink ? '' : 'blue'">
<Icon v-if="data[item.label]" iconName="merge" />
</span>
<span class="blue" v-else-if="item.label === 'traceId'">
<el-tooltip content="Trace Link" v-if="!noLink && data[item.label]">
<Icon iconName="merge" />
</el-tooltip>
</span>
<span v-else>{{ data[item.label] }}</span>
</div>
</div>
@@ -41,6 +41,7 @@ limitations under the License. -->
import { useDashboardStore } from "@/store/modules/dashboard";
import type { LayoutConfig } from "@/types/dashboard";
import { dateFormat } from "@/utils/dateFormat";
import { WidgetType } from "@/views/dashboard/data";
/*global defineProps, defineEmits, Recordable */
const props = defineProps({
@@ -78,7 +79,7 @@ limitations under the License. -->
traceId: id,
id: props.data.serviceId || "",
},
"Trace",
WidgetType.Trace,
);
}
</script>

View File

@@ -13,14 +13,14 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<Graph :config="config" v-if="isService" />
<PodTopology :config="config" v-else />
<service-map :config="config" v-if="isService" />
<pod-map :config="config" v-else />
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import Graph from "./components/Graph.vue";
import PodTopology from "./components/PodTopology.vue";
import { EntityType } from "../../data";
import ServiceMap from "./service/ServiceMap.vue";
import PodMap from "./pod/PodMap.vue";
import { EntityType } from "@/views/dashboard/data";
import { useDashboardStore } from "@/store/modules/dashboard";
/*global defineProps */

View File

@@ -13,15 +13,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div
ref="chart"
class="micro-topo-chart"
v-loading="loading"
element-loading-background="rgba(0, 0, 0, 0)"
:style="`height: ${height}px`"
>
<svg class="svg-topology" :width="width - 100" :height="height" @click="svgEvent">
<g class="svg-graph" :transform="`translate(${diff[0]}, ${diff[1]})`">
<svg class="hierarchy-services-svg" :width="width" :height="height" @click="svgEvent" v-if="nodes.length">
<g class="hierarchy-services-graph" :transform="`translate(${diff[0]}, ${diff[1]})`">
<g
class="topo-node"
v-for="(n, index) in topologyLayout.nodes"
@@ -32,23 +25,22 @@ limitations under the License. -->
@mousedown="startMoveNode($event, n)"
@mouseup="stopMoveNode($event)"
>
<image width="36" height="36" :x="n.x - 15" :y="n.y - 18" :href="getNodeStatus(n)" />
<!-- <circle :cx="n.x" :cy="n.y" r="12" fill="none" stroke="red"/> -->
<image width="36" height="36" :x="n.x - 15" :y="n.y - 18" :href="icons.CUBE" />
<image width="28" height="25" :x="n.x - 14" :y="n.y - 43" :href="icons.LOCAL" style="opacity: 0.8" />
<image
width="12"
height="12"
:x="n.x - 6"
:y="n.y - 38"
:href="!n.type || n.type === `N/A` ? icons.UNDEFINED : icons[n.type.toUpperCase().replace('-', '')]"
:href="!n.layer || n.layer === `N/A` ? icons.MESH : icons[n.layer.toUpperCase().replace('-', '')]"
/>
<text
class="node-text"
:x="n.x - (Math.min(n.name.length, 20) * 6) / 2 + 6"
:y="n.y + n.height + 8"
:x="n.x - (Math.min(n.name.length, 30) * 6) / 2 + 6"
:y="n.y + n.width + 8"
style="pointer-events: none"
>
{{ n.name.length > 20 ? `${n.name.substring(0, 20)}...` : n.name }}
{{ n.name.length > 30 ? `${n.name.substring(0, 30)}...` : n.name }}
</text>
</g>
<g v-for="(l, index) in topologyLayout.calls" :key="index">
@@ -58,16 +50,6 @@ limitations under the License. -->
stroke="#97B0F8"
marker-end="url(#arrow)"
/>
<circle
class="topo-line-anchor"
:cx="(l.sourceX + l.targetX) / 2"
:cy="(l.sourceY + l.targetY) / 2"
r="4"
fill="#97B0F8"
@click="handleLinkClick($event, l)"
@mouseover="showLinkTip($event, l)"
@mouseout="hideTip"
/>
</g>
<g class="arrows">
<defs v-for="(_, index) in topologyLayout.calls" :key="index">
@@ -87,261 +69,84 @@ limitations under the License. -->
</g>
</g>
</svg>
<div id="tooltip"></div>
<div class="legend">
<div>
<img :src="icons.CUBE" />
<span>
{{ settings.description ? settings.description.healthy || "" : "" }}
</span>
</div>
<div>
<img :src="icons.CUBEERROR" />
<span>
{{ settings.description ? settings.description.unhealthy || "" : "" }}
</span>
</div>
</div>
<div class="setting" v-if="showSetting && dashboardStore.editMode">
<Settings @update="updateSettings" @updateNodes="freshNodes" />
</div>
<div class="tool">
<span v-show="graphConfig.showDepth">
<span class="label">{{ t("currentDepth") }}</span>
<Selector class="inputs" :value="depth" :options="DepthList" @change="changeDepth" />
</span>
<span class="switch-icon ml-5" title="Settings" @click="setConfig" v-if="dashboardStore.editMode">
<Icon size="middle" iconName="settings" />
</span>
<span class="switch-icon ml-5" title="Back to overview topology" @click="backToTopology">
<Icon size="middle" iconName="keyboard_backspace" />
</span>
</div>
<div
class="operations-list"
v-if="topologyStore.node"
:style="{
top: operationsPos.y + 5 + 'px',
left: operationsPos.x + 5 + 'px',
}"
>
<span v-for="(item, index) of items" :key="index" @click="item.func(item.dashboard)">
{{ item.title }}
</span>
</div>
</div>
<div v-else class="hierarchy-services"> No Data</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { ref, onMounted, onBeforeUnmount, reactive, watch, computed, nextTick } from "vue";
import { useI18n } from "vue-i18n";
import { ref, computed, watch } from "vue";
import * as d3 from "d3";
import type { Node, Call } from "@/types/topology";
import { useSelectorStore } from "@/store/modules/selectors";
import { useTopologyStore } from "@/store/modules/topology";
import { useDashboardStore } from "@/store/modules/dashboard";
import { EntityType, DepthList, MetricModes, CallTypes } from "../../../data";
import router from "@/router";
import { ElMessage } from "element-plus";
import Settings from "./Settings.vue";
import type { Option } from "@/types/app";
import type { Service } from "@/types/selector";
import { useAppStoreWithOut } from "@/store/modules/app";
import getDashboard from "@/hooks/useDashboardsSession";
import type { MetricConfigOpt } from "@/types/dashboard";
import { aggregation } from "@/hooks/useMetricsProcessor";
import icons from "@/assets/img/icons";
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
import { layout, circleIntersection, computeCallPos } from "./utils/layout";
import zoom from "../../components/utils/zoom";
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
import { changeNode, computeHierarchyLevels, hierarchy } from "./utils/layout";
import zoom from "@/views/dashboard/related/components/utils/zoom";
/*global Nullable, defineProps */
const props = defineProps({
config: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
default: () => ({}),
},
entity: {
type: String,
default: "",
},
nodes: {
type: Array as PropType<Node[]>,
default: () => [],
},
calls: {
type: Array as PropType<Call[]>,
default: () => [],
},
layerLevels: {
type: Array as PropType<{ layer: string; level: number }[]>,
default: () => [],
},
});
const { t } = useI18n();
const selectorStore = useSelectorStore();
const topologyStore = useTopologyStore();
const emits = defineEmits(["showNodeTip", "handleNodeClick", "hideTip", "getNodeMetrics"]);
const dashboardStore = useDashboardStore();
const appStore = useAppStoreWithOut();
const height = ref<number>(100);
const width = ref<number>(100);
const loading = ref<boolean>(false);
const svg = ref<Nullable<any>>(null);
const graph = ref<Nullable<any>>(null);
const chart = ref<Nullable<HTMLDivElement>>(null);
const showSetting = ref<boolean>(false);
const settings = ref<any>(props.config);
const operationsPos = reactive<{ x: number; y: number }>({ x: NaN, y: NaN });
const items = ref<{ id: string; title: string; func: any; dashboard?: string }[]>([]);
const graphConfig = computed(() => props.config.graph || {});
const depth = ref<number>(graphConfig.value.depth || 2);
const topologyLayout = ref<any>({});
const tooltip = ref<Nullable<any>>(null);
const graphWidth = ref<number>(100);
const currentNode = ref<Nullable<Node>>();
const diff = computed(() => [(width.value - graphWidth.value - 130) / 2, 100]);
const radius = 8;
const graphHeight = ref<number>(100);
const currentNode = ref<Nullable<Node>>(null);
const diff = computed(() => [
(width.value - graphWidth.value - 160) / 2,
(height.value - graphHeight.value - 60) / 2,
]);
const radius = 10;
onMounted(async () => {
await nextTick();
setTimeout(() => {
init();
}, 10);
});
async function init() {
const dom = document.querySelector(".topology")?.getBoundingClientRect() || {
height: 40,
const dom = document.querySelector(".hierarchy-related")?.getBoundingClientRect() || {
height: 80,
width: 0,
};
height.value = dom.height - 40;
height.value = dom.height - 80;
width.value = dom.width;
svg.value = d3.select(".svg-topology");
graph.value = d3.select(".svg-graph");
loading.value = true;
const json = await selectorStore.fetchServices(dashboardStore.layerId);
if (json.errors) {
ElMessage.error(json.errors);
return;
}
await freshNodes();
svg.value.call(zoom(d3, graph.value, diff.value));
}
async function freshNodes() {
topologyStore.setNode(null);
topologyStore.setLink(null);
const resp = await getTopology();
loading.value = false;
if (resp && resp.errors) {
ElMessage.error(resp.errors);
}
await update();
}
async function update() {
if (settings.value.metricMode === MetricModes.Expression) {
topologyStore.queryNodeExpressions(settings.value.nodeExpressions || []);
topologyStore.getLinkExpressions(settings.value.linkClientExpressions || [], CallTypes.Client);
topologyStore.getLinkExpressions(settings.value.linkServerExpressions || [], CallTypes.Server);
} else {
topologyStore.queryNodeMetrics(settings.value.nodeMetrics || []);
topologyStore.getLinkClientMetrics(settings.value.linkClientMetrics || []);
topologyStore.getLinkServerMetrics(settings.value.linkServerMetrics || []);
}
window.addEventListener("resize", resize);
await initLegendMetrics();
svg.value = d3.select(".hierarchy-services-svg");
graph.value = d3.select(".hierarchy-services-graph");
emits("getNodeMetrics");
draw();
tooltip.value = d3.select("#tooltip");
setNodeTools(settings.value.nodeDashboard);
}
function computeLevels(calls: Call[], nodeList: Node[], levels: any[]) {
const node = findMostFrequent(calls);
const nodes = JSON.parse(JSON.stringify(nodeList)).sort((a: Node, b: Node) => {
if (a.name.toLowerCase() < b.name.toLowerCase()) {
return -1;
}
if (a.name.toLowerCase() > b.name.toLowerCase()) {
return 1;
}
return 0;
});
const index = nodes.findIndex((n: Node) => n.type === "USER");
let key = index;
if (index < 0) {
key = nodes.findIndex((n: Node) => n.id === node.id);
}
levels.push([nodes[key]]);
nodes.splice(key, 1);
for (const level of levels) {
const a = [];
for (const l of level) {
for (const n of calls) {
if (n.target === l.id) {
const i = nodes.findIndex((d: Node) => d.id === n.source);
if (i > -1) {
a.push(nodes[i]);
nodes.splice(i, 1);
}
}
if (n.source === l.id) {
const i = nodes.findIndex((d: Node) => d.id === n.target);
if (i > -1) {
a.push(nodes[i]);
nodes.splice(i, 1);
}
}
}
}
if (a.length) {
levels.push(a);
}
}
if (nodes.length) {
const ids = nodes.map((d: Node) => d.id);
const links = calls.filter((item: Call) => ids.includes(item.source) || ids.includes(item.target));
const list = computeLevels(links, nodes, []);
levels = list.map((subArrayA, index) => subArrayA.concat(levels[index]));
}
return levels;
svg.value.call(zoom(d3, graph.value, diff.value));
}
function draw() {
const levels = computeLevels(topologyStore.calls, topologyStore.nodes, []);
topologyLayout.value = layout(levels, topologyStore.calls, radius);
const levels = computeHierarchyLevels(props.nodes);
topologyLayout.value = hierarchy(levels, props.calls, radius);
graphWidth.value = topologyLayout.value.layout.width;
graphHeight.value = topologyLayout.value.layout.height;
const drag: any = d3.drag().on("drag", (d: { x: number; y: number }) => {
moveNode(d);
topologyLayout.value.calls = changeNode(d, currentNode.value, topologyLayout.value, radius);
});
setTimeout(() => {
d3.selectAll(".topo-node").call(drag);
}, 1000);
}
function moveNode(d: { x: number; y: number }) {
if (!currentNode.value) {
return;
}
for (const node of topologyLayout.value.nodes) {
if (node.id === currentNode.value.id) {
node.x = d.x;
node.y = d.y;
}
}
for (const call of topologyLayout.value.calls) {
if (call.sourceObj.id === currentNode.value.id) {
call.sourceObj.x = d.x;
call.sourceObj.y = d.y;
}
if (call.targetObj.id === currentNode.value.id) {
call.targetObj.x = d.x;
call.targetObj.y = d.y;
}
if (call.targetObj.id === currentNode.value.id || call.sourceObj.id === currentNode.value.id) {
const pos: any = circleIntersection(
call.sourceObj.x,
call.sourceObj.y,
radius,
call.targetObj.x,
call.targetObj.y,
radius,
);
call.sourceX = pos[0].x;
call.sourceY = pos[0].y;
call.targetX = pos[1].x;
call.targetY = pos[1].y;
}
}
topologyLayout.value.calls = computeCallPos(topologyLayout.value.calls, radius);
}
function startMoveNode(event: MouseEvent, d: Node) {
event.stopPropagation();
currentNode.value = d;
@@ -349,396 +154,56 @@ limitations under the License. -->
function stopMoveNode(event: MouseEvent) {
event.stopPropagation();
currentNode.value = null;
hideTip();
}
function findMostFrequent(arr: Call[]) {
let count: any = {};
let maxCount = 0;
let maxItem = null;
for (let i = 0; i < arr.length; i++) {
let item = arr[i];
count[item.sourceObj.id] = (count[item.sourceObj.id] || 0) + 1;
if (count[item.sourceObj.id] > maxCount) {
maxCount = count[item.sourceObj.id];
maxItem = item.sourceObj;
}
count[item.targetObj.id] = (count[item.targetObj.id] || 0) + 1;
if (count[item.targetObj.id] > maxCount) {
maxCount = count[item.targetObj.id];
maxItem = item.targetObj;
}
}
return maxItem;
}
async function initLegendMetrics() {
if (!topologyStore.nodes.length) {
return;
}
if (settings.value.metricMode === MetricModes.Expression) {
const expression = props.config.legendMQE && props.config.legendMQE.expression;
if (!expression) {
return;
}
const { getExpressionQuery } = useQueryTopologyExpressionsProcessor([expression], topologyStore.nodes);
const param = getExpressionQuery();
const res = await topologyStore.getNodeExpressionValue(param);
if (res.errors) {
ElMessage.error(res.errors);
} else {
topologyStore.setLegendValues([expression], res.data);
}
} else {
const names = props.config.legend.map((d: any) => d.name);
if (!names.length) {
return;
}
const ids = topologyStore.nodes.map((d: Node) => d.id);
if (ids.length) {
const param = await useQueryTopologyMetrics(names, ids);
const res = await topologyStore.getLegendMetrics(param);
if (res.errors) {
ElMessage.error(res.errors);
}
}
}
}
function getNodeStatus(d: any) {
const { legend, legendMQE } = settings.value;
if (settings.value.metricMode === MetricModes.Expression) {
if (!legendMQE) {
return icons.CUBE;
}
if (!legendMQE.expression) {
return icons.CUBE;
}
return Number(d[legendMQE.expression]) && d.isReal ? icons.CUBEERROR : icons.CUBE;
}
if (!legend) {
return icons.CUBE;
}
if (!legend.length) {
return icons.CUBE;
}
let c = true;
for (const l of legend) {
if (l.condition === "<") {
c = c && d[l.name] < Number(l.value);
} else {
c = c && d[l.name] > Number(l.value);
}
}
return c && d.isReal ? icons.CUBEERROR : icons.CUBE;
}
function showNodeTip(event: MouseEvent, data: Node) {
const nodeMetrics: string[] =
(settings.value.metricMode === MetricModes.Expression
? settings.value.nodeExpressions
: settings.value.nodeMetrics) || [];
const nodeMetricConfig = settings.value.nodeMetricConfig || [];
const html = nodeMetrics.map((m, index) => {
const metric =
topologyStore.nodeMetricValue[m].values.find((val: { id: string; value: unknown }) => val.id === data.id) || {};
const opt: MetricConfigOpt = nodeMetricConfig[index] || {};
const v = aggregation(metric.value, opt);
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${v} ${opt.unit || "unknown"}</div>`;
});
const tipHtml = [
`<div class="mb-5"><span class="grey">name: </span>${
data.name
}</div><div class="mb-5"><span class="grey">type: </span>${data.type || "UNKNOWN"}</div>`,
...html,
].join(" ");
tooltip.value
.style("top", event.offsetY + 10 + "px")
.style("left", event.offsetX + 10 + "px")
.style("visibility", "visible")
.html(tipHtml);
}
function showLinkTip(event: MouseEvent, data: Call) {
const linkClientMetrics: string[] =
settings.value.metricMode === MetricModes.Expression
? settings.value.linkClientExpressions
: settings.value.linkClientMetrics || [];
const linkServerMetricConfig: MetricConfigOpt[] = settings.value.linkServerMetricConfig || [];
const linkClientMetricConfig: MetricConfigOpt[] = settings.value.linkClientMetricConfig || [];
const linkServerMetrics: string[] =
settings.value.metricMode === MetricModes.Expression
? settings.value.linkServerExpressions
: settings.value.linkServerMetrics || [];
const htmlServer = linkServerMetrics.map((m, index) => {
const metric = topologyStore.linkServerMetrics[m].values.find(
(val: { id: string; value: unknown }) => val.id === data.id,
);
if (metric) {
const opt: MetricConfigOpt = linkServerMetricConfig[index] || {};
const v = aggregation(metric.value, opt);
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${v} ${opt.unit || ""}</div>`;
}
});
const htmlClient = linkClientMetrics.map((m: string, index: number) => {
const opt: MetricConfigOpt = linkClientMetricConfig[index] || {};
const metric = topologyStore.linkClientMetrics[m].values.find(
(val: { id: string; value: unknown }) => val.id === data.id,
);
if (metric) {
const v = aggregation(metric.value, opt);
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${v} ${opt.unit || ""}</div>`;
}
});
const html = [
...htmlServer,
...htmlClient,
`<div><span class="grey">${t("detectPoint")}:</span>${data.detectPoints.join(" | ")}</div>`,
].join(" ");
tooltip.value
.style("top", event.offsetY + "px")
.style("left", event.offsetX + "px")
.style("visibility", "visible")
.html(html);
emits("showNodeTip", event, data);
}
function hideTip() {
tooltip.value.style("visibility", "hidden");
emits("hideTip");
}
function handleNodeClick(event: MouseEvent, d: Node & { x: number; y: number }) {
event.stopPropagation();
hideTip();
topologyStore.setNode(d);
topologyStore.setLink(null);
operationsPos.x = event.offsetX;
operationsPos.y = event.offsetY;
if (d.layer === String(dashboardStore.layerId)) {
setNodeTools(settings.value.nodeDashboard);
return;
emits("handleNodeClick", event, d);
}
items.value = [
{ id: "inspect", title: "Inspect", func: handleInspect },
{ id: "alerting", title: "Alerting", func: handleGoAlerting },
];
}
function handleLinkClick(event: MouseEvent, d: Call) {
event.stopPropagation();
if (d.sourceObj.layer !== dashboardStore.layerId || d.targetObj.layer !== dashboardStore.layerId) {
return;
}
topologyStore.setNode(null);
topologyStore.setLink(d);
if (!settings.value.linkDashboard) {
return;
}
const origin = dashboardStore.entity;
const e = dashboardStore.entity === EntityType[1].value ? EntityType[0].value : dashboardStore.entity;
const { dashboard } = getDashboard({
name: settings.value.linkDashboard,
layer: dashboardStore.layerId,
entity: `${e}Relation`,
});
if (!dashboard) {
ElMessage.error(`The dashboard named ${settings.value.linkDashboard} doesn't exist`);
return;
}
dashboardStore.setEntity(dashboard.entity);
const path = `/dashboard/related/${dashboard.layer}/${e}Relation/${d.sourceObj.id}/${d.targetObj.id}/${dashboard.name}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
dashboardStore.setEntity(origin);
}
async function handleInspect() {
const id = topologyStore.node.id;
loading.value = true;
const resp = await topologyStore.getDepthServiceTopology([id], Number(depth.value));
loading.value = false;
if (resp && resp.errors) {
ElMessage.error(resp.errors);
}
await update();
topologyStore.setNode(null);
topologyStore.setLink(null);
}
function handleGoEndpoint(name: string) {
const path = `/dashboard/${dashboardStore.layerId}/${EntityType[2].value}/${topologyStore.node.id}/${name}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
dashboardStore.setEntity(origin);
}
function handleGoInstance(name: string) {
const path = `/dashboard/${dashboardStore.layerId}/${EntityType[3].value}/${topologyStore.node.id}/${name}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
dashboardStore.setEntity(origin);
}
function handleGoDashboard(name: string) {
const path = `/dashboard/${dashboardStore.layerId}/${EntityType[0].value}/${topologyStore.node.id}/${name}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
dashboardStore.setEntity(origin);
}
function handleGoAlerting() {
const path = `/alerting`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
}
async function backToTopology() {
loading.value = true;
await freshNodes();
topologyStore.setNode(null);
topologyStore.setLink(null);
}
async function getTopology() {
const ids = selectorStore.services.map((d: Service) => d.id);
const serviceIds = dashboardStore.entity === EntityType[0].value ? [selectorStore.currentService.id] : ids;
const resp = await topologyStore.getDepthServiceTopology(serviceIds, Number(depth.value));
return resp;
}
function setConfig() {
showSetting.value = !showSetting.value;
dashboardStore.selectWidget(props.config);
}
function resize() {
const dom = document.querySelector(".topology")?.getBoundingClientRect() || {
height: 40,
width: 0,
};
height.value = dom.height - 40;
width.value = dom.width;
}
function updateSettings(config: any) {
settings.value = config;
setNodeTools(config.nodeDashboard);
}
function setNodeTools(nodeDashboard: any) {
items.value = [
{ id: "inspect", title: "Inspect", func: handleInspect },
{ id: "alerting", title: "Alerting", func: handleGoAlerting },
];
if (!(nodeDashboard && nodeDashboard.length)) {
return;
}
for (const item of nodeDashboard) {
if (item.scope === EntityType[0].value) {
items.value.push({
id: "dashboard",
title: "Service Dashboard",
func: handleGoDashboard,
...item,
});
}
if (item.scope === EntityType[2].value) {
items.value.push({
id: "endpoint",
title: "Endpoint Dashboard",
func: handleGoEndpoint,
...item,
});
}
if (item.scope === EntityType[3].value) {
items.value.push({
id: "instance",
title: "Service Instance Dashboard",
func: handleGoInstance,
...item,
});
}
}
}
function svgEvent() {
topologyStore.setNode(null);
topologyStore.setLink(null);
if (!props.config) {
return;
}
dashboardStore.selectWidget(props.config);
}
async function changeDepth(opt: Option[] | any) {
depth.value = opt[0].value;
freshNodes();
}
onBeforeUnmount(() => {
window.removeEventListener("resize", resize);
});
watch(
() => [selectorStore.currentService, selectorStore.currentDestService],
(newVal, oldVal) => {
if (oldVal[0].id === newVal[0].id && !oldVal[1]) {
return;
}
if (oldVal[0].id === newVal[0].id && oldVal[1].id === newVal[1].id) {
return;
}
freshNodes();
},
);
watch(
() => appStore.durationTime,
() => [...props.calls, ...props.nodes],
() => {
if (dashboardStore.entity === EntityType[1].value) {
freshNodes();
if (!props.nodes.length) {
return;
}
if (!props.calls.length) {
return;
}
setTimeout(() => {
init();
}, 10);
},
);
</script>
<style lang="scss">
.micro-topo-chart {
position: relative;
overflow: auto;
margin-top: 30px;
<style lang="scss" scoped>
.hierarchy-services-topo {
.node-text {
fill: var(--sw-topology-color);
font-size: 12px;
opacity: 0.9;
z-index: 1;
}
.svg-topology {
.hierarchy-services-svg {
cursor: move;
background-color: $theme-background;
}
.legend {
position: absolute;
top: 10px;
left: 25px;
color: var(--sw-topology-color);
div {
margin-bottom: 8px;
}
img {
width: 32px;
float: left;
}
span {
display: inline-block;
height: 32px;
line-height: 32px;
margin-left: 5px;
}
}
.setting {
position: absolute;
top: 80px;
right: 10px;
width: 400px;
height: 600px;
overflow: auto;
padding: 0 15px;
border-radius: 3px;
color: $disabled-color;
border: 1px solid $border-color-primary;
background-color: var(--sw-topology-setting-bg);
box-shadow: var(--sw-topology-box-shadow);
transition: all 0.5ms linear;
background-color: var(--el-bg-color);
}
.label {
@@ -747,51 +212,12 @@ limitations under the License. -->
margin-right: 5px;
}
.operations-list {
position: absolute;
color: $font-color;
cursor: pointer;
border: var(--sw-topology-border);
border-radius: 3px;
background-color: $theme-background;
padding: 10px 0;
span {
display: block;
height: 30px;
line-height: 30px;
text-align: left;
padding: 0 15px;
}
span:hover {
color: $active-color;
background-color: $popper-hover-bg-color;
}
}
.tool {
position: absolute;
top: 35px;
right: 10px;
}
.switch-icon {
cursor: pointer;
transition: all 0.5ms linear;
background: var(--sw-topology-switch-icon);
color: $text-color;
display: inline-block;
padding: 2px 4px;
border-radius: 3px;
}
.topo-line {
stroke-linecap: round;
stroke-width: 1px;
stroke-dasharray: 10 10;
fill: none;
animation: topo-dash 0.3s linear infinite;
animation: var(--sw-topo-animation);
}
.topo-line-anchor,
@@ -799,26 +225,11 @@ limitations under the License. -->
cursor: pointer;
}
}
@keyframes topo-dash {
from {
stroke-dashoffset: 10;
}
to {
stroke-dashoffset: 0;
}
}
.el-loading-spinner {
top: 30%;
}
#tooltip {
position: absolute;
visibility: hidden;
padding: 5px;
border: var(--sw-topology-border);
border-radius: 3px;
background-color: $theme-background;
.hierarchy-services {
align-items: center;
display: flex;
justify-content: center;
height: 100%;
}
</style>

View File

@@ -120,3 +120,175 @@ export function circleIntersection(ax: number, ay: number, ar: number, bx: numbe
{ x: gx, y: gy },
];
}
function findMostFrequent(arr: Call[]) {
const count: any = {};
let maxCount = 0;
let maxItem = null;
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
count[item.sourceObj.id] = (count[item.sourceObj.id] || 0) + 1;
if (count[item.sourceObj.id] > maxCount) {
maxCount = count[item.sourceObj.id];
maxItem = item.sourceObj;
}
count[item.targetObj.id] = (count[item.targetObj.id] || 0) + 1;
if (count[item.targetObj.id] > maxCount) {
maxCount = count[item.targetObj.id];
maxItem = item.targetObj;
}
}
return maxItem;
}
export function computeLevels(calls: Call[], nodeList: Node[], arr: Node[][]) {
const levels: Node[][] = [];
const node = findMostFrequent(calls);
let nodes = JSON.parse(JSON.stringify(nodeList)).sort((a: Node, b: Node) => {
if (a.name.toLowerCase() < b.name.toLowerCase()) {
return -1;
}
if (a.name.toLowerCase() > b.name.toLowerCase()) {
return 1;
}
return 0;
});
const index = nodes.findIndex((n: Node) => n.type === "USER");
let key = index;
if (index < 0) {
key = nodes.findIndex((n: Node) => n.id === node.id);
}
levels.push([nodes[key]]);
nodes = nodes.filter((_: unknown, index: number) => index !== key);
for (const level of levels) {
const a = [];
for (const l of level) {
for (const n of calls) {
if (n.target === l.id) {
const i = nodes.findIndex((d: Node) => d.id === n.source);
if (i > -1 && nodes[i]) {
a.push(nodes[i]);
nodes = nodes.filter((_: unknown, index: number) => index !== i);
}
}
if (n.source === l.id) {
const i = nodes.findIndex((d: Node) => d.id === n.target);
if (i > -1 && nodes[i]) {
a.push(nodes[i]);
nodes = nodes.filter((_: unknown, index: number) => index !== i);
}
}
}
}
if (a.length) {
levels.push(a);
}
}
const list = levels.length > arr.length ? levels : arr;
const subList = levels.length > arr.length ? arr : levels;
arr = list.map((subArray: Node[], index: number) => {
if (subList[index]) {
return subArray.concat(subList[index]);
} else {
return subArray;
}
});
if (nodes.length) {
const ids = nodes.map((d: Node) => d.id);
const links = calls.filter((item: Call) => ids.includes(item.source) || ids.includes(item.target));
arr = computeLevels(links, nodes, arr);
}
return arr;
}
export function changeNode(d: { x: number; y: number }, currentNode: Nullable<Node>, layout: any, radius: number) {
if (!currentNode) {
return;
}
for (const node of layout.nodes) {
if (node.id === currentNode.id) {
node.x = d.x;
node.y = d.y;
}
}
for (const call of layout.calls) {
if (call.sourceObj.id === currentNode.id) {
call.sourceObj.x = d.x;
call.sourceObj.y = d.y;
}
if (call.targetObj.id === currentNode.id) {
call.targetObj.x = d.x;
call.targetObj.y = d.y;
}
if (call.targetObj.id === currentNode.id || call.sourceObj.id === currentNode.id) {
const pos: any = circleIntersection(
call.sourceObj.x,
call.sourceObj.y,
radius,
call.targetObj.x,
call.targetObj.y,
radius,
);
call.sourceX = pos[0].x;
call.sourceY = pos[0].y;
call.targetX = pos[1].x;
call.targetY = pos[1].y;
}
}
return computeCallPos(layout.calls, radius);
}
export function hierarchy(levels: Node[][], calls: Call[], radius: number) {
// precompute level depth
levels.forEach((l: Node[], i: number) => l.forEach((n: Node) => n && (n.level = i)));
const nodes: Node[] = levels.reduce((a, x) => a.concat(x), []);
// layout
const padding = 30;
const node_height = 100;
const node_width = 180;
const bundle_width = 10;
const metro_d = 4;
for (const n of nodes) {
n.width = 5 * metro_d;
}
let y_offset = padding;
let x_offset = 0;
for (const level of levels) {
x_offset = 0;
y_offset += 3 * bundle_width;
for (const l of level) {
const n: any = l;
for (const call of calls) {
if (call.source === n.id) {
call.sourceObj = n;
}
if (call.target === n.id) {
call.targetObj = n;
}
}
n.x = node_width + x_offset + n.width / 2;
n.y = n.level * node_height + y_offset;
x_offset += node_width + n.width;
}
}
const layout = {
width: d3.max(nodes as any, (n: { x: number }) => n.x) || 0 + node_width + 2 * padding,
height: d3.max(nodes as any, (n: { y: number }) => n.y) || 0 + node_height / 2 + 2 * padding,
};
return { nodes, layout, calls: computeCallPos(calls, radius) };
}
export function computeHierarchyLevels(nodes: Node[]) {
const levelsNum: number[] = nodes.map((d: Node) => d.l || 0);
const list = [...new Set(levelsNum)];
const sortedArr = list.sort((a, b) => b - a);
const nodesList = [];
for (const min of sortedArr) {
const arr = nodes.filter((d) => d.l === min);
nodesList.push(arr);
}
return nodesList;
}

View File

@@ -15,9 +15,12 @@ limitations under the License. -->
<template>
<div class="config-panel">
<div class="item mb-10">
<span class="label">{{
t(dashboardStore.selectedGrid.metricMode === MetricModes.General ? "metrics" : "expressions")
}}</span>
<span class="label" v-if="type === 'hierarchyServicesConfig'">
{{ t("expressions") }}
</span>
<span class="label" v-else>
{{ t("expressions") }}
</span>
<SelectSingle :value="currentMetric" :options="metricList" @change="changeMetric" class="selectors" />
</div>
<div class="item mb-10">
@@ -40,52 +43,41 @@ limitations under the License. -->
@change="changeConfigs({ label: currentConfig.label })"
/>
</div>
<div class="item mb-10" v-if="dashboardStore.selectedGrid.metricMode === MetricModes.General">
<span class="label">{{ t("aggregation") }}</span>
<SelectSingle
:value="currentConfig.calculation"
:options="CalculationOpts"
@change="changeConfigs({ calculation: $event })"
class="selectors"
:clearable="true"
/>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, watch } from "vue";
import { useI18n } from "vue-i18n";
import { CalculationOpts, MetricModes } from "../../../data";
import type { Option } from "@/types/app";
import { EntityType, ConfigFieldTypes } from "@/views/dashboard/data";
import { useDashboardStore } from "@/store/modules/dashboard";
import type { Option } from "element-plus/es/components/select-v2/src/select.types";
import getDashboard from "@/hooks/useDashboardsSession";
/*global defineEmits, defineProps */
const props = defineProps({
type: { type: String, default: "" },
isExpression: { type: Boolean, default: true },
layer: { type: String, default: "" },
expressions: { type: Array<string>, default: () => [] },
entity: { type: String, default: EntityType[0].value },
});
const { t } = useI18n();
const emit = defineEmits(["update"]);
const dashboardStore = useDashboardStore();
const getMetrics = computed(() => {
let metrics = [];
const {
linkServerExpressions,
linkServerMetrics,
linkClientExpressions,
linkClientMetrics,
nodeExpressions,
nodeMetrics,
} = dashboardStore.selectedGrid;
if (props.type === "hierarchyServicesConfig") {
return props.expressions || [];
}
let metrics: string[] = [];
const { linkServerExpressions, linkClientExpressions, nodeExpressions } = dashboardStore.selectedGrid;
switch (props.type) {
case "linkServerMetricConfig":
metrics = props.isExpression ? linkServerExpressions : linkServerMetrics;
metrics = linkServerExpressions;
break;
case "linkClientMetricConfig":
metrics = props.isExpression ? linkClientExpressions : linkClientMetrics;
metrics = linkClientExpressions;
break;
case "nodeMetricConfig":
metrics = props.isExpression ? nodeExpressions : nodeMetrics;
metrics = nodeExpressions;
break;
}
return metrics || [];
@@ -97,13 +89,23 @@ limitations under the License. -->
return m.length ? m : [{ label: "", value: "" }];
});
const currentMetric = ref<string>(metricList.value[0].value);
const currentConfig = ref<{ unit: string; calculation: string; label: string }>({
unit: "",
calculation: "",
label: "",
});
const currentIndex = ref<number>(0);
const getMetricConfig = computed(() => {
if (props.type === "hierarchyServicesConfig") {
const { dashboard } = getDashboard(
{
layer: props.layer || "",
entity: props.entity,
},
ConfigFieldTypes.ISDEFAULT,
);
if (!dashboard) {
return [];
}
const config = dashboard.expressionsConfig || [];
return config || [];
}
let config = [];
switch (props.type) {
@@ -120,6 +122,14 @@ limitations under the License. -->
return config || [];
});
const currentConfig = ref<{ unit: string; calculation: string; label: string }>(
getMetricConfig.value[0] || {
unit: "",
calculation: "",
label: "",
},
);
function changeConfigs(param: { [key: string]: string }) {
const metricConfig = getMetricConfig.value || [];
metricConfig[currentIndex.value] = {
@@ -143,7 +153,7 @@ limitations under the License. -->
};
}
watch(
() => props.type,
() => [props.type, ...props.expressions],
() => {
currentMetric.value = metricList.value[0].value;
const config = getMetricConfig.value || [];

View File

@@ -13,18 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="mt-20">
<h5 class="title">{{ t("metricMode") }}</h5>
<el-switch
v-model="isExpression"
class="mt-5"
active-text="Expressions"
inactive-text="General"
size="small"
@change="changeMetricMode"
/>
</div>
<div class="link-settings">
<div class="mb-20">
<h5 class="title">{{ t("callSettings") }}</h5>
<div class="label">{{ t("linkDashboard") }}</div>
<Selector
@@ -38,76 +27,46 @@ limitations under the License. -->
/>
<div class="label">
<span>{{ t("linkServerMetrics") }}</span>
<el-popover
placement="left"
:width="400"
trigger="click"
v-if="isExpression ? states.linkServerExpressions.length : states.linkServerMetrics.length"
>
<el-popover placement="left" :width="400" trigger="click" v-if="states.linkServerExpressions.length">
<template #reference>
<span @click="setConfigType('linkServerMetricConfig')">
<Icon class="cp ml-5" iconName="mode_edit" size="middle" />
</span>
</template>
<Metrics :type="configType" :isExpression="isExpression" @update="updateSettings" />
<Metrics :type="configType" @update="updateSettings" />
</el-popover>
</div>
<div v-if="isExpression">
<div>
<Tags
:tags="states.linkServerExpressions"
:vertical="true"
:text="t('addExpressions')"
@change="(param) => changeLinkServerExpressions(param)"
@change="(param: string[]) => changeLinkServerExpressions(param)"
/>
</div>
<Selector
v-else
class="inputs"
:multiple="true"
:value="states.linkServerMetrics"
:options="states.linkMetricList"
size="small"
placeholder="Select metrics"
@change="updateLinkServerMetrics"
/>
<span v-show="dashboardStore.entity !== EntityType[2].value">
<div class="label">
<span>{{ t("linkClientMetrics") }}</span>
<el-popover
placement="left"
:width="400"
trigger="click"
v-if="isExpression ? states.linkClientExpressions.length : states.linkClientMetrics.length"
>
<el-popover placement="left" :width="400" trigger="click" v-if="states.linkClientExpressions.length">
<template #reference>
<span @click="setConfigType('linkClientMetricConfig')">
<Icon class="cp ml-5" iconName="mode_edit" size="middle" />
</span>
</template>
<Metrics :type="configType" :isExpression="isExpression" @update="updateSettings" />
<Metrics :type="configType" @update="updateSettings" />
</el-popover>
</div>
<div v-if="isExpression">
<div>
<Tags
:tags="states.linkClientExpressions"
:vertical="true"
:text="t('addExpressions')"
@change="(param) => changeLinkClientExpressions(param)"
@change="(param: string[]) => changeLinkClientExpressions(param)"
/>
</div>
<Selector
v-else
class="inputs"
:multiple="true"
:value="states.linkClientMetrics"
:options="states.linkMetricList"
size="small"
placeholder="Select metrics"
@change="updateLinkClientMetrics"
/>
</span>
</div>
<div class="node-settings">
<div>
<h5 class="title">{{ t("nodeSettings") }}</h5>
<div class="label">{{ t("nodeDashboard") }}</div>
<Selector
@@ -149,50 +108,34 @@ limitations under the License. -->
</div>
<div class="label">
<span>{{ t("nodeMetrics") }}</span>
<el-popover
placement="left"
:width="400"
trigger="click"
v-if="isExpression ? states.nodeExpressions.length : states.nodeMetrics.length"
>
<el-popover placement="left" :width="400" trigger="click" v-if="states.nodeExpressions.length">
<template #reference>
<span @click="setConfigType('nodeMetricConfig')">
<Icon class="cp ml-5" iconName="mode_edit" size="middle" />
</span>
</template>
<Metrics :type="configType" :isExpression="isExpression" @update="updateSettings" />
<Metrics :type="configType" @update="updateSettings" />
</el-popover>
</div>
<div v-if="isExpression">
<div>
<Tags
:tags="states.nodeExpressions"
:vertical="true"
:text="t('addExpressions')"
@change="(param) => changeNodeExpressions(param)"
@change="(param: string[]) => changeNodeExpressions(param)"
/>
</div>
<Selector
v-else
class="inputs"
:multiple="true"
:value="states.nodeMetrics"
:options="states.nodeMetricList"
size="small"
placeholder="Select metrics"
@change="updateNodeMetrics"
/>
</div>
<div class="legend-settings" v-show="isService">
<div v-show="isService">
<h5 class="title">{{ t("legendSettings") }}</h5>
<span v-if="isExpression">
<span>
<div class="label">Healthy Description</div>
<el-input v-model="description.healthy" placeholder="Please input description" size="small" class="mt-5" />
</span>
<div class="label">
<span>{{ t(isExpression ? "unhealthyExpression" : "conditions") }}</span>
<span>{{ t("unhealthyExpression") }}</span>
<el-tooltip
class="cp"
v-if="isExpression"
content="The node would be red to indicate unhealthy status when the expression return greater than 0"
>
<span>
@@ -200,53 +143,12 @@ limitations under the License. -->
</span>
</el-tooltip>
</div>
<div v-if="isExpression">
<div>
<el-input v-model="legendMQE.expression" placeholder="Please input a expression" size="small" class="inputs" />
</div>
<div v-for="(metric, index) of legend" :key="index" v-else>
<Selector
class="item"
:value="metric.name"
:options="states.nodeMetricList"
size="small"
placeholder="Select a metric"
@change="changeLegend(LegendOpt.NAME, $event, index)"
/>
<Selector
class="input-small"
:value="metric.condition"
:options="MetricConditions"
size="small"
placeholder="Select a condition"
@change="changeLegend(LegendOpt.CONDITION, $event, index)"
/>
<el-input
v-model="metric.value"
placeholder="Please input a value"
type="number"
@change="changeLegend(LegendOpt.VALUE, $event, index)"
size="small"
class="item"
/>
<span>
<Icon class="cp delete" iconName="remove_circle_outline" size="middle" @click="deleteMetric(index)" />
<Icon
class="cp"
iconName="add_circle_outlinecontrol_point"
size="middle"
v-show="index === legend.length - 1 && legend.length < 5"
@click="addMetric"
/>
</span>
<div v-show="index !== legend.length - 1">&&</div>
</div>
<span v-if="!isExpression">
<div class="label">Healthy Description</div>
<el-input v-model="description.healthy" placeholder="Please input description" size="small" class="mt-5" />
</span>
<div class="label">Unhealthy Description</div>
<el-input v-model="description.unhealthy" placeholder="Please input description" size="small" class="mt-5" />
<el-button @click="setLegend" class="legend-btn" size="small" type="primary">
<el-button @click="setLegend" class="mt-20" size="small" type="primary">
{{ t("setLegend") }}
</el-button>
</div>
@@ -257,21 +159,11 @@ limitations under the License. -->
import { useDashboardStore } from "@/store/modules/dashboard";
import { useTopologyStore } from "@/store/modules/topology";
import { ElMessage } from "element-plus";
import {
MetricCatalog,
ScopeType,
MetricConditions,
EntityType,
LegendOpt,
MetricsType,
MetricModes,
CallTypes,
} from "../../../data";
import { ScopeType, EntityType, CallTypes } from "@/views/dashboard/data";
import type { Option } from "@/types/app";
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
import type { Node } from "@/types/topology";
import type { DashboardItem, MetricConfigOpt } from "@/types/dashboard";
import type { Node } from "@/types/topology";
import Metrics from "./Metrics.vue";
/*global defineEmits */
@@ -280,7 +172,6 @@ limitations under the License. -->
const dashboardStore = useDashboardStore();
const topologyStore = useTopologyStore();
const { selectedGrid } = dashboardStore;
const isExpression = ref<boolean>(dashboardStore.selectedGrid.metricMode === MetricModes.Expression);
const nodeDashboard =
selectedGrid.nodeDashboard && selectedGrid.nodeDashboard.length ? selectedGrid.nodeDashboard : "";
const isService = [EntityType[0].value, EntityType[1].value].includes(dashboardStore.entity);
@@ -296,11 +187,6 @@ limitations under the License. -->
scope: string;
dashboard: string;
}[];
linkServerMetrics: string[];
linkClientMetrics: string[];
nodeMetrics: string[];
nodeMetricList: Option[];
linkMetricList: Option[];
linkDashboards: (DashboardItem & { label: string; value: string })[];
nodeDashboards: (DashboardItem & { label: string; value: string })[];
linkServerExpressions: string[];
@@ -309,27 +195,18 @@ limitations under the License. -->
}>({
linkDashboard: selectedGrid.linkDashboard || "",
nodeDashboard: selectedGrid.nodeDashboard || [],
linkServerMetrics: selectedGrid.linkServerMetrics || [],
linkClientMetrics: selectedGrid.linkClientMetrics || [],
nodeMetrics: selectedGrid.nodeMetrics || [],
nodeMetricList: [],
linkMetricList: [],
linkDashboards: [],
nodeDashboards: [],
linkServerExpressions: selectedGrid.linkServerExpressions || [],
linkClientExpressions: selectedGrid.linkClientExpressions || [],
nodeExpressions: selectedGrid.nodeExpressions || [],
});
const l = selectedGrid.legend && selectedGrid.legend.length;
const legend = ref<{ name: string; condition: string; value: string }[]>(
l ? selectedGrid.legend : [{ name: "", condition: "", value: "" }],
);
const legendMQE = ref<{ expression: string }>(selectedGrid.legendMQE || { expression: "" });
const configType = ref<string>("");
const description = reactive<any>(selectedGrid.description || {});
getMetricList();
async function getMetricList() {
getDashboardList();
async function getDashboardList() {
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
const json = await dashboardStore.fetchMetricList();
if (json.errors) {
@@ -351,13 +228,6 @@ limitations under the License. -->
},
[],
);
states.nodeMetricList = (json.data.metrics || []).filter(
(d: { type: string }) => d.type === MetricsType.REGULAR_VALUE,
);
states.linkMetricList = (json.data.metrics || []).filter(
(d: { catalog: string; type: string }) =>
entity + "Relation" === (MetricCatalog as any)[d.catalog] && d.type === MetricsType.REGULAR_VALUE,
);
if (isService) {
return;
}
@@ -373,13 +243,11 @@ limitations under the License. -->
}
async function setLegend() {
updateSettings();
if (isExpression.value) {
const expression = dashboardStore.selectedGrid.legendMQE && dashboardStore.selectedGrid.legendMQE.expression;
if (!expression) {
emit("updateNodes");
return;
}
const { getExpressionQuery } = useQueryTopologyExpressionsProcessor([expression], topologyStore.nodes);
const { getExpressionQuery } = useQueryTopologyExpressionsProcessor(
[expression],
topologyStore.nodes.filter((d: Node) => d.isReal),
);
const param = getExpressionQuery();
const res = await topologyStore.getNodeExpressionValue(param);
if (res.errors) {
@@ -387,22 +255,6 @@ limitations under the License. -->
} else {
topologyStore.setLegendValues([expression], res.data);
}
} else {
const names = dashboardStore.selectedGrid.legend.map((d: any) => d.name && d.condition && d.value);
if (!names.length) {
emit("updateNodes");
return;
}
const ids = topologyStore.nodes.map((d: Node) => d.id);
const param = await useQueryTopologyMetrics(names, ids);
const res = await topologyStore.getLegendMetrics(param);
if (res.errors) {
ElMessage.error(res.errors);
}
}
emit("updateNodes");
}
function changeNodeDashboard(opt: any) {
states.nodeDashboard = opt[0].value;
@@ -412,9 +264,6 @@ limitations under the License. -->
states.linkDashboard = opt[0].value;
updateSettings();
}
function changeLegend(type: string, opt: any, index: number) {
(legend.value[index] as any)[type] = opt[0].value || opt;
}
function changeScope(index: number, opt: Option[] | any) {
items[index].scope = opt[0].value;
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
@@ -445,27 +294,15 @@ limitations under the License. -->
updateSettings();
}
function updateSettings(metricConfig?: { [key: string]: MetricConfigOpt[] }) {
let metrics = [];
if (isExpression.value) {
metrics = legend.value.filter((d: any) => d.name);
} else {
metrics = legend.value.filter((d: any) => d.name && d.value && d.condition);
}
const param = {
...dashboardStore.selectedGrid,
linkDashboard: states.linkDashboard,
nodeDashboard: isService
? items.filter((d: { scope: string; dashboard: string }) => d.dashboard)
: states.nodeDashboard,
linkServerMetrics: states.linkServerMetrics,
linkClientMetrics: states.linkClientMetrics,
nodeMetrics: states.nodeMetrics,
linkServerExpressions: states.linkServerExpressions,
linkClientExpressions: states.linkClientExpressions,
nodeExpressions: states.nodeExpressions,
metricMode: isExpression.value ? MetricModes.Expression : MetricModes.General,
legend: metrics,
legendMQE: legendMQE.value,
...metricConfig,
description,
@@ -474,30 +311,8 @@ limitations under the License. -->
dashboardStore.setConfigs(param);
emit("update", param);
}
function updateLinkServerMetrics(options: Option[] | any) {
const opt = options.map((d: Option) => d.value);
const index = states.linkServerMetrics.findIndex((d: any) => !opt.includes(d));
states.linkServerMetrics = opt;
if (index < 0) {
changeLinkServerMetrics();
return;
}
const origin = dashboardStore.selectedGrid.linkServerMetricConfig || [];
const config = origin.length === 1 ? [] : origin.splice(index, 1);
changeLinkServerMetrics({ linkServerMetricConfig: config });
}
async function changeLinkServerMetrics(config?: { [key: string]: MetricConfigOpt[] }) {
updateSettings(config);
if (!states.linkServerMetrics.length) {
topologyStore.setLinkServerMetrics({});
return;
}
topologyStore.getLinkServerMetrics(states.linkServerMetrics);
}
function changeLinkServerExpressions(param: string[]) {
if (!isExpression.value) {
return;
}
states.linkServerExpressions = param;
updateSettings();
if (!states.linkServerExpressions.length) {
@@ -507,9 +322,6 @@ limitations under the License. -->
topologyStore.getLinkExpressions(states.linkServerExpressions, CallTypes.Server);
}
function changeLinkClientExpressions(param: string[]) {
if (!isExpression.value) {
return;
}
states.linkClientExpressions = param;
updateSettings();
if (!states.linkClientExpressions.length) {
@@ -518,63 +330,11 @@ limitations under the License. -->
}
topologyStore.getLinkExpressions(states.linkClientExpressions, CallTypes.Client);
}
function updateLinkClientMetrics(options: Option[] | any) {
const opt = options.map((d: Option) => d.value);
const index = states.linkClientMetrics.findIndex((d: any) => !opt.includes(d));
states.linkClientMetrics = opt;
if (index < 0) {
changeLinkClientMetrics();
return;
}
const origin = dashboardStore.selectedGrid.linkClientMetricConfig || [];
const config = origin.length === 1 ? [] : origin.splice(index, 1);
changeLinkClientMetrics({ linkClientMetricConfig: config });
}
async function changeLinkClientMetrics(config?: { [key: string]: MetricConfigOpt[] }) {
updateSettings(config);
if (!states.linkClientMetrics.length) {
topologyStore.setLinkClientMetrics({});
return;
}
topologyStore.getLinkClientMetrics(states.linkClientMetrics);
}
function updateNodeMetrics(options: Option[] | any) {
const opt = options.map((d: Option) => d.value);
const index = states.nodeMetrics.findIndex((d: any) => !opt.includes(d));
states.nodeMetrics = opt;
if (index < 0) {
changeNodeMetrics();
return;
}
const origin = dashboardStore.selectedGrid.nodeMetricConfig || [];
const config = origin.length === 1 ? [] : origin.splice(index, 1);
changeNodeMetrics({ nodeMetricConfig: config });
}
async function changeNodeMetrics(config?: { [key: string]: MetricConfigOpt[] }) {
updateSettings(config);
if (!states.nodeMetrics.length) {
topologyStore.setNodeMetricValue({});
return;
}
topologyStore.queryNodeMetrics(states.nodeMetrics);
}
function deleteMetric(index: number) {
if (legend.value.length === 1) {
legend.value = [{ name: "", condition: "", value: "" }];
return;
}
legend.value.splice(index, 1);
}
function addMetric() {
legend.value.push({ name: "", condition: "", value: "" });
}
function setConfigType(type: string) {
configType.value = type;
}
function changeNodeExpressions(param: string[]) {
if (!isExpression.value) {
return;
}
states.nodeExpressions = param;
updateSettings();
if (!states.nodeExpressions.length) {
@@ -583,31 +343,8 @@ limitations under the License. -->
}
topologyStore.queryNodeExpressions(states.nodeExpressions);
}
function changeMetricMode() {
legend.value = [{ name: "", condition: "", value: "" }];
const config = {
linkServerMetricConfig: [],
linkClientMetricConfig: [],
nodeMetricConfig: [],
};
if (isExpression.value) {
states.linkServerMetrics = [];
states.linkClientMetrics = [];
states.nodeMetrics = [];
} else {
states.linkServerExpressions = [];
states.linkClientExpressions = [];
states.nodeExpressions = [];
}
updateSettings(config);
}
</script>
<style lang="scss" scoped>
.link-settings {
margin-bottom: 20px;
}
.inputs {
margin-top: 8px;
width: 355px;
@@ -640,7 +377,6 @@ limitations under the License. -->
}
.legend-btn {
margin: 20px 0;
cursor: pointer;
}

View File

@@ -0,0 +1,176 @@
<!-- 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="hierarchy-services-topo"
v-loading="loading"
element-loading-background="rgba(0, 0, 0, 0)"
:style="`height: ${height}px`"
>
<Graph
:nodes="topologyStore.hierarchyInstanceNodes"
:calls="topologyStore.hierarchyInstanceCalls"
:entity="EntityType[3].value"
@getNodeMetrics="getNodeMetrics"
@showNodeTip="showNodeTip"
@handleNodeClick="handleNodeClick"
@hideTip="hideTip"
/>
<div id="popover"></div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, nextTick } from "vue";
import * as d3 from "d3";
import type { HierarchyNode } from "@/types/topology";
import { useTopologyStore } from "@/store/modules/topology";
import { useDashboardStore } from "@/store/modules/dashboard";
import { EntityType, ConfigFieldTypes } from "@/views/dashboard/data";
import router from "@/router";
import { ElMessage } from "element-plus";
import type { MetricConfigOpt } from "@/types/dashboard";
import getDashboard from "@/hooks/useDashboardsSession";
import Graph from "../components/Graph.vue";
/*global Nullable*/
const topologyStore = useTopologyStore();
const dashboardStore = useDashboardStore();
const height = ref<number>(100);
const loading = ref<boolean>(false);
const popover = ref<Nullable<any>>(null);
onMounted(async () => {
await nextTick();
init();
});
getTopology();
async function init() {
const dom = document.querySelector(".hierarchy-related")?.getBoundingClientRect() || {
height: 80,
width: 0,
};
height.value = dom.height - 80;
popover.value = d3.select("#popover");
}
async function getTopology() {
loading.value = true;
const resp = await topologyStore.getHierarchyInstanceTopology();
loading.value = false;
if (resp && resp.errors) {
ElMessage.error(resp.errors);
}
}
async function getNodeMetrics() {
const layerList = [];
const layerMap = new Map();
for (const n of topologyStore.hierarchyInstanceNodes) {
if (layerMap.get(n.layer)) {
const arr = layerMap.get(n.layer);
arr.push(n);
layerMap.set(n.layer, arr);
} else {
layerMap.set(n.layer, [n]);
}
}
for (const d of layerMap.values()) {
layerList.push(d);
}
for (const list of layerList) {
const { dashboard } = getDashboard(
{
layer: list[0].layer || "",
entity: EntityType[3].value,
},
ConfigFieldTypes.ISDEFAULT,
);
const exp = (dashboard && dashboard.expressions) || [];
await topologyStore.queryHierarchyInstanceNodeExpressions(exp, list[0].layer);
}
}
function showNodeTip(event: MouseEvent, data: HierarchyNode) {
if (!data) {
return;
}
if (!popover.value) {
return;
}
const dashboard =
getDashboard(
{
layer: data.layer || "",
entity: EntityType[3].value,
},
ConfigFieldTypes.ISDEFAULT,
).dashboard || {};
const exprssions = dashboard.expressions || [];
const nodeMetricConfig = dashboard.expressionsConfig || [];
const html = exprssions.map((m: string, index: number) => {
const metric =
topologyStore.hierarchyInstanceNodeMetrics[data.layer || ""][m].values.find(
(val: { id: string; value: string }) => val.id === data.id,
) || {};
const opt: MetricConfigOpt = nodeMetricConfig[index] || {};
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${metric.value || NaN} ${
opt.unit || ""
}</div>`;
});
const tipHtml = [
`<div class="mb-5"><span class="grey">name: </span>${data.name}</div><div class="mb-5"><span class="grey">layer: </span>${data.layer}</div>`,
...html,
].join(" ");
popover.value
.style("top", event.offsetY + 10 + "px")
.style("left", event.offsetX + 10 + "px")
.style("visibility", "visible")
.html(tipHtml);
}
function hideTip() {
popover.value.style("visibility", "hidden");
}
function handleNodeClick(event: MouseEvent, d: HierarchyNode & { serviceId: string }) {
const origin = dashboardStore.entity;
event.stopPropagation();
hideTip();
const { dashboard } = getDashboard(
{
layer: d.layer || "",
entity: EntityType[3].value,
},
ConfigFieldTypes.ISDEFAULT,
);
if (!dashboard) {
return;
}
const name = dashboard.name;
const path = `/dashboard/${dashboard.layer}/${EntityType[3].value}/${d.serviceId}/${d.key}/${name}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
dashboardStore.setEntity(origin);
}
</script>
<style lang="scss" scoped>
@import url("../style.scss");
</style>

View File

@@ -69,10 +69,10 @@ limitations under the License. -->
import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app";
import { EntityType, DepthList, MetricModes, CallTypes } from "../../../data";
import { EntityType, DepthList, CallTypes } from "@/views/dashboard/data";
import { ElMessage } from "element-plus";
import Sankey from "./Sankey.vue";
import Settings from "./Settings.vue";
import Settings from "../config/Settings.vue";
import router from "@/router";
import getDashboard from "@/hooks/useDashboardsSession";
@@ -119,15 +119,9 @@ limitations under the License. -->
};
height.value = dom.height - 70;
width.value = dom.width - 5;
if (settings.value.metricMode === MetricModes.Expression) {
topologyStore.queryNodeExpressions(settings.value.nodeExpressions || []);
topologyStore.getLinkExpressions(settings.value.linkClientExpressions || [], CallTypes.Client);
topologyStore.getLinkExpressions(settings.value.linkServerExpressions || [], CallTypes.Server);
} else {
topologyStore.getLinkClientMetrics(settings.value.linkClientMetrics || []);
topologyStore.getLinkServerMetrics(settings.value.linkServerMetrics || []);
topologyStore.queryNodeMetrics(settings.value.nodeMetrics || []);
}
}
function resize() {

View File

@@ -22,8 +22,6 @@ limitations under the License. -->
import { useTopologyStore } from "@/store/modules/topology";
import type { Node, Call } from "@/types/topology";
import type { MetricConfigOpt } from "@/types/dashboard";
import { aggregation } from "@/hooks/useMetricsProcessor";
import { MetricModes } from "../../../data";
import { useAppStoreWithOut } from "@/store/modules/app";
import { Themes } from "@/constants/data";
@@ -85,14 +83,8 @@ limitations under the License. -->
};
}
function linkTooltip(data: Call) {
const clientMetrics: string[] =
props.settings.metricMode === MetricModes.Expression
? props.settings.linkClientExpressions
: props.settings.linkClientMetrics;
const serverMetrics: string[] =
props.settings.metricMode === MetricModes.Expression
? props.settings.linkServerExpressions
: props.settings.linkServerMetrics;
const clientMetrics: string[] = props.settings.linkClientExpressions;
const serverMetrics: string[] = props.settings.linkServerExpressions;
const linkServerMetricConfig: MetricConfigOpt[] = props.settings.linkServerMetricConfig || [];
const linkClientMetricConfig: MetricConfigOpt[] = props.settings.linkClientMetricConfig || [];
@@ -102,8 +94,10 @@ limitations under the License. -->
{};
if (metric) {
const opt: MetricConfigOpt = linkServerMetricConfig[index] || {};
const v = aggregation(metric.value, opt);
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${v} ${opt.unit || ""}</div>`;
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${metric.value} ${
opt.unit || ""
}</div>`;
}
});
const htmlClient = clientMetrics.map((m, index) => {
@@ -111,8 +105,8 @@ limitations under the License. -->
const metric =
topologyStore.linkClientMetrics[m].values.find((val: { id: string; value: unknown }) => val.id === data.id) ||
{};
const v = aggregation(metric.value, opt);
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${v} ${opt.unit || ""}</div>`;
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${metric.value} ${opt.unit || ""}</div>`;
});
const html = [
`<div>${data.sourceObj.serviceName} -> ${data.targetObj.serviceName}</div>`,
@@ -124,17 +118,14 @@ limitations under the License. -->
}
function nodeTooltip(data: Node) {
const nodeMetrics: string[] =
props.settings.metricMode === MetricModes.Expression
? props.settings.nodeExpressions
: props.settings.nodeMetrics;
const nodeMetrics: string[] = props.settings.nodeExpressions;
const nodeMetricConfig = props.settings.nodeMetricConfig || [];
const html = nodeMetrics.map((m, index) => {
const metric =
topologyStore.nodeMetricValue[m].values.find((val: { id: string; value: unknown }) => val.id === data.id) || {};
const opt: MetricConfigOpt = nodeMetricConfig[index] || {};
const v = aggregation(metric.value, opt);
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${v} ${opt.unit || ""}</div>`;
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${metric.value} ${opt.unit || ""}</div>`;
});
return [` <div><span>name: </span>${data.serviceName}</div>`, ...html].join(" ");
}

View File

@@ -0,0 +1,184 @@
<!-- 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="hierarchy-services-topo"
v-loading="loading"
element-loading-background="rgba(0, 0, 0, 0)"
:style="`height: ${height}px`"
>
<Graph
:config="config"
:nodes="topologyStore.hierarchyServiceNodes"
:calls="topologyStore.hierarchyServiceCalls"
:entity="EntityType[0].value"
@getNodeMetrics="getNodeMetrics"
@showNodeTip="showNodeTip"
@handleNodeClick="handleNodeClick"
@hideTip="hideTip"
/>
<div id="popover"></div>
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { ref, onMounted, nextTick } from "vue";
import * as d3 from "d3";
import type { HierarchyNode } from "@/types/topology";
import { useTopologyStore } from "@/store/modules/topology";
import { useDashboardStore } from "@/store/modules/dashboard";
import { EntityType, ConfigFieldTypes } from "@/views/dashboard/data";
import router from "@/router";
import { ElMessage } from "element-plus";
import type { MetricConfigOpt } from "@/types/dashboard";
import getDashboard from "@/hooks/useDashboardsSession";
import Graph from "../components/Graph.vue";
/*global Nullable, defineProps */
defineProps({
config: {
type: Object as PropType<any>,
default: () => ({}),
},
});
const topologyStore = useTopologyStore();
const dashboardStore = useDashboardStore();
const height = ref<number>(100);
const loading = ref<boolean>(false);
const popover = ref<Nullable<any>>(null);
onMounted(async () => {
await nextTick();
init();
});
getTopology();
async function init() {
const dom = document.querySelector(".hierarchy-related")?.getBoundingClientRect() || {
height: 80,
width: 0,
};
height.value = dom.height - 80;
popover.value = d3.select("#popover");
}
async function getTopology() {
loading.value = true;
const resp = await topologyStore.getHierarchyServiceTopology();
loading.value = false;
if (resp && resp.errors) {
ElMessage.error(resp.errors);
}
}
async function getNodeMetrics() {
const layerList = [];
const layerMap = new Map();
for (const n of topologyStore.hierarchyServiceNodes) {
if (layerMap.get(n.layer)) {
const arr = layerMap.get(n.layer);
arr.push(n);
layerMap.set(n.layer, arr);
} else {
layerMap.set(n.layer, [n]);
}
}
for (const d of layerMap.values()) {
layerList.push(d);
}
for (const list of layerList) {
const { dashboard } = getDashboard(
{
layer: list[0].layer || "",
entity: EntityType[0].value,
},
ConfigFieldTypes.ISDEFAULT,
);
const exp = (dashboard && dashboard.expressions) || [];
await topologyStore.queryHierarchyNodeExpressions(exp, list[0].layer);
}
}
function showNodeTip(event: MouseEvent, data: HierarchyNode) {
if (!data) {
return;
}
if (!popover.value) {
return;
}
const dashboard =
getDashboard(
{
layer: data.layer || "",
entity: EntityType[0].value,
},
ConfigFieldTypes.ISDEFAULT,
).dashboard || {};
const exprssions = dashboard.expressions || [];
const nodeMetricConfig = dashboard.expressionsConfig || [];
const html = exprssions.map((m: string, index: number) => {
const metric =
topologyStore.hierarchyNodeMetrics[data.layer || ""][m].values.find(
(val: { id: string; value: string }) => val.id === data.id,
) || {};
const opt: MetricConfigOpt = nodeMetricConfig[index] || {};
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${metric.value || NaN} ${
opt.unit || ""
}</div>`;
});
const tipHtml = [
`<div class="mb-5"><span class="grey">name: </span>${data.name}</div><div class="mb-5"><span class="grey">layer: </span>${data.layer}</div>`,
...html,
].join(" ");
popover.value
.style("top", event.offsetY + 10 + "px")
.style("left", event.offsetX + 10 + "px")
.style("visibility", "visible")
.html(tipHtml);
}
function hideTip() {
popover.value.style("visibility", "hidden");
}
function handleNodeClick(event: MouseEvent, d: HierarchyNode) {
const origin = dashboardStore.entity;
event.stopPropagation();
hideTip();
const { dashboard } = getDashboard(
{
layer: d.layer || "",
entity: EntityType[0].value,
},
ConfigFieldTypes.ISDEFAULT,
);
if (!dashboard) {
return;
}
const name = dashboard.name;
const path = `/dashboard/${dashboard.layer}/${EntityType[0].value}/${d.key}/${name}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
dashboardStore.setEntity(origin);
}
</script>
<style lang="scss" scoped>
@import url("../style.scss");
</style>

View File

@@ -0,0 +1,746 @@
<!-- 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 ref="chart" class="micro-topo-chart" v-loading="loading" :style="`height: ${height}px`">
<svg class="svg-topology" :width="width - 100" :height="height" @click="svgEvent">
<g class="svg-graph" :transform="`translate(${diff[0]}, ${diff[1]})`">
<g
class="topo-node"
v-for="(n, index) in topologyLayout.nodes"
:key="index"
@mouseout="hideTip"
@mouseover="showNodeTip($event, n)"
@click="handleNodeClick($event, n)"
@mousedown="startMoveNode($event, n)"
@mouseup="stopMoveNode($event)"
>
<image width="36" height="36" :x="n.x - 15" :y="n.y - 18" :href="getNodeStatus(n)" />
<image width="28" height="25" :x="n.x - 14" :y="n.y - 43" :href="icons.LOCAL" style="opacity: 0.8" />
<image
width="12"
height="12"
:x="n.x - 6"
:y="n.y - 38"
:href="!n.type || n.type === `N/A` ? icons.UNDEFINED : icons[n.type.toUpperCase().replace('-', '')]"
/>
<text
class="node-text"
:x="n.x - (Math.min(n.name.length, 20) * 6) / 2 + 6"
:y="n.y + n.height + 8"
style="pointer-events: none"
>
{{ n.name.length > 20 ? `${n.name.substring(0, 20)}...` : n.name }}
</text>
</g>
<g v-for="(l, index) in topologyLayout.calls" :key="index">
<path
class="topo-line"
:d="`M${l.sourceX} ${l.sourceY} L${l.targetX} ${l.targetY}`"
stroke="#97B0F8"
marker-end="url(#arrow)"
/>
<circle
class="topo-line-anchor"
:cx="(l.sourceX + l.targetX) / 2"
:cy="(l.sourceY + l.targetY) / 2"
r="4"
fill="#97B0F8"
@click="handleLinkClick($event, l)"
@mouseover="showLinkTip($event, l)"
@mouseout="hideTip"
/>
</g>
<g class="arrows">
<defs v-for="(_, index) in topologyLayout.calls" :key="index">
<marker
id="arrow"
markerUnits="strokeWidth"
markerWidth="16"
markerHeight="16"
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 class="legend">
<div>
<img :src="icons.CUBE" />
<span>
{{ settings.description ? settings.description.healthy || "" : "" }}
</span>
</div>
<div>
<img :src="icons.CUBEERROR" />
<span>
{{ settings.description ? settings.description.unhealthy || "" : "" }}
</span>
</div>
</div>
<div class="setting" v-if="showSetting && dashboardStore.editMode">
<Settings @update="updateSettings" @updateNodes="freshNodes" />
</div>
<div class="tool">
<span v-show="graphConfig.showDepth">
<span class="label">{{ t("currentDepth") }}</span>
<Selector class="inputs" :value="depth" :options="DepthList" @change="changeDepth" />
</span>
<span class="switch-icon ml-5" title="Settings" @click="setConfig" v-if="dashboardStore.editMode">
<Icon size="middle" iconName="settings" />
</span>
<span class="switch-icon ml-5" title="Back to overview topology" @click="backToTopology">
<Icon size="middle" iconName="keyboard_backspace" />
</span>
</div>
<div
class="operations-list"
v-if="topologyStore.node"
:style="{
top: operationsPos.y + 5 + 'px',
left: operationsPos.x + 5 + 'px',
}"
>
<span v-for="(item, index) of items" :key="index" @click="item.func(item)">
{{ item.title }}
</span>
</div>
<el-dialog
v-model="hierarchyRelated"
:title="getHierarchyTitle()"
:destroy-on-close="true"
@closed="hierarchyRelated = false"
width="640px"
>
<div class="hierarchy-related">
<hierarchy-map :config="config" />
</div>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { ref, onMounted, onBeforeUnmount, reactive, watch, computed, nextTick } from "vue";
import { useI18n } from "vue-i18n";
import * as d3 from "d3";
import type { Node, Call } from "@/types/topology";
import { useSelectorStore } from "@/store/modules/selectors";
import { useTopologyStore } from "@/store/modules/topology";
import { useDashboardStore } from "@/store/modules/dashboard";
import { EntityType, DepthList, CallTypes } from "@/views/dashboard/data";
import router from "@/router";
import { ElMessage } from "element-plus";
import Settings from "../config/Settings.vue";
import HierarchyMap from "./HierarchyMap.vue";
import type { Option } from "@/types/app";
import type { Service } from "@/types/selector";
import { useAppStoreWithOut } from "@/store/modules/app";
import getDashboard from "@/hooks/useDashboardsSession";
import type { MetricConfigOpt } from "@/types/dashboard";
import icons from "@/assets/img/icons";
import { layout, computeLevels, changeNode } from "../components/utils/layout";
import zoom from "@/views/dashboard/related/components/utils/zoom";
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
import { ConfigFieldTypes } from "@/views/dashboard/data";
/*global Nullable, defineProps */
const props = defineProps({
config: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
});
const { t } = useI18n();
const selectorStore = useSelectorStore();
const topologyStore = useTopologyStore();
const dashboardStore = useDashboardStore();
const appStore = useAppStoreWithOut();
const height = ref<number>(100);
const width = ref<number>(100);
const loading = ref<boolean>(false);
const svg = ref<Nullable<any>>(null);
const graph = ref<Nullable<any>>(null);
const chart = ref<Nullable<HTMLDivElement>>(null);
const showSetting = ref<boolean>(false);
const settings = ref<any>(props.config);
const operationsPos = reactive<{ x: number; y: number }>({ x: NaN, y: NaN });
const items = ref<{ id: string; title: string; func: any; dashboard?: string }[]>([]);
const graphConfig = computed(() => props.config.graph || {});
const depth = ref<number>(graphConfig.value.depth || 2);
const topologyLayout = ref<any>({});
const tooltip = ref<Nullable<any>>(null);
const graphWidth = ref<number>(100);
const currentNode = ref<Nullable<Node>>(null);
const diff = computed(() => [(width.value - graphWidth.value - 130) / 2, 100]);
const hierarchyRelated = ref<boolean>(false);
const radius = 8;
onMounted(async () => {
await nextTick();
setTimeout(() => {
init();
}, 10);
});
async function init() {
const dom = document.querySelector(".topology")?.getBoundingClientRect() || {
height: 40,
width: 0,
};
height.value = dom.height - 40;
width.value = dom.width;
svg.value = d3.select(".svg-topology");
graph.value = d3.select(".svg-graph");
loading.value = true;
const json = await selectorStore.fetchServices(dashboardStore.layerId);
if (json.errors) {
ElMessage.error(json.errors);
return;
}
await freshNodes();
svg.value.call(zoom(d3, graph.value, diff.value));
}
async function freshNodes() {
loading.value = true;
topologyStore.setNode(null);
topologyStore.setLink(null);
const resp = await getTopology();
loading.value = false;
if (resp && resp.errors) {
ElMessage.error(resp.errors);
}
await update();
}
async function update() {
topologyStore.queryNodeExpressions(settings.value.nodeExpressions || []);
topologyStore.getLinkExpressions(settings.value.linkClientExpressions || [], CallTypes.Client);
topologyStore.getLinkExpressions(settings.value.linkServerExpressions || [], CallTypes.Server);
window.addEventListener("resize", resize);
await initLegendMetrics();
draw();
tooltip.value = d3.select("#tooltip");
setNodeTools(settings.value.nodeDashboard);
}
function draw() {
const levels = computeLevels(topologyStore.calls, topologyStore.nodes, []);
topologyLayout.value = layout(levels, topologyStore.calls, radius);
graphWidth.value = topologyLayout.value.layout.width;
const drag: any = d3.drag().on("drag", (d: { x: number; y: number }) => {
topologyLayout.value.calls = changeNode(d, currentNode.value, topologyLayout.value, radius);
});
setTimeout(() => {
d3.selectAll(".topo-node").call(drag);
}, 1000);
}
function startMoveNode(event: MouseEvent, d: Node) {
event.stopPropagation();
currentNode.value = d;
}
function stopMoveNode(event: MouseEvent) {
event.stopPropagation();
currentNode.value = null;
}
function getHierarchyTitle() {
if (!currentNode.value) {
return;
}
if (currentNode.value.layers.includes(dashboardStore.layerId)) {
return `${dashboardStore.layerId} --> ${currentNode.value.name}`;
}
const layer = currentNode.value.layers.filter((d: string) => d !== dashboardStore.layerId);
if (layer.length) {
return `${layer[0]} --> ${currentNode.value.name}`;
}
return "";
}
async function initLegendMetrics() {
if (!topologyStore.nodes.length) {
return;
}
const expression = props.config.legendMQE && props.config.legendMQE.expression;
if (!expression) {
return;
}
const { getExpressionQuery } = useQueryTopologyExpressionsProcessor(
[expression],
topologyStore.nodes.filter((d: Node) => d.isReal),
);
const param = getExpressionQuery();
const res = await topologyStore.getNodeExpressionValue(param);
if (res.errors) {
ElMessage.error(res.errors);
} else {
topologyStore.setLegendValues([expression], res.data);
}
}
function getNodeStatus(d: any) {
const { legendMQE } = settings.value;
if (!legendMQE) {
return icons.CUBE;
}
if (!legendMQE.expression) {
return icons.CUBE;
}
return Number(d[legendMQE.expression]) && d.isReal ? icons.CUBEERROR : icons.CUBE;
}
function showNodeTip(event: MouseEvent, data: Node) {
const nodeMetrics: string[] = settings.value.nodeExpressions || [];
const nodeMetricConfig = settings.value.nodeMetricConfig || [];
const html = nodeMetrics.map((m, index) => {
const metric =
(topologyStore.nodeMetricValue[m] &&
topologyStore.nodeMetricValue[m].values.find((val: { id: string; value: string }) => val.id === data.id)) ||
{};
const opt: MetricConfigOpt = nodeMetricConfig[index] || {};
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${metric.value || NaN} ${
opt.unit || "unknown"
}</div>`;
});
let tipHtml = `<div class="mb-5"><span class="grey">name: </span>${
data.name
}</div><div class="mb-5"><span class="grey">type: </span>${data.type || "UNKNOWN"}</div>`;
if (data.isReal) {
tipHtml = [tipHtml, ...html].join(" ");
}
tooltip.value
.style("top", event.offsetY + 10 + "px")
.style("left", event.offsetX + 10 + "px")
.style("visibility", "visible")
.html(tipHtml);
}
function showLinkTip(event: MouseEvent, data: Call) {
const linkClientMetrics: string[] = settings.value.linkClientExpressions || [];
const linkServerMetricConfig: MetricConfigOpt[] = settings.value.linkServerMetricConfig || [];
const linkClientMetricConfig: MetricConfigOpt[] = settings.value.linkClientMetricConfig || [];
const linkServerMetrics: string[] = settings.value.linkServerExpressions || [];
const htmlServer = linkServerMetrics.map((m, index) => {
const metric = topologyStore.linkServerMetrics[m].values.find(
(val: { id: string; value: unknown }) => val.id === data.id,
);
if (metric) {
const opt: MetricConfigOpt = linkServerMetricConfig[index] || {};
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${metric.value || NaN} ${
opt.unit || ""
}</div>`;
}
});
const htmlClient = linkClientMetrics.map((m: string, index: number) => {
const opt: MetricConfigOpt = linkClientMetricConfig[index] || {};
const metric = topologyStore.linkClientMetrics[m].values.find(
(val: { id: string; value: unknown }) => val.id === data.id,
);
if (metric) {
return ` <div class="mb-5"><span class="grey">${opt.label || m}: </span>${metric.value || NaN} ${
opt.unit || ""
}</div>`;
}
});
const html = [
...htmlServer,
...htmlClient,
`<div><span class="grey">${t("detectPoint")}:</span>${data.detectPoints.join(" | ")}</div>`,
].join(" ");
tooltip.value
.style("top", event.offsetY + "px")
.style("left", event.offsetX + "px")
.style("visibility", "visible")
.html(html);
}
function hideTip() {
tooltip.value.style("visibility", "hidden");
}
function handleNodeClick(event: MouseEvent, d: Node & { x: number; y: number }) {
event.stopPropagation();
hideTip();
topologyStore.setNode(d);
topologyStore.setLink(null);
operationsPos.x = event.offsetX;
operationsPos.y = event.offsetY;
if (d.layers.includes(dashboardStore.layerId)) {
setNodeTools(settings.value.nodeDashboard);
return;
}
initNodeMenus();
}
function handleLinkClick(event: MouseEvent, d: Call) {
event.stopPropagation();
if (!d.sourceObj.layers.includes(dashboardStore.layerId) || !d.targetObj.layers.includes(dashboardStore.layerId)) {
return;
}
topologyStore.setNode(null);
topologyStore.setLink(d);
if (!settings.value.linkDashboard) {
return;
}
const origin = dashboardStore.entity;
const e = dashboardStore.entity === EntityType[1].value ? EntityType[0].value : dashboardStore.entity;
const { dashboard } = getDashboard({
name: settings.value.linkDashboard,
layer: dashboardStore.layerId,
entity: `${e}Relation`,
});
if (!dashboard) {
ElMessage.error(`The dashboard named ${settings.value.linkDashboard} doesn't exist`);
return;
}
dashboardStore.setEntity(dashboard.entity);
const path = `/dashboard/related/${dashboard.layer}/${e}Relation/${d.sourceObj.id}/${d.targetObj.id}/${dashboard.name}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
dashboardStore.setEntity(origin);
}
async function handleHierarchyRelatedServices() {
hierarchyRelated.value = true;
}
async function handleInspect() {
const id = topologyStore.node.id;
loading.value = true;
const resp = await topologyStore.getDepthServiceTopology([id], Number(depth.value));
loading.value = false;
if (resp && resp.errors) {
ElMessage.error(resp.errors);
}
await update();
topologyStore.setNode(null);
topologyStore.setLink(null);
}
function handleGoEndpoint(params: { dashboard: string }) {
if (!params.dashboard) {
return;
}
const origin = dashboardStore.entity;
const path = `/dashboard/${dashboardStore.layerId}/${EntityType[2].value}/${topologyStore.node.id}/${params.dashboard}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
dashboardStore.setEntity(origin);
}
function handleGoInstance(params: { dashboard: string }) {
if (!params.dashboard) {
return;
}
const origin = dashboardStore.entity;
const path = `/dashboard/${dashboardStore.layerId}/${EntityType[3].value}/${topologyStore.node.id}/${params.dashboard}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
dashboardStore.setEntity(origin);
}
function handleGoDashboard(params: { dashboard: string }) {
if (!params.dashboard) {
return;
}
const origin = dashboardStore.entity;
const path = `/dashboard/${dashboardStore.layerId}/${EntityType[0].value}/${topologyStore.node.id}/${params.dashboard}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
dashboardStore.setEntity(origin);
}
function handleGoAlerting() {
const path = `/alerting`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
}
function handleGoLayerDashboard(param: { id: string }) {
if (!(param.id && currentNode.value)) {
return;
}
const origin = dashboardStore.entity;
const { dashboard } = getDashboard(
{
layer: param.id,
entity: EntityType[0].value,
},
ConfigFieldTypes.ISDEFAULT,
);
if (!dashboard) {
return ElMessage.info("No Dashboard");
}
const path = `/dashboard/${param.id}/${EntityType[0].value}/${currentNode.value.id}/${dashboard.name}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
dashboardStore.setEntity(origin);
}
async function backToTopology() {
loading.value = true;
await freshNodes();
topologyStore.setNode(null);
topologyStore.setLink(null);
}
async function getTopology() {
const ids = selectorStore.services.map((d: Service) => d.id);
const serviceIds = dashboardStore.entity === EntityType[0].value ? [selectorStore.currentService.id] : ids;
const resp = await topologyStore.getDepthServiceTopology(serviceIds, Number(depth.value));
return resp;
}
function setConfig() {
showSetting.value = !showSetting.value;
dashboardStore.selectWidget(props.config);
}
function resize() {
const dom = document.querySelector(".topology")?.getBoundingClientRect() || {
height: 40,
width: 0,
};
height.value = dom.height - 40;
width.value = dom.width;
}
function updateSettings(config: any) {
settings.value = config;
setNodeTools(config.nodeDashboard);
}
function initNodeMenus() {
items.value = [
{
id: "hierarchyServices",
title: "Hierarchy Services",
func: handleHierarchyRelatedServices,
},
{ id: "inspect", title: "Inspect", func: handleInspect },
{ id: "alerting", title: "Alerting", func: handleGoAlerting },
];
if (!currentNode.value) {
return;
}
const diffLayers = currentNode.value.layers.filter(
(l: string) => l !== dashboardStore.layerId && l !== "UNDEFINED",
);
for (const l of diffLayers) {
items.value.push({
id: l,
title: `${l} Dashboard`,
func: handleGoLayerDashboard,
});
}
}
function setNodeTools(nodeDashboard: any) {
initNodeMenus();
if (!(nodeDashboard && nodeDashboard.length)) {
return;
}
for (const item of nodeDashboard) {
if (item.scope === EntityType[0].value) {
items.value.push({
id: "dashboard",
title: "Service Dashboard",
func: handleGoDashboard,
...item,
});
}
if (item.scope === EntityType[2].value) {
items.value.push({
id: "endpoint",
title: "Endpoint Dashboard",
func: handleGoEndpoint,
...item,
});
}
if (item.scope === EntityType[3].value) {
items.value.push({
id: "instance",
title: "Service Instance Dashboard",
func: handleGoInstance,
...item,
});
}
}
}
function svgEvent() {
topologyStore.setNode(null);
topologyStore.setLink(null);
dashboardStore.selectWidget(props.config);
}
async function changeDepth(opt: Option[] | any) {
depth.value = opt[0].value;
freshNodes();
}
onBeforeUnmount(() => {
window.removeEventListener("resize", resize);
});
watch(
() => [selectorStore.currentService, selectorStore.currentDestService],
(newVal, oldVal) => {
if (!(oldVal[0] && newVal[0])) {
return;
}
freshNodes();
hierarchyRelated.value = false;
},
);
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
freshNodes();
hierarchyRelated.value = false;
}
},
);
</script>
<style lang="scss" scoped>
.hierarchy-related {
height: 600px;
width: 600px;
overflow: hidden;
}
.micro-topo-chart {
position: relative;
overflow: auto;
margin-top: 30px;
.node-text {
fill: var(--sw-topology-color);
font-size: 12px;
opacity: 0.9;
}
.svg-topology {
cursor: move;
background-color: $theme-background;
}
.legend {
position: absolute;
top: 10px;
left: 25px;
color: var(--sw-topology-color);
div {
margin-bottom: 8px;
}
img {
width: 32px;
float: left;
}
span {
display: inline-block;
height: 32px;
line-height: 32px;
margin-left: 5px;
}
}
.setting {
position: absolute;
top: 80px;
right: 10px;
width: 400px;
height: 600px;
overflow: auto;
padding: 0 15px;
border-radius: 3px;
color: $disabled-color;
border: 1px solid $border-color-primary;
background-color: var(--sw-topology-setting-bg);
box-shadow: var(--sw-topology-box-shadow);
transition: all 0.5ms linear;
}
.label {
color: var(--sw-topology-color);
display: inline-block;
margin-right: 5px;
}
.operations-list {
position: absolute;
color: $font-color;
cursor: pointer;
border: var(--sw-topology-border);
border-radius: 3px;
background-color: $theme-background;
padding: 10px 0;
span {
display: block;
height: 30px;
line-height: 30px;
text-align: left;
padding: 0 15px;
}
span:hover {
color: $active-color;
background-color: $popper-hover-bg-color;
}
}
.tool {
position: absolute;
top: 35px;
right: 10px;
}
.switch-icon {
cursor: pointer;
transition: all 0.5ms linear;
background: var(--sw-topology-switch-icon);
color: $text-color;
display: inline-block;
padding: 2px 4px;
border-radius: 3px;
}
.topo-line {
stroke-linecap: round;
stroke-width: 1px;
stroke-dasharray: 10 10;
fill: none;
animation: var(--sw-topo-animation);
}
.topo-line-anchor,
.topo-node {
cursor: pointer;
}
}
.el-loading-spinner {
top: 30%;
}
#tooltip {
position: absolute;
visibility: hidden;
padding: 5px;
border: var(--sw-topology-border);
border-radius: 3px;
background-color: $theme-background;
}
</style>

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