Compare commits
47 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
065337c344 | ||
![]() |
21fe455fd6 | ||
![]() |
88dbee311c | ||
![]() |
9001a96128 | ||
![]() |
e4b2203cf6 | ||
![]() |
54c236bacf | ||
![]() |
f001290658 | ||
![]() |
13b2693f29 | ||
![]() |
731d652a7d | ||
![]() |
7f6e4d09c0 | ||
![]() |
12cd279c90 | ||
![]() |
62eb054ff5 | ||
![]() |
e0bbe99b6c | ||
![]() |
03f321b62a | ||
![]() |
460b24f42c | ||
![]() |
fd2c7ca716 | ||
![]() |
8bc6761468 | ||
![]() |
8746d3c985 | ||
![]() |
c18058765a | ||
![]() |
b9e0eadecb | ||
![]() |
680f1263a5 | ||
![]() |
0e0b4e1ff1 | ||
![]() |
2faeecebcc | ||
![]() |
e25bf9ee8b | ||
![]() |
d0ebdefcee | ||
![]() |
7342036646 | ||
![]() |
8e58f000a0 | ||
![]() |
931cea4c4c | ||
![]() |
ccb4d78f6b | ||
![]() |
860af150f7 | ||
![]() |
7d24e065e9 | ||
![]() |
7aef327d2e | ||
![]() |
f76500bb6e | ||
![]() |
63e01540dc | ||
![]() |
a46b91d1cf | ||
![]() |
5061b19cf7 | ||
![]() |
f809123b4f | ||
![]() |
306508856b | ||
![]() |
867af6924d | ||
![]() |
4ec99fc868 | ||
![]() |
b5c135b811 | ||
![]() |
300ec27ec4 | ||
![]() |
c5d80d96fb | ||
![]() |
001fa25a3b | ||
![]() |
0d82507a87 | ||
![]() |
591484b11c | ||
![]() |
b2ab93926d |
13
README.md
@@ -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
@@ -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": {
|
||||
|
@@ -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",
|
||||
|
@@ -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 |
@@ -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 |
@@ -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 |
18
src/assets/icons/hierarchy_topology.svg
Normal 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 |
138
src/assets/icons/logo-light.svg
Normal file
@@ -0,0 +1,138 @@
|
||||
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License. -->
|
||||
<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 |
17
src/assets/icons/workflow_scheduler.svg
Executable 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
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 8.5 KiB |
BIN
src/assets/img/tools/BROWSER.png
Normal file
After Width: | Height: | Size: 314 B |
BIN
src/assets/img/tools/DATABASE.png
Normal file
After Width: | Height: | Size: 150 B |
BIN
src/assets/img/tools/ELASTICSEARCH.png
Normal file
After Width: | Height: | Size: 150 B |
BIN
src/assets/img/tools/GENERAL.png
Normal file
After Width: | Height: | Size: 211 B |
BIN
src/assets/img/tools/K8S.png
Normal file
After Width: | Height: | Size: 471 B |
BIN
src/assets/img/tools/K8S_SERVICE.png
Normal file
After Width: | Height: | Size: 471 B |
BIN
src/assets/img/tools/MESH.png
Normal file
After Width: | Height: | Size: 262 B |
BIN
src/assets/img/tools/MESH_CP.png
Normal file
After Width: | Height: | Size: 262 B |
BIN
src/assets/img/tools/MESH_DP.png
Normal file
After Width: | Height: | Size: 262 B |
BIN
src/assets/img/tools/MONGODB.png
Normal file
After Width: | Height: | Size: 150 B |
BIN
src/assets/img/tools/MQ.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
src/assets/img/tools/NGINX.png
Normal file
After Width: | Height: | Size: 263 B |
BIN
src/assets/img/tools/OS_LINUX.png
Normal file
After Width: | Height: | Size: 241 B |
BIN
src/assets/img/tools/POSTGRESQL.png
Normal file
After Width: | Height: | Size: 150 B |
BIN
src/assets/img/tools/RABBITMQ.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
src/assets/img/tools/VIRTUAL_CACHE.png
Normal file
After Width: | Height: | Size: 211 B |
BIN
src/assets/img/tools/VIRTUAL_DATABASE.png
Normal file
After Width: | Height: | Size: 211 B |
BIN
src/assets/img/tools/VIRTUAL_GATEWAY.png
Normal file
After Width: | Height: | Size: 211 B |
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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>
|
||||
|
@@ -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 {
|
||||
|
@@ -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>
|
||||
|
@@ -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
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
@@ -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}}`;
|
||||
|
@@ -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 {
|
||||
|
@@ -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>;
|
||||
|
@@ -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,
|
||||
};
|
||||
|
@@ -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,
|
||||
};
|
||||
}
|
@@ -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;
|
||||
}
|
@@ -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) || {};
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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:
|
||||
|
@@ -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:
|
||||
|
@@ -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生态系统中的组件和服务器提供了可观察性。",
|
||||
|
@@ -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;
|
||||
|
@@ -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
@@ -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,
|
||||
},
|
||||
];
|
@@ -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",
|
||||
|
@@ -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) } });
|
||||
|
@@ -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);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -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;
|
||||
},
|
||||
|
@@ -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
@@ -59,7 +59,7 @@ export interface SubItem {
|
||||
icon: string;
|
||||
title: string;
|
||||
activate: boolean;
|
||||
name?: string;
|
||||
name: string;
|
||||
path?: string;
|
||||
notShow?: boolean;
|
||||
id?: string;
|
||||
|
10
src/types/dashboard.d.ts
vendored
@@ -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;
|
||||
}
|
||||
|
||||
|
9
src/types/global.d.ts
vendored
@@ -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;
|
||||
|
36
src/types/topology.d.ts
vendored
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
@@ -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>
|
@@ -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>
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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 || ""),
|
||||
|
110
src/views/dashboard/configuration/Tab.vue
Normal 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>
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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 }) {
|
||||
|
@@ -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>
|
||||
|
@@ -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,
|
||||
|
@@ -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;
|
||||
|
@@ -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>
|
||||
|
@@ -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;
|
||||
|
@@ -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",
|
||||
}
|
||||
|
@@ -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);
|
||||
},
|
||||
);
|
||||
|
@@ -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);
|
||||
},
|
||||
);
|
||||
|
@@ -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);
|
||||
},
|
||||
);
|
||||
|
@@ -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>
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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>
|
||||
|
@@ -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,
|
||||
() => {
|
||||
|
@@ -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) {
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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";
|
||||
|
@@ -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 {
|
||||
|
@@ -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(() => {
|
||||
|
@@ -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>
|
||||
|
@@ -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 */
|
||||
|
@@ -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>
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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 || [];
|
@@ -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;
|
||||
}
|
||||
|
176
src/views/dashboard/related/topology/pod/InstanceMap.vue
Normal 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>
|
@@ -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() {
|
@@ -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(" ");
|
||||
}
|
184
src/views/dashboard/related/topology/service/HierarchyMap.vue
Normal 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>
|
746
src/views/dashboard/related/topology/service/ServiceMap.vue
Normal 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>
|