Merge branch 'main' into Add-rocketmq-menu

This commit is contained in:
吴晟 Wu Sheng 2024-02-20 21:21:18 +08:00 committed by GitHub
commit 37ce9309dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
62 changed files with 2621 additions and 1176 deletions

439
package-lock.json generated
View File

@ -64,7 +64,7 @@
"typescript": "~4.7.4",
"unplugin-auto-import": "^0.7.0",
"unplugin-vue-components": "^0.19.2",
"vite": "^4.0.5",
"vite": "^4.5.2",
"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"
],
@ -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": {
@ -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.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
"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
},
@ -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": {
@ -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.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.2.tgz",
"integrity": "sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==",
"dev": true,
"requires": {
"esbuild": "^0.16.3",
"esbuild": "^0.18.10",
"fsevents": "~2.3.2",
"postcss": "^8.4.20",
"resolve": "^1.22.1",
"rollup": "^3.7.0"
"postcss": "^8.4.27",
"rollup": "^3.27.1"
}
},
"vite-plugin-monaco-editor": {

View File

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

View File

@ -0,0 +1,18 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg t="1704964118567" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5167">
<path d="M900.032 646.016h-56.064V502.976a16 16 0 0 0-16-16H544v-96h62.976c22.144 0 40-17.92 40-40V161.024a40 40 0 0 0-40-40H417.024a40 40 0 0 0-40 40v189.952c0 22.144 17.92 40 40 40H480v96H195.968a16 16 0 0 0-16 16v143.04h-55.936a38.016 38.016 0 0 0-38.016 38.016v176c0 20.928 17.024 37.952 37.952 37.952h176a38.016 38.016 0 0 0 38.016-38.016v-176a38.016 38.016 0 0 0-37.952-37.952h-56V550.976H480v95.04h-56a38.016 38.016 0 0 0-38.016 38.016v176c0 20.928 17.024 37.952 38.016 37.952h176a38.016 38.016 0 0 0 38.016-38.016v-176a38.016 38.016 0 0 0-38.016-37.952H544V550.976h236.032v95.04h-56.064a38.016 38.016 0 0 0-37.952 38.016v176c0 20.928 17.024 37.952 38.016 37.952h176a38.016 38.016 0 0 0 37.952-38.016v-176a38.016 38.016 0 0 0-38.016-37.952zM440.96 184.96h141.952v141.952H441.024V185.024zM278.016 838.016H145.92V705.92h132.032v132.032z m299.968 0H446.08V705.92H577.92v132.032z m300.032 0h-132.032V705.92h132.032v132.032z" p-id="5168"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

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

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

View File

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

View File

@ -79,7 +79,7 @@ limitations under the License. -->
</script>
<style lang="scss" scoped>
.input-name {
width: 300px;
width: 250px;
}
.vertical {

View File

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

View File

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

View File

@ -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(
(d: { name: string; layer: string; entity: string }) =>
d.name === opt.name && d.entity === opt.entity && d.layer === opt.layer,
);
let dashboard: Recordable;
if (type === ConfigFieldTypes.NAME) {
dashboard = list.find(
(d: { name: string; layer: string; entity: string }) =>
d.name === opt.name && d.entity === opt.entity && d.layer === opt.layer,
);
} else {
dashboard = list.find(
(d: { name: string; layer: string; entity: string; isDefault: boolean }) =>
d.isDefault && d.entity === opt.entity && d.layer === opt.layer,
);
}
const all = dashboardStore.layout;
const widgets: LayoutConfig[] = [];
for (const item of all) {
@ -52,6 +62,9 @@ export default function getDashboard(param?: { name: string; layer: string; enti
filters,
};
dashboardStore.setWidget(item);
if (widget.id === sourceId) {
return;
}
const targetTabIndex = (widget.id || "").split("-");
const sourceTabindex = (sourceId || "").split("-") || [];
let container: Nullable<Element>;

View File

@ -332,10 +332,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;
@ -347,6 +351,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;
}
@ -356,11 +364,11 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
}
const entity = {
serviceName,
normal: true,
normal,
serviceInstanceName,
endpointName,
destServiceName,
destNormal: destServiceName ? true : undefined,
destNormal: destServiceName ? destNormal : undefined,
destServiceInstanceName,
destEndpointName,
};

View File

@ -385,6 +385,7 @@ const msg = {
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 related with hierarchy topology nodes",
hierarchyNodeMetrics: "Metrics for Hierarchy Graph Node",
hierarchyNodeDashboard: "As dashboard for Hierarchy Graph Node",
};
export default msg;

View File

@ -385,6 +385,7 @@ const msg = {
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 related with hierarchy topology nodes",
hierarchyNodeMetrics: "Metrics for Hierarchy Graph Node",
hierarchyNodeDashboard: "As dashboard for Hierarchy Graph Node",
};
export default msg;

View File

@ -383,6 +383,7 @@ const msg = {
traceDesc:
"Trace Segment代表在单一操作系统进程例如JVM中执行的追踪部分。它包含了一组跨度spans这些跨度通常与单一请求或执行上下文关联。",
tabExpressions: "Tab表达式",
hierarchyNodeMetrics: "层级拓扑节点关联的指标",
hierarchyNodeMetrics: "层次图节点的指标",
hierarchyNodeDashboard: "作为层次图节点的dashboard",
};
export default msg;

View File

@ -21,6 +21,7 @@ import { routesMarketplace } from "./marketplace";
import { routesAlarm } from "./alarm";
import routesLayers from "./layer";
import { routesSettings } from "./settings";
import { routesNotFound } from "./notFound";
const routes: RouteRecordRaw[] = [
...routesMarketplace,
@ -28,6 +29,7 @@ const routes: RouteRecordRaw[] = [
...routesAlarm,
...routesDashboard,
...routesSettings,
...routesNotFound,
];
const router = createRouter({

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

@ -0,0 +1,26 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { RouteRecordRaw } from "vue-router";
import NotFound from "@/views/NotFound.vue";
export const routesNotFound: Array<RouteRecordRaw> = [
{
path: "/:pathMatch(.*)*",
name: "NotFound",
component: NotFound,
},
];

View File

@ -24,7 +24,6 @@ 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, WidgetType } from "@/views/dashboard/data";
interface DashboardState {
showConfig: boolean;
@ -344,11 +343,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 +426,7 @@ export const dashboardStore = defineStore({
d.layer === this.currentDashboard?.layer,
);
if (index > -1) {
const { t } = useI18n();
ElMessage.error(t("nameError"));
ElMessage.error("The dashboard name cannot be duplicate");
return;
}
res = await graphql.query("addNewTemplate").params({ setting: { configuration: JSON.stringify(c) } });

View File

@ -16,10 +16,10 @@
*/
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";
@ -35,9 +35,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 +51,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 +87,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 +115,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,6 +223,12 @@ export const topologyStore = defineStore({
setLinkClientMetrics(m: MetricVal) {
this.linkClientMetrics = m;
},
setNodeMetricValue(m: MetricVal) {
this.nodeMetricValue = m;
},
setNodeValue(m: MetricVal) {
this.nodeMetricValue = m;
},
setLegendValues(expressions: string, data: { [key: string]: any }) {
for (let idx = 0; idx < this.nodes.length; idx++) {
for (let index = 0; index < expressions.length; index++) {
@ -481,6 +595,97 @@ export const topologyStore = defineStore({
this.setLinkClientMetrics(res.data.data);
return res.data;
},
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 resp = await this.getListLayerLevels();
if (resp.errors) {
return resp;
}
const levels = resp.data.levels || [];
this.setHierarchyServiceTopology(res.data.data.hierarchyServiceTopology || {}, levels);
return res.data;
},
async getListLayerLevels() {
const res: AxiosResponse = await graphql.query("queryListLayerLevels").params({});
return res.data;
},
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;
}
const resp = await this.getListLayerLevels();
if (resp.errors) {
return resp;
}
const levels = resp.data.levels || [];
this.setHierarchyInstanceTopology(res.data.data.hierarchyInstanceTopology || {}, levels);
return res.data;
},
async queryHierarchyExpressions(expressions: string[], nodes: Node[]) {
const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor(expressions, nodes);
const param = getExpressionQuery();
const res = await this.getNodeExpressionValue(param);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
const metrics = handleExpressionValues(res.data);
return metrics;
},
async queryHierarchyNodeExpressions(expressions: string[], layer: string) {
const nodes = this.hierarchyServiceNodes.filter((n: HierarchyNode) => n.layer === layer);
if (!nodes.length) {
this.setHierarchyNodeMetricValue({}, layer);
return;
}
if (!expressions.length) {
this.setHierarchyNodeMetricValue({}, layer);
return;
}
const metrics = await this.queryHierarchyExpressions(expressions, nodes);
this.setHierarchyNodeMetricValue(metrics, layer);
},
async queryHierarchyInstanceNodeExpressions(expressions: string[], layer: string) {
const nodes = this.hierarchyInstanceNodes.filter((n: HierarchyNode) => n.layer === layer);
if (!expressions.length) {
this.setHierarchyInstanceNodeMetricValue({}, layer);
return;
}
if (!nodes.length) {
this.setHierarchyInstanceNodeMetricValue({}, layer);
return;
}
const metrics = await this.queryHierarchyExpressions(expressions, nodes);
this.setHierarchyInstanceNodeMetricValue(metrics, layer);
},
},
});

View File

@ -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,7 +68,7 @@ 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 {
@ -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;

View File

@ -23,6 +23,7 @@ export type DashboardItem = {
name: string;
isDefault: boolean;
expressions?: string[];
expressionsConfig?: MetricConfigOpt[];
};
export interface LayoutConfig {
x: number;

View File

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

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

@ -0,0 +1,29 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export function debounce(callback: Function, dur: number) {
let timer: any;
return function () {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function () {
callback();
}, dur);
};
}

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

@ -0,0 +1,33 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="not-found flex-h">
<Icon size="largest" iconName="logo-light" />
<h1 class="ml-20">404 Page Not Found</h1>
</div>
</template>
<style lang="scss" scoped>
.not-found {
height: 100%;
width: 100%;
align-items: center;
justify-content: center;
}
.icon {
width: 160px;
height: 160px;
}
</style>

View File

@ -73,23 +73,7 @@ limitations under the License. -->
<span v-else> -- </span>
</template>
</el-table-column>
<el-table-column prop="isDefault" label="Default Dashboard" width="140">
<template #default="scope">
<el-popconfirm
:title="t('rootTitle')"
@confirm="handleTopLevel(scope.row)"
v-if="[EntityType[0].value, EntityType[3].value].includes(scope.row.entity)"
>
<template #reference>
<el-button size="small" style="width: 80px">
{{ scope.row.isDefault ? "Disable" : "Enable" }}
</el-button>
</template>
</el-popconfirm>
<span v-else> -- </span>
</template>
</el-table-column>
<el-table-column label="Operations" width="300">
<el-table-column label="Operations" width="400">
<template #default="scope">
<el-button size="small" @click="handleEdit(scope.row)">
{{ t("edit") }}
@ -98,12 +82,13 @@ limitations under the License. -->
{{ t("rename") }}
</el-button>
<el-button
:disabled="![EntityType[0].value, EntityType[3].value].includes(scope.row.entity)"
v-if="[EntityType[0].value, EntityType[3].value].includes(scope.row.entity)"
size="small"
@click="handleEditMQE(scope.row)"
>
MQE
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">
@ -146,19 +131,39 @@ limitations under the License. -->
@next-click="changePage"
/>
</div>
<el-dialog v-model="MQEVisible" title="Edit MQE" width="400px">
<div>{{ t("hierarchyNodeMetrics") }}</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) => changeExpressions(param)"
@change="(param: string[]) => changeExpressions(param)"
/>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="MQEVisible = false">Cancel</el-button>
<el-button @click="MQEVisible = false"> Cancel </el-button>
<el-button type="primary" @click="saveMQE"> Confirm </el-button>
</span>
</template>
@ -178,8 +183,9 @@ limitations under the License. -->
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;
@ -192,7 +198,7 @@ limitations under the License. -->
const multipleSelection = ref<DashboardItem[]>([]);
const dashboardFile = ref<Nullable<HTMLDivElement>>(null);
const MQEVisible = ref<boolean>(false);
const currentRow = ref<any>({});
const currentRow = ref<DashboardItem | Recordable>({});
const handleSelectionChange = (val: DashboardItem[]) => {
multipleSelection.value = val;
@ -207,17 +213,29 @@ limitations under the License. -->
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 = {
@ -243,12 +261,45 @@ limitations under the License. -->
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(1);
searchDashboards(currentPage.value);
loading.value = false;
MQEVisible.value = false;
}
async function importTemplates(event: any) {
@ -264,19 +315,19 @@ limitations under the License. -->
}
loading.value = true;
for (const item of arr) {
const { layer, name, entity, isRoot, children, isDefault } = 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,
isDefault: false,
isRoot: isRoot || false,
isDefault: isDefault || false,
expressions: expressions,
expressionsConfig: expressionsConfig,
};
if (index > -1) {
p.id = item.id;
p.isRoot = isRoot;
p.isDefault = isDefault;
}
dashboardStore.setCurrentDashboard(p);
dashboardStore.setLayout(children);
@ -285,6 +336,7 @@ limitations under the License. -->
dashboards.value = dashboardStore.dashboards;
loading.value = false;
dashboardFile.value = null;
searchDashboards(currentPage.value);
}
function exportTemplates() {
if (!multipleSelection.value.length) {
@ -467,74 +519,10 @@ limitations under the License. -->
items.push(d);
}
dashboardStore.resetDashboards(items);
searchDashboards(1);
searchDashboards(currentPage.value);
loading.value = false;
}
async function handleTopLevel(row: DashboardItem) {
const items: DashboardItem[] = [];
loading.value = true;
for (const d of dashboardStore.dashboards) {
if (d.id === row.id) {
d.isDefault = !row.isDefault;
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 === row.layer && [EntityType[0].value].includes(d.entity) && !row.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(1);
loading.value = false;
}
function handleRename(row: DashboardItem) {
ElMessageBox.prompt("Please input dashboard name", "Edit", {
confirmButtonText: "OK",
@ -579,7 +567,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;
}
@ -610,8 +598,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));
@ -695,7 +684,16 @@ limitations under the License. -->
margin-left: 10px;
}
.placeholder {
display: inline-block;
width: 136px;
}
.expressions {
height: 300px;
}
.label {
font-size: 12px;
}
</style>

View File

@ -81,7 +81,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 {
@ -101,6 +101,6 @@ limitations under the License. -->
min-height: calc(100% - 150px);
width: 100%;
overflow: auto;
min-width: 1200px;
min-width: 1000px;
}
</style>

View File

@ -366,3 +366,7 @@ export enum CallTypes {
Server = "SERVER",
Client = "CLIENT",
}
export enum ConfigFieldTypes {
ISDEFAULT = "ISDEFAULT",
NAME = "NAME",
}

View File

@ -26,6 +26,9 @@ limitations under the License. -->
@change="changeService"
class="selectors"
/>
<span class="ml-5 cp hierarchy-btn" v-if="dashboardStore.entity === 'Service'" @click="viewTopology">
<Icon size="small" iconName="hierarchy_topology" />
</span>
</div>
<div class="selectors-item" v-if="key === 3 || key === 4 || key === 5 || key === 6">
<span class="label">
@ -41,6 +44,13 @@ limitations under the License. -->
class="selectorPod"
:isRemote="['EndpointRelation', 'Endpoint'].includes(dashboardStore.entity)"
/>
<span
class="ml-5 cp hierarchy-btn"
v-if="dashboardStore.entity === 'ServiceInstance'"
@click="showHierarchy = true"
>
<Icon size="small" iconName="hierarchy_topology" />
</span>
</div>
<div class="selectors-item" v-if="key === 5 || key === 6">
<span class="label"> $Process </span>
@ -126,10 +136,17 @@ limitations under the License. -->
</div>
</div>
</div>
<el-dialog v-model="showHierarchy" :destroy-on-close="true" @closed="showHierarchy = false" width="640px">
<div class="hierarchy-related">
<instance-map v-if="dashboardStore.entity === 'ServiceInstance'" />
<hierarchy-map v-else />
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { reactive, ref, computed, watch } from "vue";
import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import {
@ -146,17 +163,21 @@ limitations under the License. -->
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: WidgetType }[]>(AllTools);
const loading = ref<boolean>(false);
const showHierarchy = ref<boolean>(false);
const states = reactive<{
destService: string;
destPod: string;
@ -637,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) => {
@ -705,4 +730,19 @@ limitations under the License. -->
.relation {
margin-top: 5px;
}
.hierarchy-btn {
display: inline-block;
padding: 0 2px 2px;
border: 1px solid #666;
border-radius: 4px;
text-align: center;
color: #aaa;
}
.hierarchy-related {
height: 600px;
width: 600px;
overflow: hidden;
}
</style>

View File

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

View File

@ -13,335 +13,140 @@ 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]})`">
<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)"
<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"
: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="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.layer || n.layer === `N/A` ? icons.MESH : icons[n.layer.toUpperCase().replace('-', '')]"
/>
<text
class="node-text"
:x="n.x - (Math.min(n.name.length, 30) * 6) / 2 + 6"
:y="n.y + n.width + 8"
style="pointer-events: none"
>
<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="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>
{{ n.name.length > 30 ? `${n.name.substring(0, 30)}...` : n.name }}
</text>
</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>
<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)"
/>
</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 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;
}
items.value = [
{ id: "inspect", title: "Inspect", func: handleInspect },
{ id: "alerting", title: "Alerting", func: handleGoAlerting },
];
emits("handleNodeClick", event, d);
}
function handleLinkClick(event: MouseEvent, d: Call) {
event.stopPropagation();
if (d.sourceObj.layer !== dashboardStore.layerId || d.targetObj.layer !== dashboardStore.layerId) {
return;
}
topologyStore.setNode(null);
topologyStore.setLink(d);
if (!settings.value.linkDashboard) {
return;
}
const origin = dashboardStore.entity;
const e = dashboardStore.entity === EntityType[1].value ? EntityType[0].value : dashboardStore.entity;
const { dashboard } = getDashboard({
name: settings.value.linkDashboard,
layer: dashboardStore.layerId,
entity: `${e}Relation`,
});
if (!dashboard) {
ElMessage.error(`The dashboard named ${settings.value.linkDashboard} doesn't exist`);
return;
}
dashboardStore.setEntity(dashboard.entity);
const path = `/dashboard/related/${dashboard.layer}/${e}Relation/${d.sourceObj.id}/${d.targetObj.id}/${dashboard.name}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
dashboardStore.setEntity(origin);
}
async function handleInspect() {
const id = topologyStore.node.id;
loading.value = true;
const resp = await topologyStore.getDepthServiceTopology([id], Number(depth.value));
loading.value = false;
if (resp && resp.errors) {
ElMessage.error(resp.errors);
}
await update();
topologyStore.setNode(null);
topologyStore.setLink(null);
}
function handleGoEndpoint(name: string) {
const path = `/dashboard/${dashboardStore.layerId}/${EntityType[2].value}/${topologyStore.node.id}/${name}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
dashboardStore.setEntity(origin);
}
function handleGoInstance(name: string) {
const path = `/dashboard/${dashboardStore.layerId}/${EntityType[3].value}/${topologyStore.node.id}/${name}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
dashboardStore.setEntity(origin);
}
function handleGoDashboard(name: string) {
const path = `/dashboard/${dashboardStore.layerId}/${EntityType[0].value}/${topologyStore.node.id}/${name}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
dashboardStore.setEntity(origin);
}
function handleGoAlerting() {
const path = `/alerting`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
}
async function backToTopology() {
loading.value = true;
await freshNodes();
topologyStore.setNode(null);
topologyStore.setLink(null);
}
async function getTopology() {
const ids = selectorStore.services.map((d: Service) => d.id);
const serviceIds = dashboardStore.entity === EntityType[0].value ? [selectorStore.currentService.id] : ids;
const resp = await topologyStore.getDepthServiceTopology(serviceIds, Number(depth.value));
return resp;
}
function setConfig() {
showSetting.value = !showSetting.value;
dashboardStore.selectWidget(props.config);
}
function resize() {
const dom = document.querySelector(".topology")?.getBoundingClientRect() || {
height: 40,
width: 0,
};
height.value = dom.height - 40;
width.value = dom.width;
}
function updateSettings(config: any) {
settings.value = config;
setNodeTools(config.nodeDashboard);
}
function setNodeTools(nodeDashboard: any) {
items.value = [
{ id: "inspect", title: "Inspect", func: handleInspect },
{ id: "alerting", title: "Alerting", func: handleGoAlerting },
];
if (!(nodeDashboard && nodeDashboard.length)) {
return;
}
for (const item of nodeDashboard) {
if (item.scope === EntityType[0].value) {
items.value.push({
id: "dashboard",
title: "Service Dashboard",
func: handleGoDashboard,
...item,
});
}
if (item.scope === EntityType[2].value) {
items.value.push({
id: "endpoint",
title: "Endpoint Dashboard",
func: handleGoEndpoint,
...item,
});
}
if (item.scope === EntityType[3].value) {
items.value.push({
id: "instance",
title: "Service Instance Dashboard",
func: handleGoInstance,
...item,
});
}
}
}
function svgEvent() {
topologyStore.setNode(null);
topologyStore.setLink(null);
if (!props.config) {
return;
}
dashboardStore.selectWidget(props.config);
}
async function changeDepth(opt: Option[] | any) {
depth.value = opt[0].value;
freshNodes();
}
onBeforeUnmount(() => {
window.removeEventListener("resize", resize);
});
watch(
() => [selectorStore.currentService, selectorStore.currentDestService],
(newVal, oldVal) => {
if (oldVal[0].id === newVal[0].id && !oldVal[1]) {
return;
}
if (oldVal[0].id === newVal[0].id && oldVal[1].id === newVal[1].id) {
return;
}
freshNodes();
},
);
watch(
() => appStore.durationTime,
() => [...props.calls, ...props.nodes],
() => {
if (dashboardStore.entity === EntityType[1].value) {
freshNodes();
if (!props.nodes.length) {
return;
}
if (!props.calls.length) {
return;
}
setTimeout(() => {
init();
}, 10);
},
);
</script>
<style lang="scss">
.micro-topo-chart {
position: relative;
overflow: auto;
margin-top: 30px;
<style lang="scss" scoped>
.hierarchy-services-topo {
.node-text {
fill: var(--sw-topology-color);
font-size: 12px;
opacity: 0.9;
z-index: 1;
}
.svg-topology {
.hierarchy-services-svg {
cursor: move;
background-color: $theme-background;
}
.legend {
position: absolute;
top: 10px;
left: 25px;
color: var(--sw-topology-color);
div {
margin-bottom: 8px;
}
img {
width: 32px;
float: left;
}
span {
display: inline-block;
height: 32px;
line-height: 32px;
margin-left: 5px;
}
}
.setting {
position: absolute;
top: 80px;
right: 10px;
width: 400px;
height: 600px;
overflow: auto;
padding: 0 15px;
border-radius: 3px;
color: $disabled-color;
border: 1px solid $border-color-primary;
background-color: var(--sw-topology-setting-bg);
box-shadow: var(--sw-topology-box-shadow);
transition: all 0.5ms linear;
background-color: var(--el-bg-color);
}
.label {
@ -747,51 +212,12 @@ limitations under the License. -->
margin-right: 5px;
}
.operations-list {
position: absolute;
color: $font-color;
cursor: pointer;
border: var(--sw-topology-border);
border-radius: 3px;
background-color: $theme-background;
padding: 10px 0;
span {
display: block;
height: 30px;
line-height: 30px;
text-align: left;
padding: 0 15px;
}
span:hover {
color: $active-color;
background-color: $popper-hover-bg-color;
}
}
.tool {
position: absolute;
top: 35px;
right: 10px;
}
.switch-icon {
cursor: pointer;
transition: all 0.5ms linear;
background: var(--sw-topology-switch-icon);
color: $text-color;
display: inline-block;
padding: 2px 4px;
border-radius: 3px;
}
.topo-line {
stroke-linecap: round;
stroke-width: 1px;
stroke-dasharray: 10 10;
fill: none;
animation: topo-dash 0.3s linear infinite;
animation: var(--sw-topo-animation);
}
.topo-line-anchor,
@ -799,26 +225,11 @@ limitations under the License. -->
cursor: pointer;
}
}
@keyframes topo-dash {
from {
stroke-dashoffset: 10;
}
to {
stroke-dashoffset: 0;
}
}
.el-loading-spinner {
top: 30%;
}
#tooltip {
position: absolute;
visibility: hidden;
padding: 5px;
border: var(--sw-topology-border);
border-radius: 3px;
background-color: $theme-background;
.hierarchy-services {
align-items: center;
display: flex;
justify-content: center;
height: 100%;
}
</style>

View File

@ -120,3 +120,165 @@ 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[], 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;
}
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: any) => n && (n.level = i)));
const nodes: Node[] = levels.reduce((a, x) => a.concat(x), []);
// layout
const padding = 30;
const node_height = 100;
const node_width = 180;
const bundle_width = 10;
const metro_d = 4;
for (const n of nodes) {
n.width = 5 * metro_d;
}
let y_offset = padding;
let x_offset = 0;
for (const level of levels) {
x_offset = 0;
y_offset += 3 * bundle_width;
for (const l of level) {
const n: any = l;
for (const call of calls) {
if (call.source === n.id) {
call.sourceObj = n;
}
if (call.target === n.id) {
call.targetObj = n;
}
}
n.x = node_width + x_offset + n.width / 2;
n.y = n.level * node_height + y_offset;
x_offset += node_width + n.width;
}
}
const layout = {
width: d3.max(nodes as any, (n: { x: number }) => n.x) || 0 + node_width + 2 * padding,
height: d3.max(nodes as any, (n: { y: number }) => n.y) || 0 + node_height / 2 + 2 * padding,
};
return { nodes, layout, calls: computeCallPos(calls, radius) };
}
export function computeHierarchyLevels(nodes: Node[]) {
const levelsNum: number[] = nodes.map((d: Node) => d.l || 0);
const list = [...new Set(levelsNum)];
const sortedArr = list.sort((a, b) => b - a);
const nodesList = [];
for (const min of sortedArr) {
const arr = nodes.filter((d) => d.l === min);
nodesList.push(arr);
}
return nodesList;
}

View File

@ -15,9 +15,12 @@ limitations under the License. -->
<template>
<div class="config-panel">
<div class="item mb-10">
<span class="label">{{
t(dashboardStore.selectedGrid.metricMode === MetricModes.General ? "metrics" : "expressions")
}}</span>
<span class="label" v-if="type === 'hierarchyServicesConfig'">
{{ t("expressions") }}
</span>
<span class="label" v-else>
{{ t(dashboardStore.selectedGrid.metricMode === MetricModes.General ? "metrics" : "expressions") }}
</span>
<SelectSingle :value="currentMetric" :options="metricList" @change="changeMetric" class="selectors" />
</div>
<div class="item mb-10">
@ -40,7 +43,10 @@ limitations under the License. -->
@change="changeConfigs({ label: currentConfig.label })"
/>
</div>
<div class="item mb-10" v-if="dashboardStore.selectedGrid.metricMode === MetricModes.General">
<div
class="item mb-10"
v-if="type !== 'hierarchyServicesConfig' && dashboardStore.selectedGrid.metricMode === MetricModes.General"
>
<span class="label">{{ t("aggregation") }}</span>
<SelectSingle
:value="currentConfig.calculation"
@ -55,20 +61,27 @@ limitations under the License. -->
<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 { CalculationOpts, MetricModes, 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 = [];
if (props.type === "hierarchyServicesConfig") {
return props.expressions || [];
}
let metrics: string[] = [];
const {
linkServerExpressions,
linkServerMetrics,
@ -97,13 +110,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 +143,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 +174,7 @@ limitations under the License. -->
};
}
watch(
() => props.type,
() => [props.type, ...props.expressions],
() => {
currentMetric.value = metricList.value[0].value;
const config = getMetricConfig.value || [];

View File

@ -24,7 +24,7 @@ limitations under the License. -->
@change="changeMetricMode"
/>
</div>
<div class="link-settings">
<div class="mb-20">
<h5 class="title">{{ t("callSettings") }}</h5>
<div class="label">{{ t("linkDashboard") }}</div>
<Selector
@ -57,7 +57,7 @@ limitations under the License. -->
:tags="states.linkServerExpressions"
:vertical="true"
:text="t('addExpressions')"
@change="(param) => changeLinkServerExpressions(param)"
@change="(param: string[]) => changeLinkServerExpressions(param)"
/>
</div>
<Selector
@ -92,7 +92,7 @@ limitations under the License. -->
:tags="states.linkClientExpressions"
:vertical="true"
:text="t('addExpressions')"
@change="(param) => changeLinkClientExpressions(param)"
@change="(param: string[]) => changeLinkClientExpressions(param)"
/>
</div>
<Selector
@ -107,7 +107,7 @@ limitations under the License. -->
/>
</span>
</div>
<div class="node-settings">
<div>
<h5 class="title">{{ t("nodeSettings") }}</h5>
<div class="label">{{ t("nodeDashboard") }}</div>
<Selector
@ -168,7 +168,7 @@ limitations under the License. -->
:tags="states.nodeExpressions"
:vertical="true"
:text="t('addExpressions')"
@change="(param) => changeNodeExpressions(param)"
@change="(param: string[]) => changeNodeExpressions(param)"
/>
</div>
<Selector
@ -182,7 +182,7 @@ limitations under the License. -->
@change="updateNodeMetrics"
/>
</div>
<div class="legend-settings" v-show="isService">
<div v-show="isService">
<h5 class="title">{{ t("legendSettings") }}</h5>
<span v-if="isExpression">
<div class="label">Healthy Description</div>
@ -246,7 +246,7 @@ limitations under the License. -->
</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>
@ -266,7 +266,7 @@ limitations under the License. -->
MetricsType,
MetricModes,
CallTypes,
} from "../../../data";
} from "@/views/dashboard/data";
import type { Option } from "@/types/app";
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
@ -604,10 +604,6 @@ limitations under the License. -->
}
</script>
<style lang="scss" scoped>
.link-settings {
margin-bottom: 20px;
}
.inputs {
margin-top: 8px;
width: 355px;
@ -640,7 +636,6 @@ limitations under the License. -->
}
.legend-btn {
margin: 20px 0;
cursor: pointer;
}

View File

@ -0,0 +1,175 @@
<!-- 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 { aggregation } from "@/hooks/useMetricsProcessor";
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: 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>`;
});
const tipHtml = [
`<div class="mb-5"><span class="grey">name: </span>${data.name}</div><div class="mb-5"><span class="grey">layer: </span>${data.layer}</div>`,
...html,
].join(" ");
popover.value
.style("top", event.offsetY + 10 + "px")
.style("left", event.offsetX + 10 + "px")
.style("visibility", "visible")
.html(tipHtml);
}
function hideTip() {
popover.value.style("visibility", "hidden");
}
function handleNodeClick(event: MouseEvent, d: HierarchyNode & { serviceId: string }) {
const origin = dashboardStore.entity;
event.stopPropagation();
hideTip();
const { dashboard } = getDashboard(
{
layer: d.layer || "",
entity: EntityType[3].value,
},
ConfigFieldTypes.ISDEFAULT,
);
if (!dashboard) {
return;
}
const name = dashboard.name;
const path = `/dashboard/${dashboard.layer}/${EntityType[3].value}/${d.serviceId}/${d.key}/${name}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
dashboardStore.setEntity(origin);
}
</script>
<style lang="scss" scoped>
@import url("../style.scss");
</style>

View File

@ -69,10 +69,10 @@ limitations under the License. -->
import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app";
import { EntityType, DepthList, MetricModes, CallTypes } from "../../../data";
import { EntityType, DepthList, MetricModes, 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";

View File

@ -23,7 +23,7 @@ limitations under the License. -->
import type { Node, Call } from "@/types/topology";
import type { MetricConfigOpt } from "@/types/dashboard";
import { aggregation } from "@/hooks/useMetricsProcessor";
import { MetricModes } from "../../../data";
import { MetricModes } from "@/views/dashboard/data";
import { useAppStoreWithOut } from "@/store/modules/app";
import { Themes } from "@/constants/data";

View File

@ -0,0 +1,184 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div
class="hierarchy-services-topo"
v-loading="loading"
element-loading-background="rgba(0, 0, 0, 0)"
:style="`height: ${height}px`"
>
<Graph
:config="config"
:nodes="topologyStore.hierarchyServiceNodes"
:calls="topologyStore.hierarchyServiceCalls"
:entity="EntityType[0].value"
@getNodeMetrics="getNodeMetrics"
@showNodeTip="showNodeTip"
@handleNodeClick="handleNodeClick"
@hideTip="hideTip"
/>
<div id="popover"></div>
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { ref, onMounted, nextTick } from "vue";
import * as d3 from "d3";
import type { HierarchyNode } from "@/types/topology";
import { useTopologyStore } from "@/store/modules/topology";
import { useDashboardStore } from "@/store/modules/dashboard";
import { EntityType, ConfigFieldTypes } from "@/views/dashboard/data";
import router from "@/router";
import { ElMessage } from "element-plus";
import type { MetricConfigOpt } from "@/types/dashboard";
import { aggregation } from "@/hooks/useMetricsProcessor";
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: 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>`;
});
const tipHtml = [
`<div class="mb-5"><span class="grey">name: </span>${data.name}</div><div class="mb-5"><span class="grey">layer: </span>${data.layer}</div>`,
...html,
].join(" ");
popover.value
.style("top", event.offsetY + 10 + "px")
.style("left", event.offsetX + 10 + "px")
.style("visibility", "visible")
.html(tipHtml);
}
function hideTip() {
popover.value.style("visibility", "hidden");
}
function handleNodeClick(event: MouseEvent, d: HierarchyNode) {
const origin = dashboardStore.entity;
event.stopPropagation();
hideTip();
const { dashboard } = getDashboard(
{
layer: d.layer || "",
entity: EntityType[0].value,
},
ConfigFieldTypes.ISDEFAULT,
);
if (!dashboard) {
return;
}
const name = dashboard.name;
const path = `/dashboard/${dashboard.layer}/${EntityType[0].value}/${d.key}/${name}`;
const routeUrl = router.resolve({ path });
window.open(routeUrl.href, "_blank");
dashboardStore.setEntity(origin);
}
</script>
<style lang="scss" scoped>
@import url("../style.scss");
</style>

View File

@ -0,0 +1,793 @@
<!-- 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"
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]})`">
<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, MetricModes, 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 { aggregation } from "@/hooks/useMetricsProcessor";
import icons from "@/assets/img/icons";
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
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() {
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();
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;
}
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] &&
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);
}
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);
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;
}
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();
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>

View File

@ -0,0 +1,34 @@
/**
* 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.
*/
.hierarchy-services-topo {
position: absolute;
width: 100%;
}
.el-loading-spinner {
top: 30%;
}
#popover {
position: absolute;
visibility: hidden;
padding: 5px;
border: var(--sw-topology-border);
border-radius: 3px;
background-color: var(--theme-background);
z-index: 9999;
}

View File

@ -172,9 +172,8 @@ limitations under the License. -->
}
.trace-chart {
height: calc(100% - 100px);
height: calc(100% - 95px);
overflow: auto;
padding-bottom: 20px;
}
.trace-detail-wrapper {

View File

@ -150,7 +150,13 @@ limitations under the License. -->
ElMessage.error(resp.errors);
return;
}
state.service = getCurrentNode(traceStore.services) || traceStore.services[0];
if (props.data.filters && props.data.filters.id === "0") {
state.service = { value: "", label: "" };
return;
} else {
state.service = getCurrentNode(traceStore.services) || traceStore.services[0];
}
emits("get", state.service.id);
getEndpoints(state.service.id);
@ -198,7 +204,7 @@ limitations under the License. -->
if (props.data.filters && props.data.filters.id) {
param = {
...param,
serviceId: props.data.filters.id || undefined,
serviceId: props.data.filters.id && props.data.filters.id !== "0" ? props.data.filters.id : undefined,
endpointId: state.endpoint.id || undefined,
serviceInstanceId: state.instance.id || undefined,
};

View File

@ -159,6 +159,7 @@ limitations under the License. -->
.selectors {
margin: 2px 2px 0 0;
width: 120px;
}
.trace-t-wrapper {
@ -182,11 +183,11 @@ limitations under the License. -->
}
.trace-t {
width: 420px;
width: 300px;
}
.list {
width: 300px;
width: 280px;
}
.trace-tr {
@ -226,7 +227,8 @@ limitations under the License. -->
.no-data {
padding-top: 50px;
width: 100%;
width: 280px;
text-align: center;
height: 100px;
}
</style>

View File

@ -29,6 +29,7 @@ limitations under the License. -->
import type { Span, Ref } from "@/types/trace";
import SpanDetail from "./SpanDetail.vue";
import { useAppStoreWithOut } from "@/store/modules/app";
import { debounce } from "@/utils/debounce";
/* global defineProps, Nullable, defineExpose,Recordable*/
const props = defineProps({
@ -45,6 +46,8 @@ limitations under the License. -->
const refSpans = ref<Array<Ref>>([]);
const tree = ref<Nullable<any>>(null);
const traceGraph = ref<Nullable<HTMLDivElement>>(null);
const debounceFunc = debounce(draw, 500);
defineExpose({
tree,
});
@ -55,6 +58,15 @@ limitations under the License. -->
loading.value = false;
return;
}
draw();
loading.value = false;
window.addEventListener("resize", debounceFunc);
});
function draw() {
if (!traceGraph.value) {
return;
}
if (props.type === "List") {
tree.value = new ListGraph(traceGraph.value, handleSelectSpan);
tree.value.init({ label: "TRACE_ROOT", children: segmentId.value }, props.data, fixSpansSize.value);
@ -63,11 +75,6 @@ limitations under the License. -->
tree.value = new TreeGraph(traceGraph.value, handleSelectSpan);
tree.value.init({ label: `${props.traceId}`, children: segmentId.value }, props.data);
}
loading.value = false;
window.addEventListener("resize", resize);
});
function resize() {
tree.value.resize();
}
function handleSelectSpan(i: Recordable) {
currentSpan.value = i.data;
@ -132,28 +139,61 @@ limitations under the License. -->
}
segmentHeaders.forEach((span: Span) => {
if (span.refs.length) {
let exit = 0;
span.refs.forEach((ref) => {
const index = props.data.findIndex(
const i = props.data.findIndex(
(i: Recordable) => ref.parentSegmentId === i.segmentId && ref.parentSpanId === i.spanId,
);
if (index === -1) {
// create a known broken node.
const i = ref.parentSpanId;
const fixSpanKeyContent = {
if (i > -1) {
exit = 1;
}
});
if (!exit) {
const ref = span.refs[0];
// create a known broken node.
const i = ref.parentSpanId;
const fixSpanKeyContent = {
traceId: ref.traceId,
segmentId: ref.parentSegmentId,
spanId: i,
parentSpanId: i > -1 ? 0 : -1,
};
if (!_.find(fixSpans, fixSpanKeyContent)) {
fixSpans.push({
...fixSpanKeyContent,
refs: [],
endpointName: `VNode: ${ref.parentSegmentId}`,
serviceCode: "VirtualNode",
type: `[Broken] ${ref.type}`,
peer: "",
component: `VirtualNode: #${i}`,
isError: true,
isBroken: true,
layer: "Broken",
tags: [],
logs: [],
startTime: 0,
endTime: 0,
});
}
// if root broken node is not exist, create a root broken node.
if (fixSpanKeyContent.parentSpanId > -1) {
const fixRootSpanKeyContent = {
traceId: ref.traceId,
segmentId: ref.parentSegmentId,
spanId: i,
parentSpanId: i > -1 ? 0 : -1,
spanId: 0,
parentSpanId: -1,
};
if (!_.find(fixSpans, fixSpanKeyContent)) {
if (!_.find(fixSpans, fixRootSpanKeyContent)) {
fixSpans.push({
...fixSpanKeyContent,
...fixRootSpanKeyContent,
refs: [],
endpointName: `VNode: ${ref.parentSegmentId}`,
serviceCode: "VirtualNode",
type: `[Broken] ${ref.type}`,
peer: "",
component: `VirtualNode: #${i}`,
component: `VirtualNode: #0`,
isError: true,
isBroken: true,
layer: "Broken",
@ -163,44 +203,16 @@ limitations under the License. -->
endTime: 0,
});
}
// if root broken node is not exist, create a root broken node.
if (fixSpanKeyContent.parentSpanId > -1) {
const fixRootSpanKeyContent = {
traceId: ref.traceId,
segmentId: ref.parentSegmentId,
spanId: 0,
parentSpanId: -1,
};
if (!_.find(fixSpans, fixRootSpanKeyContent)) {
fixSpans.push({
...fixRootSpanKeyContent,
refs: [],
endpointName: `VNode: ${ref.parentSegmentId}`,
serviceCode: "VirtualNode",
type: `[Broken] ${ref.type}`,
peer: "",
component: `VirtualNode: #0`,
isError: true,
isBroken: true,
layer: "Broken",
tags: [],
logs: [],
startTime: 0,
endTime: 0,
});
}
}
}
});
}
}
});
[...fixSpans, ...props.data].forEach((i) => {
i.label = i.endpointName || "no operation name";
i.children = [];
if (segmentGroup[i.segmentId] === undefined) {
if (!segmentGroup[i.segmentId]) {
segmentIdGroup.push(i.segmentId);
segmentGroup[i.segmentId] = [];
segmentGroup[i.segmentId].push(i);
segmentGroup[i.segmentId] = [i];
} else {
segmentGroup[i.segmentId].push(i);
}
@ -273,7 +285,7 @@ limitations under the License. -->
}
onBeforeUnmount(() => {
d3.selectAll(".d3-tip").remove();
window.removeEventListener("resize", resize);
window.removeEventListener("resize", debounceFunc);
});
watch(
() => props.data,

View File

@ -42,6 +42,12 @@ limitations under the License. -->
<span class="g-sm-4 grey">{{ t("isError") }}:</span>
<span class="g-sm-8 wba">{{ currentSpan.isError }}</span>
</div>
<h5 class="mb-10" v-if="diffRefs.length"> {{ t("traceID") }}. </h5>
<div class="mb-10 clear item" v-for="item in diffRefs" :key="item.traceId">
<span class="g-sm-12 wba cp link" @click="viewRelateTrace(item)">
{{ item.traceId }}
</span>
</div>
<h5 class="mb-10" v-if="currentSpan.tags && currentSpan.tags.length"> {{ t("tags") }}. </h5>
<div class="mb-10 clear item" v-for="i in currentSpan.tags" :key="i.key">
<span class="g-sm-4 grey">{{ i.key }}:</span>
@ -127,7 +133,7 @@ limitations under the License. -->
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted } from "vue";
import { ref, computed, onMounted, inject } from "vue";
import { useI18n } from "vue-i18n";
import type { PropType } from "vue";
import dayjs from "dayjs";
@ -138,14 +144,20 @@ limitations under the License. -->
import { useTraceStore } from "@/store/modules/trace";
import LogTable from "@/views/dashboard/related/log/LogTable/Index.vue";
import type { SpanAttachedEvent } from "@/types/trace";
import getDashboard from "@/hooks/useDashboardsSession";
import { useDashboardStore } from "@/store/modules/dashboard";
import { WidgetType } from "@/views/dashboard/data";
import type { LayoutConfig } from "@/types/dashboard";
/*global defineProps, Nullable, Recordable */
const props = defineProps({
currentSpan: { type: Object as PropType<Recordable>, default: () => ({}) },
traceId: { type: String, default: "" },
});
const options: Recordable<LayoutConfig> = inject("options") || {};
const { t } = useI18n();
const traceStore = useTraceStore();
const dashboardStore = useDashboardStore();
const pageNum = ref<number>(1);
const showRelatedLogs = ref<boolean>(false);
const showEventDetail = ref<boolean>(false);
@ -154,6 +166,9 @@ limitations under the License. -->
const total = computed(() =>
traceStore.traceList.length === pageSize ? pageSize * pageNum.value + 1 : pageSize * pageNum.value,
);
const diffRefs = computed(() =>
props.currentSpan.refs.filter((d: Recordable) => d.traceId !== props.currentSpan.traceId),
);
const tree = ref<any>(null);
const eventGraph = ref<Nullable<HTMLDivElement>>(null);
const visDate = (date: number, pattern = "YYYY-MM-DD HH:mm:ss:SSS") => dayjs(date).format(pattern);
@ -217,6 +232,19 @@ limitations under the License. -->
tree.value.draw();
}
function viewRelateTrace(item: Recordable) {
const { associationWidget } = getDashboard(dashboardStore.currentDashboard);
associationWidget(
(options.id as any) || "",
{
sourceId: options.id || "",
traceId: item.traceId || "",
id: "0",
},
WidgetType.Trace,
);
}
function selectEvent(i: any) {
currentEvent.value = i.data;
showEventDetail.value = true;
@ -259,7 +287,12 @@ limitations under the License. -->
cursor: pointer;
}
.link,
.link-hover:hover {
color: #448dfe;
color: var(--el-color-primary);
}
.link {
text-decoration: underline;
}
</style>

View File

@ -47,6 +47,7 @@ export default class ListGraph {
this.el = el;
this.width = el.getBoundingClientRect().width - 10;
this.height = el.getBoundingClientRect().height - 10;
d3.select(".trace-list-dowanload").remove();
this.svg = d3
.select(this.el)
.append("svg")
@ -384,15 +385,4 @@ export default class ListGraph {
visDate(date: number, pattern = "YYYY-MM-DD HH:mm:ss:SSS") {
return dayjs(date).format(pattern);
}
resize() {
if (!this.el) {
return;
}
this.width = this.el.getBoundingClientRect().width - 20;
this.height = this.el.getBoundingClientRect().height - 10;
this.svg.attr("width", this.width).attr("height", this.height);
this.svg.select("g").attr("transform", () => `translate(160, 0)`);
const transform = d3.zoomTransform(this.svg).translate(0, 0);
d3.zoom().transform(this.svg, transform);
}
}

View File

@ -55,6 +55,7 @@ export default class TraceMap {
this.topChild = [];
this.width = el.clientWidth - 20;
this.height = el.clientHeight - 30;
d3.select(".d3-trace-tree").remove();
this.body = d3
.select(this.el)
.append("svg")
@ -78,17 +79,6 @@ export default class TraceMap {
this.svg = this.body.append("g").attr("transform", () => `translate(120, 0)`);
this.svg.call(this.tip);
}
resize() {
if (!this.el) {
return;
}
this.width = this.el.clientWidth;
this.height = this.el.clientHeight + 100;
this.body.attr("width", this.width).attr("height", this.height);
this.body.select("g").attr("transform", () => `translate(160, 0)`);
const transform = d3.zoomTransform(this.body).translate(0, 0);
d3.zoom().transform(this.body, transform);
}
init(data: Recordable, row: Recordable) {
this.treemap = d3.tree().size([row.length * 35, this.width]);
this.row = row;