74 Commits

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

* fix: service
2023-12-04 16:45:07 +08:00
Fine0830
b2ab93926d feat: support search on Marketplace (#350) 2023-11-30 07:30:01 -08:00
Fine0830
a1c7a00a83 refactor: change router components (#349) 2023-11-27 21:08:19 -08:00
Fine0830
20e3ef12fe style: add underline for text widget (#348) 2023-11-28 11:14:06 +08:00
block666
0ea95b1ca6 Remove the description of OpenFunction in the UI. (#347) 2023-11-27 07:50:05 -08:00
Fine0830
a18ac3372e fix: change colors to match dark theme for Network Profiling (#346) 2023-11-26 04:02:41 -08:00
Fine0830
c1c086d999 fix: change metrics config, fix tab routes (#345) 2023-11-25 21:15:43 -08:00
Fine0830
ac57b229fc feat: enhance topology layout and fix calls metrics (#344) 2023-11-23 16:37:26 +08:00
Fine0830
d8a3c27345 fix: Log associate with Trace (#343) 2023-11-22 13:03:59 +08:00
Rick
03e1508afc feat: support to save and load theme setting from localStorage (#342) 2023-11-21 16:48:57 +08:00
Fine0830
8618a9440e fix: polish list style (#341) 2023-11-21 11:24:27 +08:00
Fine0830
02c5724859 fix: change log widget and loading mask style (#340) 2023-11-17 13:16:48 +08:00
Fine0830
c6d1c49569 refactor: enhance the Log widget (#339) 2023-11-17 10:15:39 +08:00
weixiang1862
8f3ce7d371 feat: add Nginx menu i18n (#338) 2023-11-16 21:23:10 +08:00
Fine0830
2085dc84b9 fix: set the height for trace widget (#337) 2023-11-16 11:44:34 +08:00
Fine0830
a4271bb479 feat: enhance the Dark Theme (#336) 2023-11-15 19:53:23 +08:00
Fine0830
832dc1676b feat: implement the Dark Theme (#334) 2023-11-14 20:37:15 +08:00
dependabot[bot]
780104c5d2 build(deps): bump axios from 0.24.0 to 1.6.0 (#335) 2023-11-11 17:41:00 +08:00
Fine0830
d86543aeed refactor: update Logs view (#333)
New columns of the Log View
1. Timestamp + Raw log(content) as column one, 70% of the initial table width. No separate timestamp column. The first column should be timestamp - content
2. Level tag(key=level)'s value. This could be missed from tags, if so, keep the column empty.
3. Trace link. If trace ID exists, generate a link to trace. Don't need to put the relative trace ID in the text.
4. Still keep details pop-up style.
2023-11-01 22:45:45 +08:00
Fine0830
d2eae87957 feat: add a title and a description for trace segments (#332) 2023-10-23 17:42:02 +08:00
Fine0830
d9064e8b45 fix: avoid querying data with empty parameters (#331)
* fix: Avoid querying data with empty parameters
2023-10-23 10:37:43 +08:00
peachisai
6fb4f074c1 add a netty icon (#330) 2023-10-21 22:46:17 +08:00
Xiangying Meng
f88c8a9771 feat(pulsar): add new menu for pulsar monitoring (#329) 2023-10-21 16:45:57 +08:00
dependabot[bot]
1be2792ff4 build(deps): bump @cypress/request and cypress (#327) 2023-10-19 17:41:18 +08:00
dependabot[bot]
037c2bbb11 build(deps-dev): bump @babel/traverse from 7.22.5 to 7.23.2 (#326) 2023-10-19 17:34:55 +08:00
Fine0830
70063c376f Fix tooltip style to support multiple metrics scrolling view in a metrics graph (#325) 2023-10-08 10:58:47 +08:00
zhourunjie1988
e42734ba80 fix: icons display in trace tree diagram (#324) 2023-09-26 17:20:05 +08:00
Fine0830
102436ca51 feat: add shortName for service (#323) 2023-09-20 21:16:41 +08:00
何延龙
d00fe6df9e fix: the display height of the link tree structure (#322) 2023-09-20 15:50:45 +08:00
吴晟 Wu Sheng
6872ad5bf2 Update README.md (#321) 2023-09-20 12:19:20 +08:00
Fine0830
3dc929dd53 refactor: update pagination (#320) 2023-09-20 11:57:52 +08:00
Fine0830
310fff4b28 fix: independent widget duration (#319) 2023-09-19 17:47:59 +08:00
Zhu Wang
63e97edae7 feat(kafka): add new menu for kafka monitoring (#318) 2023-09-07 14:51:51 +08:00
178 changed files with 5006 additions and 3998 deletions

View File

@@ -37,7 +37,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
node-version: [14.x, 16.x, 18.x] node-version: [16.x, 18.x, 20.x]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}

View File

@@ -34,14 +34,15 @@ npm install
npm run dev npm run dev
``` ```
The default UI address is `http://localhost:8080`. The default UI address is `http://localhost:3000`.
# Contact Us # Contact Us
- Submit an [issue](https://github.com/apache/skywalking/issues) if you face some issues. Submit a [discussion](https://github.com/apache/skywalking/discussions) if you want to propose new feature or have any question. - Mail list: **dev@skywalking.apache.org**. Mail to `dev-subscribe@skywalking.apache.org`, follow the reply to subscribe to the mail list.
- Mailing list: **dev@skywalking.apache.org**. Mail to `dev-subscribe@skywalking.apache.org`, follow the reply to subscribe the mailing list. - Send `Request to join SkyWalking slack` mail to the mail list(`dev@skywalking.apache.org`), we will invite you in.
- Join Slack. Send `Request to join SkyWalking slack` mail to the mail list(`dev@skywalking.apache.org`), we will invite you in. - For Chinese speaker, send `[CN] Request to join SkyWalking slack` mail to the mail list(`dev@skywalking.apache.org`), we will invite you in.
- QQ Group: 392443393, 901167865 - Twitter, [ASFSkyWalking](https://twitter.com/AsfSkyWalking)
- [bilibili B 站 视频](https://space.bilibili.com/390683219)
# License # License

1165
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -18,12 +18,12 @@
"check-components-types": "if (! git diff --quiet -U0 ./src/types); then echo 'type files are not updated correctly'; git diff -U0 ./src/types; exit 1; fi" "check-components-types": "if (! git diff --quiet -U0 ./src/types); then echo 'type files are not updated correctly'; git diff -U0 ./src/types; exit 1; fi"
}, },
"dependencies": { "dependencies": {
"axios": "^0.24.0", "axios": "^1.6.0",
"d3": "^7.3.0", "d3": "^7.3.0",
"d3-flame-graph": "^4.1.3", "d3-flame-graph": "^4.1.3",
"d3-tip": "^0.9.1", "d3-tip": "^0.9.1",
"echarts": "^5.2.2", "echarts": "^5.2.2",
"element-plus": "^2.1.0", "element-plus": "^2.2.5",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"monaco-editor": "^0.34.1", "monaco-editor": "^0.34.1",
"pinia": "^2.0.28", "pinia": "^2.0.28",
@@ -52,7 +52,7 @@
"@vue/test-utils": "^2.2.6", "@vue/test-utils": "^2.2.6",
"@vue/tsconfig": "^0.1.3", "@vue/tsconfig": "^0.1.3",
"@vueuse/core": "^9.6.0", "@vueuse/core": "^9.6.0",
"cypress": "^12.0.2", "cypress": "^13.3.2",
"eslint": "^8.22.0", "eslint": "^8.22.0",
"eslint-plugin-cypress": "^2.12.1", "eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-vue": "^9.3.0", "eslint-plugin-vue": "^9.3.0",
@@ -74,7 +74,7 @@
"typescript": "~4.7.4", "typescript": "~4.7.4",
"unplugin-auto-import": "^0.7.0", "unplugin-auto-import": "^0.7.0",
"unplugin-vue-components": "^0.19.2", "unplugin-vue-components": "^0.19.2",
"vite": "^4.0.5", "vite": "^4.5.3",
"vite-plugin-monaco-editor": "^1.1.0", "vite-plugin-monaco-editor": "^1.1.0",
"vite-plugin-svg-icons": "^2.0.1", "vite-plugin-svg-icons": "^2.0.1",
"vitest": "^0.25.6", "vitest": "^0.25.6",

View File

@@ -27,10 +27,11 @@ limitations under the License. -->
} }
}, 500); }, 500);
</script> </script>
<style> <style lang="scss">
#app { #app {
color: #2c3e50; color: $font-color;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
background-color: $layout-background;
} }
</style> </style>

View File

@@ -12,4 +12,4 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<svg t="1655799536378" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9286" width="48" height="48"><path d="M563.2 614.4v51.2c0 30.72-20.48 51.2-51.2 51.2s-51.2-20.48-51.2-51.2v-51.2H409.6c-30.72 0-51.2-20.48-51.2-51.2s20.48-51.2 51.2-51.2h51.2V460.8c0-30.72 20.48-51.2 51.2-51.2s51.2 20.48 51.2 51.2v51.2h51.2c30.72 0 51.2 20.48 51.2 51.2s-20.48 51.2-51.2 51.2h-51.2z m51.2-563.2c158.72 15.36 281.6 143.36 281.6 307.2v512c0 56.32-46.08 102.4-102.4 102.4h-563.2c-56.32 0-102.4-46.08-102.4-102.4V153.6c0-56.32 46.08-102.4 102.4-102.4H614.4z m163.84 230.4c-25.6-61.44-76.8-107.52-138.24-122.88v71.68c0 30.72 20.48 51.2 51.2 51.2h87.04zM537.6 153.6h-256c-30.72 0-51.2 20.48-51.2 51.2v614.4c0 30.72 20.48 51.2 51.2 51.2h460.8c30.72 0 51.2-20.48 51.2-51.2V384h-153.6c-56.32 0-102.4-46.08-102.4-102.4V153.6z" fill="#707070" p-id="9287"></path></svg> <svg t="1655799536378" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9286" width="48" height="48"><path d="M563.2 614.4v51.2c0 30.72-20.48 51.2-51.2 51.2s-51.2-20.48-51.2-51.2v-51.2H409.6c-30.72 0-51.2-20.48-51.2-51.2s20.48-51.2 51.2-51.2h51.2V460.8c0-30.72 20.48-51.2 51.2-51.2s51.2 20.48 51.2 51.2v51.2h51.2c30.72 0 51.2 20.48 51.2 51.2s-20.48 51.2-51.2 51.2h-51.2z m51.2-563.2c158.72 15.36 281.6 143.36 281.6 307.2v512c0 56.32-46.08 102.4-102.4 102.4h-563.2c-56.32 0-102.4-46.08-102.4-102.4V153.6c0-56.32 46.08-102.4 102.4-102.4H614.4z m163.84 230.4c-25.6-61.44-76.8-107.52-138.24-122.88v71.68c0 30.72 20.48 51.2 51.2 51.2h87.04zM537.6 153.6h-256c-30.72 0-51.2 20.48-51.2 51.2v614.4c0 30.72 20.48 51.2 51.2 51.2h460.8c30.72 0 51.2-20.48 51.2-51.2V384h-153.6c-56.32 0-102.4-46.08-102.4-102.4V153.6z" p-id="9287"></path></svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -12,4 +12,4 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M856.32 428.064a32 32 0 0 0-32 32v163.328H372.48c-0.896 0-1.664 0.448-2.56 0.512v-177.696h244.48a32 32 0 1 0 0-64H130.56c-0.896 0-1.664 0.448-2.56 0.512V231.68h488.16a32 32 0 1 0 0-64H96a32 32 0 0 0-32 32v701.824a32 32 0 0 0 32 32h760.32a32 32 0 0 0 32-32V460.064a32 32 0 0 0-32-32zM128 445.728c0.896 0.064 1.664 0.512 2.56 0.512h175.36v423.264H128V445.728z m241.92 423.776v-182.624c0.896 0.064 1.664 0.512 2.56 0.512h451.84v182.08h-454.4zM960 174.656h-61.376V113.28a32 32 0 1 0-64 0v61.344H752.64a32 32 0 1 0 0 64h81.984v81.984a32 32 0 1 0 64 0V238.656H960a32 32 0 1 0 0-64z" fill="#2c2c2c"></path></svg> <svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M856.32 428.064a32 32 0 0 0-32 32v163.328H372.48c-0.896 0-1.664 0.448-2.56 0.512v-177.696h244.48a32 32 0 1 0 0-64H130.56c-0.896 0-1.664 0.448-2.56 0.512V231.68h488.16a32 32 0 1 0 0-64H96a32 32 0 0 0-32 32v701.824a32 32 0 0 0 32 32h760.32a32 32 0 0 0 32-32V460.064a32 32 0 0 0-32-32zM128 445.728c0.896 0.064 1.664 0.512 2.56 0.512h175.36v423.264H128V445.728z m241.92 423.776v-182.624c0.896 0.064 1.664 0.512 2.56 0.512h451.84v182.08h-454.4zM960 174.656h-61.376V113.28a32 32 0 1 0-64 0v61.344H752.64a32 32 0 1 0 0 64h81.984v81.984a32 32 0 1 0 64 0V238.656H960a32 32 0 1 0 0-64z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

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

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -12,4 +12,4 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<svg t="1655695739627" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2218" width="48" height="48"><path d="M173.292308 177.230769C86.646154 265.846154 39.384615 382.030769 39.384615 504.123077 39.384615 531.692308 61.046154 551.384615 86.646154 551.384615s47.261538-21.661538 47.261538-47.261538c-1.969231-96.492308 37.415385-189.046154 106.338462-257.969231s163.446154-106.338462 257.969231-106.338461c27.569231 0 47.261538-21.661538 47.261538-47.261539 0-27.569231-21.661538-47.261538-47.261538-47.261538C378.092308 43.323077 259.938462 90.584615 173.292308 177.230769z m57.107692 326.892308c0 27.569231 19.692308 47.261538 47.261538 47.261538s47.261538-21.661538 47.261539-47.261538c0-45.292308 17.723077-90.584615 51.2-122.092308 33.476923-33.476923 76.8-49.230769 122.092308-51.2 27.569231 0 47.261538-21.661538 47.261538-47.261538 0-27.569231-19.692308-47.261538-47.261538-47.261539-70.892308 0-139.815385 27.569231-191.015385 76.8-7.876923 9.846154-80.738462 82.707692-76.8 191.015385z m665.6-204.8c-17.723077-23.630769-41.353846-51.2-45.292308-55.138462-5.907692-3.938462-13.784615-7.876923-21.661538-7.876923-7.876923 0-15.753846 1.969231-19.692308 7.876923L610.461538 441.107692c-47.261538-39.384615-118.153846-37.415385-163.446153 7.876923-45.292308 45.292308-47.261538 116.184615-7.876923 163.446154l-191.015385 191.015385c-5.907692 5.907692-9.846154 13.784615-9.846154 21.661538 0 9.846154 3.938462 19.692308 11.815385 25.6l53.16923 39.384616c72.861538 57.107692 163.446154 88.615385 259.938462 88.615384 232.369231 0 421.415385-189.046154 421.415385-421.415384 0-94.523077-33.476923-185.107692-88.615385-257.969231z" p-id="2219" fill="#707070"></path></svg> <svg t="1655695739627" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2218" width="48" height="48"><path d="M173.292308 177.230769C86.646154 265.846154 39.384615 382.030769 39.384615 504.123077 39.384615 531.692308 61.046154 551.384615 86.646154 551.384615s47.261538-21.661538 47.261538-47.261538c-1.969231-96.492308 37.415385-189.046154 106.338462-257.969231s163.446154-106.338462 257.969231-106.338461c27.569231 0 47.261538-21.661538 47.261538-47.261539 0-27.569231-21.661538-47.261538-47.261538-47.261538C378.092308 43.323077 259.938462 90.584615 173.292308 177.230769z m57.107692 326.892308c0 27.569231 19.692308 47.261538 47.261538 47.261538s47.261538-21.661538 47.261539-47.261538c0-45.292308 17.723077-90.584615 51.2-122.092308 33.476923-33.476923 76.8-49.230769 122.092308-51.2 27.569231 0 47.261538-21.661538 47.261538-47.261538 0-27.569231-19.692308-47.261538-47.261538-47.261539-70.892308 0-139.815385 27.569231-191.015385 76.8-7.876923 9.846154-80.738462 82.707692-76.8 191.015385z m665.6-204.8c-17.723077-23.630769-41.353846-51.2-45.292308-55.138462-5.907692-3.938462-13.784615-7.876923-21.661538-7.876923-7.876923 0-15.753846 1.969231-19.692308 7.876923L610.461538 441.107692c-47.261538-39.384615-118.153846-37.415385-163.446153 7.876923-45.292308 45.292308-47.261538 116.184615-7.876923 163.446154l-191.015385 191.015385c-5.907692 5.907692-9.846154 13.784615-9.846154 21.661538 0 9.846154 3.938462 19.692308 11.815385 25.6l53.16923 39.384616c72.861538 57.107692 163.446154 88.615385 259.938462 88.615384 232.369231 0 421.415385-189.046154 421.415385-421.415384 0-94.523077-33.476923-185.107692-88.615385-257.969231z" p-id="2219"></path></svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

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

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -0,0 +1,17 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg t="1712402256302" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1578">
<path d="M640 885.589333a39.424 39.424 0 0 1-32.938667 46.634667A483.413333 483.413333 0 0 1 512 938.666667a295.68 295.68 0 0 1-92.117333-16.768 32.085333 32.085333 0 1 1 13.568-62.72 415.658667 415.658667 0 0 0 78.549333 15.36 295.722667 295.722667 0 0 0 75.434667-4.266667c20.181333-9.514667 47.317333-9.216 52.565333 15.36z m-397.824-307.968a160.597333 160.597333 0 0 1 156.842667 164.096 198.314667 198.314667 0 0 1-6.954667 48.426667 154.325333 154.325333 0 0 1-149.930667 115.712 164.266667 164.266667 0 0 1 0-328.192z m539.605333 0a164.266667 164.266667 0 1 1-156.842666 164.096 160.597333 160.597333 0 0 1 156.885333-164.096z m-30.122666-262.058666A344.917333 344.917333 0 0 1 853.333333 497.024c1.109333 16.768-1.749333 38.826667-30.08 41.258667a32.554667 32.554667 0 0 1-33.493333-26.325334 334.848 334.848 0 0 0-80.341333-146.304 34.517333 34.517333 0 0 1-4.992-54.613333 33.408 33.408 0 0 1 47.232 4.693333z m-417.706667-4.48c13.269333 16.64 4.821333 28.501333-8.789333 48.512A422.4 422.4 0 0 0 256 517.802667a33.152 33.152 0 0 1-36.821333 26.709333 31.573333 31.573333 0 0 1-27.52-32.512 89.6 89.6 0 0 1 3.797333-29.226667 402.773333 402.773333 0 0 1 93.312-177.536 30.592 30.592 0 0 1 45.184 5.845334zM512 85.333333a164.266667 164.266667 0 1 1-156.842667 164.096A160.597333 160.597333 0 0 1 512 85.333333z" p-id="1579"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 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.

Before

Width:  |  Height:  |  Size: 269 B

After

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 B

View File

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

View File

@@ -53,6 +53,7 @@ limitations under the License. -->
import { addResizeListener, removeResizeListener } from "@/utils/event"; import { addResizeListener, removeResizeListener } from "@/utils/event";
import Trace from "@/views/dashboard/related/trace/Index.vue"; import Trace from "@/views/dashboard/related/trace/Index.vue";
import associateProcessor from "@/hooks/useAssociateProcessor"; import associateProcessor from "@/hooks/useAssociateProcessor";
import { WidgetType } from "@/views/dashboard/data";
/*global Nullable, defineProps, defineEmits, Indexable*/ /*global Nullable, defineProps, defineEmits, Indexable*/
const emits = defineEmits(["select"]); const emits = defineEmits(["select"]);
@@ -63,7 +64,7 @@ limitations under the License. -->
const currentParams = ref<Nullable<EventParams>>(null); const currentParams = ref<Nullable<EventParams>>(null);
const showTrace = ref<boolean>(false); const showTrace = ref<boolean>(false);
const traceOptions = ref<{ type: string; filters?: unknown }>({ const traceOptions = ref<{ type: string; filters?: unknown }>({
type: "Trace", type: WidgetType.Trace,
}); });
const menuPos = reactive<{ x: number; y: number }>({ x: NaN, y: NaN }); const menuPos = reactive<{ x: number; y: number }>({ x: NaN, y: NaN });
const props = defineProps({ const props = defineProps({
@@ -234,12 +235,10 @@ limitations under the License. -->
.no-data { .no-data {
font-size: $font-size-smaller; font-size: $font-size-smaller;
height: 100%; height: 100%;
box-sizing: border-box; align-items: center;
display: -webkit-box; justify-content: center;
-webkit-box-orient: horizontal; display: flex;
-webkit-box-pack: center; color: var(--text-color-placeholder);
-webkit-box-align: center;
color: #666;
} }
.chart { .chart {
@@ -252,11 +251,11 @@ limitations under the License. -->
display: block; display: block;
white-space: nowrap; white-space: nowrap;
z-index: 9999999; z-index: 9999999;
box-shadow: #ddd 1px 2px 10px; box-shadow: var(--sw-topology-box-shadow);
transition: all cubic-bezier(0.075, 0.82, 0.165, 1) linear; transition: all cubic-bezier(0.075, 0.82, 0.165, 1) linear;
background-color: rgb(255 255 255); background-color: var(--sw-bg-color-overlay);
border-radius: 4px; border-radius: 4px;
color: rgb(51 51 51); color: $font-color;
padding: 5px; padding: 5px;
} }
@@ -267,7 +266,7 @@ limitations under the License. -->
&:hover { &:hover {
color: $active-color; color: $active-color;
background-color: #eee; background-color: $popper-hover-bg-color;
} }
} }
</style> </style>

View File

@@ -85,10 +85,10 @@ limitations under the License. -->
.bar-select { .bar-select {
position: relative; position: relative;
justify-content: space-between; justify-content: space-between;
border: 1px solid #ddd; border: 1px solid var(--el-border-color);
background: $theme-background; background: $theme-background;
border-radius: 3px; border-radius: 3px;
color: #000; color: $font-color;
font-size: $font-size-smaller; font-size: $font-size-smaller;
height: 24px; height: 24px;
@@ -97,8 +97,8 @@ limitations under the License. -->
border-radius: 3px; border-radius: 3px;
margin: 3px; margin: 3px;
color: $active-color; color: $active-color;
background-color: #fafafa; background-color: var(--theme-background);
border: 1px solid #e8e8e8; border: 1px solid var(--el-color-primary);
text-align: center; text-align: center;
} }
} }
@@ -112,7 +112,7 @@ limitations under the License. -->
width: 100%; width: 100%;
padding: 2px 10px; padding: 2px 10px;
overflow: auto; overflow: auto;
color: #606266; color: var(--sw-setting-color);
position: relative; position: relative;
&:hover { &:hover {
@@ -133,13 +133,13 @@ limitations under the License. -->
} }
.opt-wrapper { .opt-wrapper {
color: #606266; color: var(--sw-setting-color);
position: absolute; position: absolute;
top: 26px; top: 26px;
left: 0; left: 0;
background-color: $theme-background; background-color: $theme-background;
box-shadow: 0 1px 6px rgb(99 99 99 / 20%); box-shadow: 0 1px 6px rgb(99 99 99 / 20%);
border: 1px solid #ddd; border: 1px solid var(--el-border-color);
width: 100%; width: 100%;
border-radius: 0 0 3px 3px; border-radius: 0 0 3px 3px;
border-right-width: 1px !important; border-right-width: 1px !important;
@@ -169,7 +169,7 @@ limitations under the License. -->
} }
&:hover { &:hover {
background-color: #f5f5f5; background-color: var(--layout-background);
} }
} }
</style> </style>

View File

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

View File

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

View File

@@ -26,3 +26,8 @@ export const Languages = [
{ label: "Chinese", value: "zh" }, { label: "Chinese", value: "zh" },
{ label: "Spanish", value: "es" }, { label: "Spanish", value: "es" },
]; ];
export enum Themes {
Dark = "dark",
Light = "light",
}

View File

@@ -24,6 +24,7 @@ export const Services = {
group group
layers layers
normal normal
shortName
} }
`, `,
}; };

View File

@@ -23,6 +23,7 @@ export const ServicesTopology = {
name name
type type
isReal isReal
layers
} }
calls { calls {
id 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 * See the License for the specific language governing permissions and
* limitations under the License. * 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 getInstanceTopology = `query queryData(${InstanceTopology.variable}) {${InstanceTopology.query}}`;
export const getEndpointTopology = `query queryData(${EndpointTopology.variable}) {${EndpointTopology.query}}`; export const getEndpointTopology = `query queryData(${EndpointTopology.variable}) {${EndpointTopology.query}}`;
export const getServicesTopology = `query queryData(${ServicesTopology.variable}) {${ServicesTopology.query}}`; export const getServicesTopology = `query queryData(${ServicesTopology.variable}) {${ServicesTopology.query}}`;
export const getProcessTopology = `query queryData(${ProcessTopology.variable}) {${ProcessTopology.query}}`; export const getProcessTopology = `query queryData(${ProcessTopology.variable}) {${ProcessTopology.query}}`;
export const getHierarchyInstanceTopology = `query queryData(${HierarchyInstanceTopology.variable}) {${HierarchyInstanceTopology.query}}`;
export const getHierarchyServiceTopology = `query queryData(${HierarchyServiceTopology.variable}) {${HierarchyServiceTopology.query}}`;
export const queryListLayerLevels = `query queryLayerLevels {${ListLayerLevels.query}}`;

View File

@@ -14,32 +14,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
export enum MetricQueryTypes {
ReadMetricsValue = "readMetricsValue",
ReadMetricsValues = "readMetricsValues",
SortMetrics = "sortMetrics",
ReadLabeledMetricsValues = "readLabeledMetricsValues",
READHEATMAP = "readHeatMap",
ReadSampledRecords = "readSampledRecords",
ReadRecords = "readRecords",
ReadNullableMetricsValue = "readNullableMetricsValue",
}
export enum Calculations {
Percentage = "percentage",
ByteToKB = "byteToKB",
ByteToMB = "byteToMB",
ByteToGB = "byteToGB",
Apdex = "apdex",
ConvertSeconds = "convertSeconds",
ConvertMilliseconds = "convertMilliseconds",
MsToS = "msTos",
Average = "average",
PercentageAvg = "percentageAvg",
ApdexAvg = "apdexAvg",
SecondToDay = "secondToDay",
NanosecondToMillisecond = "nanosecondToMillisecond",
}
export enum sizeEnum { export enum sizeEnum {
XS = "XS", XS = "XS",
SM = "SM", SM = "SM",
@@ -68,50 +43,6 @@ screenMap.set(sizeEnum.XL, screenEnum.XL);
screenMap.set(sizeEnum.XXL, screenEnum.XXL); screenMap.set(sizeEnum.XXL, screenEnum.XXL);
export const RespFields: Indexable = { export const RespFields: Indexable = {
readMetricsValues: `{
label
values {
values {value isEmptyValue}
}
}`,
readMetricsValue: ``,
readNullableMetricsValue: `{
value
isEmptyValue
}`,
sortMetrics: `{
name
id
value
refId
}`,
readLabeledMetricsValues: `{
label
values {
values {value isEmptyValue}
}
}`,
readHeatMap: `{
values {
id
values
}
buckets {
min
max
}
}`,
readSampledRecords: `{
name
value
refId
}`,
readRecords: `{
id
name
value
refId
}`,
execExpression: `{ execExpression: `{
type type
results { results {
@@ -130,3 +61,43 @@ export const RespFields: Indexable = {
error error
}`, }`,
}; };
export const DarkChartColors = [
"#79bbff",
"#a0a7e6",
"#30A4EB",
"#45BFC0",
"#ebbf93",
"#884dde",
"#1bbf93",
"#7289ab",
"#f56c6c",
"#81feb7",
"#4094fa",
"#ff894d",
"#884dde",
"#ebbf93",
"#fedc6d",
"#da7cfa",
"#b88230",
"#a0cfff",
];
export const LightChartColors = [
"#3f96e3",
"#a0a7e6",
"#45BFC0",
"#FFCC55",
"#FF6A84",
"#c23531",
"#2f4554",
"#61a0a8",
"#d48265",
"#91c7ae",
"#749f83",
"#ca8622",
"#bda29a",
"#6e7074",
"#546570",
"#c4ccd3",
];

View File

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

View File

@@ -30,6 +30,8 @@ import { useDebounceFn } from "@vueuse/core";
import { useEventListener } from "./useEventListener"; import { useEventListener } from "./useEventListener";
import { useBreakpoint } from "./useBreakpoint"; import { useBreakpoint } from "./useBreakpoint";
import echarts from "@/utils/echarts"; import echarts from "@/utils/echarts";
import { useAppStoreWithOut } from "@/store/modules/app";
import { Themes } from "@/constants/data";
export type ECOption = echarts.ComposeOption< export type ECOption = echarts.ComposeOption<
| BarSeriesOption | BarSeriesOption
@@ -44,8 +46,9 @@ export type ECOption = echarts.ComposeOption<
>; >;
export function useECharts(elRef: Ref<HTMLDivElement>, theme: "light" | "dark" | "default" = "default"): Indexable { export function useECharts(elRef: Ref<HTMLDivElement>, theme: "light" | "dark" | "default" = "default"): Indexable {
const appStore = useAppStoreWithOut();
const getDarkMode = computed(() => { const getDarkMode = computed(() => {
return theme === "default" ? "light" : theme; return appStore.theme === "default" ? Themes.Light : theme;
}); });
let chartInstance: Nullable<echarts.ECharts> = null; let chartInstance: Nullable<echarts.ECharts> = null;
let resizeFn: Fn = resize; let resizeFn: Fn = resize;
@@ -55,7 +58,7 @@ export function useECharts(elRef: Ref<HTMLDivElement>, theme: "light" | "dark" |
resizeFn = useDebounceFn(resize, 200); resizeFn = useDebounceFn(resize, 200);
const getOptions = computed(() => { const getOptions = computed(() => {
if (getDarkMode.value !== "dark") { if (getDarkMode.value !== Themes.Dark) {
return cacheOptions.value as ECOption; return cacheOptions.value as ECOption;
} }
return { return {

View File

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

View File

@@ -16,6 +16,9 @@
*/ */
import type { LegendOptions } from "@/types/dashboard"; import type { LegendOptions } from "@/types/dashboard";
import { isDef } from "@/utils/is"; import { isDef } from "@/utils/is";
import { DarkChartColors, LightChartColors } from "./data";
import { useAppStoreWithOut } from "@/store/modules/app";
import { Themes } from "@/constants/data";
export default function useLegendProcess(legend?: LegendOptions) { export default function useLegendProcess(legend?: LegendOptions) {
let isRight = false; let isRight = false;
@@ -96,37 +99,11 @@ export default function useLegendProcess(legend?: LegendOptions) {
return { source, headers }; return { source, headers };
} }
function chartColors(keys: string[]) { function chartColors() {
let color: string[] = []; const appStore = useAppStoreWithOut();
switch (keys.length) { const list = appStore.theme === Themes.Dark ? DarkChartColors : LightChartColors;
case 2:
color = ["#FF6A84", "#a0b1e6"]; return list;
break;
case 1:
color = ["#3f96e3"];
break;
default:
color = [
"#30A4EB",
"#45BFC0",
"#FFCC55",
"#FF6A84",
"#a0a7e6",
"#c23531",
"#2f4554",
"#61a0a8",
"#d48265",
"#91c7ae",
"#749f83",
"#ca8622",
"#bda29a",
"#6e7074",
"#546570",
"#c4ccd3",
];
break;
}
return color;
} }
return { showEchartsLegend, isRight, aggregations, chartColors }; return { showEchartsLegend, isRight, aggregations, chartColors };
} }

View File

@@ -1,41 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { MetricQueryTypes, Calculations } from "./data";
import { MetricModes } from "@/views/dashboard/data";
export function useListConfig(config: Indexable, index: number) {
if (config.metricModes === MetricModes.Expression) {
return {
isLinear: false,
isAvg: true,
};
}
const i = Number(index);
const types = [Calculations.Average, Calculations.ApdexAvg, Calculations.PercentageAvg];
const calculation = config.metricConfig && config.metricConfig[i] && config.metricConfig[i].calculation;
const isLinear =
[MetricQueryTypes.ReadMetricsValues, MetricQueryTypes.ReadLabeledMetricsValues].includes(config.metricTypes[i]) &&
!types.includes(calculation);
const isAvg =
[MetricQueryTypes.ReadMetricsValues, MetricQueryTypes.ReadLabeledMetricsValues].includes(config.metricTypes[i]) &&
types.includes(calculation);
return {
isLinear,
isAvg,
};
}

View File

@@ -1,437 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import dayjs from "dayjs";
import { RespFields, MetricQueryTypes, Calculations } from "./data";
import { ElMessage } from "element-plus";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app";
import type { Instance, Endpoint, Service } from "@/types/selector";
import type { MetricConfigOpt } from "@/types/dashboard";
export function useQueryProcessor(config: Indexable) {
if (!(config.metrics && config.metrics[0])) {
return;
}
if (!(config.metricTypes && config.metricTypes[0])) {
return;
}
const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore();
if (!selectorStore.currentService && dashboardStore.entity !== "All") {
return;
}
const conditions: Recordable = {
duration: appStore.durationTime,
};
const variables: string[] = [`$duration: Duration!`];
const isRelation = ["ServiceRelation", "ServiceInstanceRelation", "EndpointRelation", "ProcessRelation"].includes(
dashboardStore.entity,
);
if (isRelation && !selectorStore.currentDestService) {
return;
}
const fragment = config.metrics.map((name: string, index: number) => {
const metricType = config.metricTypes[index] || "";
const c = (config.metricConfig && config.metricConfig[index]) || {};
if ([MetricQueryTypes.ReadSampledRecords, MetricQueryTypes.SortMetrics].includes(metricType)) {
variables.push(`$condition${index}: TopNCondition!`);
conditions[`condition${index}`] = {
name,
parentService: ["All"].includes(dashboardStore.entity) ? null : selectorStore.currentService.value,
normal: selectorStore.currentService ? selectorStore.currentService.normal : true,
topN: Number(c.topN) || 10,
order: c.sortOrder || "DES",
};
} else {
const entity = {
serviceName: dashboardStore.entity === "All" ? undefined : selectorStore.currentService.value,
normal: dashboardStore.entity === "All" ? undefined : selectorStore.currentService.normal,
serviceInstanceName: ["ServiceInstance", "ServiceInstanceRelation", "ProcessRelation"].includes(
dashboardStore.entity,
)
? selectorStore.currentPod && selectorStore.currentPod.value
: undefined,
endpointName: dashboardStore.entity.includes("Endpoint")
? selectorStore.currentPod && selectorStore.currentPod.value
: undefined,
processName: dashboardStore.entity.includes("Process")
? selectorStore.currentProcess && selectorStore.currentProcess.value
: undefined,
destNormal: isRelation ? selectorStore.currentDestService.normal : undefined,
destServiceName: isRelation ? selectorStore.currentDestService.value : undefined,
destServiceInstanceName: ["ServiceInstanceRelation", "ProcessRelation"].includes(dashboardStore.entity)
? selectorStore.currentDestPod && selectorStore.currentDestPod.value
: undefined,
destEndpointName:
dashboardStore.entity === "EndpointRelation"
? selectorStore.currentDestPod && selectorStore.currentDestPod.value
: undefined,
destProcessName: dashboardStore.entity.includes("ProcessRelation")
? selectorStore.currentDestProcess && selectorStore.currentDestProcess.value
: undefined,
};
if ([MetricQueryTypes.ReadRecords].includes(metricType)) {
variables.push(`$condition${index}: RecordCondition!`);
conditions[`condition${index}`] = {
name,
parentEntity: entity,
topN: Number(c.topN) || 10,
order: c.sortOrder || "DES",
};
} else {
if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) {
const labels = (c.labelsIndex || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
variables.push(`$labels${index}: [String!]!`);
conditions[`labels${index}`] = labels;
}
variables.push(`$condition${index}: MetricsCondition!`);
conditions[`condition${index}`] = {
name,
entity,
};
}
}
if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) {
return `${name}${index}: ${metricType}(condition: $condition${index}, labels: $labels${index}, duration: $duration)${RespFields[metricType]}`;
}
const t = metricType === MetricQueryTypes.ReadMetricsValue ? MetricQueryTypes.ReadNullableMetricsValue : metricType;
return `${name}${index}: ${t}(condition: $condition${index}, duration: $duration)${RespFields[t]}`;
});
const queryStr = `query queryData(${variables}) {${fragment}}`;
return {
queryStr,
conditions,
};
}
export function useSourceProcessor(
resp: { errors: string; data: Indexable },
config: {
metrics: string[];
metricTypes: string[];
metricConfig: MetricConfigOpt[];
},
) {
if (resp.errors) {
ElMessage.error(resp.errors);
return {};
}
if (!resp.data) {
ElMessage.error("The query is wrong");
return {};
}
const source: { [key: string]: unknown } = {};
const keys = Object.keys(resp.data);
config.metricTypes.forEach((type: string, index) => {
const m = config.metrics[index];
const c = (config.metricConfig && config.metricConfig[index]) || {};
if (type === MetricQueryTypes.ReadMetricsValues) {
source[c.label || m] = (resp.data[keys[index]] && calculateExp(resp.data[keys[index]].values.values, c)) || [];
}
if (type === MetricQueryTypes.ReadLabeledMetricsValues) {
const resVal = Object.values(resp.data)[0] || [];
const labels = (c.label || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
const labelsIdx = (c.labelsIndex || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
for (const item of resVal) {
const values = item.values.values.map((d: { value: number; isEmptyValue: boolean }) =>
d.isEmptyValue ? NaN : aggregation(Number(d.value), c),
);
const indexNum = labelsIdx.findIndex((d: string) => d === item.label);
if (labels[indexNum] && indexNum > -1) {
source[labels[indexNum]] = values;
} else {
source[item.label] = values;
}
}
}
if (type === MetricQueryTypes.ReadMetricsValue) {
const v = Object.values(resp.data)[0] || {};
source[m] = v.isEmptyValue ? NaN : aggregation(Number(v.value), c);
}
if (
(
[MetricQueryTypes.ReadRecords, MetricQueryTypes.ReadSampledRecords, MetricQueryTypes.SortMetrics] as string[]
).includes(type)
) {
source[m] = (Object.values(resp.data)[0] || []).map((d: { value: unknown; name: string }) => {
d.value = aggregation(Number(d.value), c);
return d;
});
}
if (type === MetricQueryTypes.READHEATMAP) {
const resVal = Object.values(resp.data)[0] || {};
const nodes = [] as Indexable[];
if (!(resVal && resVal.values)) {
source[m] = { nodes: [] };
return;
}
resVal.values.forEach((items: { values: number[] }, x: number) => {
const grids = items.values.map((val: number, y: number) => [x, y, val]);
nodes.push(...grids);
});
let buckets = [] as Indexable[];
if (resVal.buckets.length) {
buckets = [resVal.buckets[0].min, ...resVal.buckets.map((item: { min: string; max: string }) => item.max)];
}
source[m] = { nodes, buckets }; // nodes: number[][]
}
});
return source;
}
export function useQueryPodsMetrics(
pods: Array<(Instance | Endpoint | Service) & Indexable>,
config: {
metrics: string[];
metricTypes: string[];
metricConfig: MetricConfigOpt[];
},
scope: string,
) {
const metricTypes = (config.metricTypes || []).filter((m: string) => m);
if (!metricTypes.length) {
return;
}
const metrics = (config.metrics || []).filter((m: string) => m);
if (!metrics.length) {
return;
}
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const conditions: { [key: string]: unknown } = {
duration: appStore.durationTime,
};
const variables: string[] = [`$duration: Duration!`];
const currentService = selectorStore.currentService || {};
const fragmentList = pods.map((d: (Instance | Endpoint | Service) & Indexable, index: number) => {
const param = {
serviceName: scope === "Service" ? d.label : currentService.label,
serviceInstanceName: scope === "ServiceInstance" ? d.label : undefined,
endpointName: scope === "Endpoint" ? d.label : undefined,
normal: scope === "Service" ? d.normal : currentService.normal,
};
const f = metrics.map((name: string, idx: number) => {
const metricType = metricTypes[idx] || "";
variables.push(`$condition${index}${idx}: MetricsCondition!`);
conditions[`condition${index}${idx}`] = {
name,
entity: param,
};
let labelStr = "";
if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) {
const c = config.metricConfig[idx] || {};
variables.push(`$labels${index}${idx}: [String!]!`);
labelStr = `labels: $labels${index}${idx}, `;
const labels = (c.labelsIndex || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
conditions[`labels${index}${idx}`] = labels;
}
const t =
metricType === MetricQueryTypes.ReadMetricsValue ? MetricQueryTypes.ReadNullableMetricsValue : metricType;
return `${name}${index}${idx}: ${t}(condition: $condition${index}${idx}, ${labelStr}duration: $duration)${RespFields[t]}`;
});
return f;
});
const fragment = fragmentList.flat(1).join(" ");
const queryStr = `query queryData(${variables}) {${fragment}}`;
return { queryStr, conditions };
}
export function usePodsSource(
pods: Array<Instance | Endpoint>,
resp: { errors: string; data: Indexable },
config: {
metrics: string[];
metricTypes: string[];
metricConfig: MetricConfigOpt[];
},
): Indexable {
if (resp.errors) {
ElMessage.error(resp.errors);
return {};
}
const names: string[] = [];
const metricConfigArr: MetricConfigOpt[] = [];
const metricTypesArr: string[] = [];
const data = pods.map((d: any, idx: number) => {
config.metrics.map((name: string, index: number) => {
const c: any = (config.metricConfig && config.metricConfig[index]) || {};
const key = name + idx + index;
if (config.metricTypes[index] === MetricQueryTypes.ReadMetricsValue) {
const v = resp.data[key];
d[name] = v.isEmptyValue ? NaN : aggregation(v.value, c);
if (idx === 0) {
names.push(name);
metricConfigArr.push(c);
metricTypesArr.push(config.metricTypes[index]);
}
}
if (config.metricTypes[index] === MetricQueryTypes.ReadMetricsValues) {
d[name] = {};
if ([Calculations.Average, Calculations.ApdexAvg, Calculations.PercentageAvg].includes(c.calculation)) {
d[name]["avg"] = calculateExp(resp.data[key].values.values, c);
}
d[name]["values"] = resp.data[key].values.values.map((val: { value: number; isEmptyValue: boolean }) =>
val.isEmptyValue ? NaN : aggregation(val.value, c),
);
if (idx === 0) {
names.push(name);
metricConfigArr.push(c);
metricTypesArr.push(config.metricTypes[index]);
}
}
if (config.metricTypes[index] === MetricQueryTypes.ReadLabeledMetricsValues) {
const resVal = resp.data[key] || [];
const labels = (c.label || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
const labelsIdx = (c.labelsIndex || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
for (let i = 0; i < resVal.length; i++) {
const item = resVal[i];
const values = item.values.values.map((d: { value: number; isEmptyValue: boolean }) =>
d.isEmptyValue ? NaN : aggregation(Number(d.value), c),
);
const indexNum = labelsIdx.findIndex((d: string) => d === item.label);
let key = item.label;
if (labels[indexNum] && indexNum > -1) {
key = labels[indexNum];
}
if (!d[key]) {
d[key] = {};
}
if ([Calculations.Average, Calculations.ApdexAvg, Calculations.PercentageAvg].includes(c.calculation)) {
d[key]["avg"] = calculateExp(item.values.values, c);
}
d[key]["values"] = values;
if (idx === 0) {
names.push(key);
metricConfigArr.push({ ...c, index: i });
metricTypesArr.push(config.metricTypes[index]);
}
}
}
});
return d;
});
return { data, names, metricConfigArr, metricTypesArr };
}
export function useQueryTopologyMetrics(metrics: string[], ids: string[]) {
const appStore = useAppStoreWithOut();
const conditions: { [key: string]: unknown } = {
duration: appStore.durationTime,
ids,
};
const variables: string[] = [`$duration: Duration!`, `$ids: [ID!]!`];
const fragmentList = metrics.map((d: string, index: number) => {
conditions[`m${index}`] = d;
variables.push(`$m${index}: String!`);
return `${d}: getValues(metric: {
name: $m${index}
ids: $ids
}, duration: $duration) {
values {
id
value
}
}`;
});
const queryStr = `query queryData(${variables}) {${fragmentList.join(" ")}}`;
return { queryStr, conditions };
}
export function calculateExp(
list: { value: number; isEmptyValue: boolean }[],
config: { calculation?: string },
): (number | string)[] {
const arr = list.filter((d: { value: number; isEmptyValue: boolean }) => !d.isEmptyValue);
const sum = arr.length ? arr.map((d: { value: number }) => Number(d.value)).reduce((a, b) => a + b) : 0;
let data: (number | string)[] = [];
switch (config.calculation) {
case Calculations.Average:
data = [(sum / arr.length).toFixed(2)];
break;
case Calculations.PercentageAvg:
data = [(sum / arr.length / 100).toFixed(2)];
break;
case Calculations.ApdexAvg:
data = [(sum / arr.length / 10000).toFixed(2)];
break;
default:
data = list.map((d: { value: number; isEmptyValue: boolean }) =>
d.isEmptyValue ? NaN : aggregation(d.value, config),
);
break;
}
return data;
}
export function aggregation(val: number, config: { calculation?: string }): number | string {
let data: number | string = Number(val);
switch (config.calculation) {
case Calculations.Percentage:
data = (val / 100).toFixed(2);
break;
case Calculations.PercentageAvg:
data = (val / 100).toFixed(2);
break;
case Calculations.ByteToKB:
data = (val / 1024).toFixed(2);
break;
case Calculations.ByteToMB:
data = (val / 1024 / 1024).toFixed(2);
break;
case Calculations.ByteToGB:
data = (val / 1024 / 1024 / 1024).toFixed(2);
break;
case Calculations.Apdex:
data = (val / 10000).toFixed(2);
break;
case Calculations.ConvertSeconds:
data = dayjs(val * 1000).format("YYYY-MM-DD HH:mm:ss");
break;
case Calculations.ConvertMilliseconds:
data = dayjs(val).format("YYYY-MM-DD HH:mm:ss");
break;
case Calculations.MsToS:
data = (val / 1000).toFixed(2);
break;
case Calculations.SecondToDay:
data = (val / 86400).toFixed(2);
break;
case Calculations.NanosecondToMillisecond:
data = (val / 1000 / 1000).toFixed(2);
break;
case Calculations.ApdexAvg:
data = (val / 10000).toFixed(2);
break;
default:
data;
break;
}
return data;
}

View File

@@ -24,7 +24,7 @@ limitations under the License. -->
<style lang="scss" scoped> <style lang="scss" scoped>
.app-main { .app-main {
height: calc(100% - 40px); height: calc(100% - 40px);
background: #f7f9fa; background: $layout-background;
overflow: auto; overflow: auto;
} }
</style> </style>

View File

@@ -48,11 +48,20 @@ limitations under the License. -->
@input="changeTimeRange" @input="changeTimeRange"
/> />
<span> UTC{{ appStore.utcHour >= 0 ? "+" : "" }}{{ `${appStore.utcHour}:${appStore.utcMin}` }} </span> <span> UTC{{ appStore.utcHour >= 0 ? "+" : "" }}{{ `${appStore.utcHour}:${appStore.utcMin}` }} </span>
<span class="ml-5" ref="themeSwitchRef">
<el-switch
v-model="theme"
:active-icon="Moon"
:inactive-icon="Sunny"
inline-prompt
@change="handleChangeTheme"
/>
</span>
<span title="refresh" class="ghost ml-5 cp" @click="handleReload"> <span title="refresh" class="ghost ml-5 cp" @click="handleReload">
<Icon iconName="retry" :loading="appStore.autoRefresh" class="middle" /> <Icon iconName="retry" :loading="appStore.autoRefresh" class="middle" />
</span> </span>
<span class="version ml-5 cp"> <span class="version ml-5 cp">
<el-popover trigger="hover" width="250" placement="bottom" effect="light" :content="appStore.version"> <el-popover trigger="hover" width="250" placement="bottom" :content="appStore.version">
<template #reference> <template #reference>
<span> <span>
<Icon iconName="info_outline" size="middle" /> <Icon iconName="info_outline" size="middle" />
@@ -64,17 +73,18 @@ limitations under the License. -->
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch } from "vue"; import { Themes } from "@/constants/data";
import { useRoute } from "vue-router"; import router from "@/router";
import { useI18n } from "vue-i18n";
import timeFormat from "@/utils/timeFormat";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { ElMessage } from "element-plus";
import { MetricCatalog } from "@/views/dashboard/data";
import type { DashboardItem } from "@/types/dashboard"; import type { DashboardItem } from "@/types/dashboard";
import router from "@/router"; import timeFormat from "@/utils/timeFormat";
import { ArrowRight } from "@element-plus/icons-vue"; import { MetricCatalog } from "@/views/dashboard/data";
import { ArrowRight, Moon, Sunny } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
/*global Indexable */ /*global Indexable */
const { t, te } = useI18n(); const { t, te } = useI18n();
@@ -84,11 +94,67 @@ limitations under the License. -->
const pathNames = ref<{ path?: string; name: string; selected: boolean }[][]>([]); const pathNames = ref<{ path?: string; name: string; selected: boolean }[][]>([]);
const timeRange = ref<number>(0); const timeRange = ref<number>(0);
const pageTitle = ref<string>(""); const pageTitle = ref<string>("");
const theme = ref<boolean>(true);
const themeSwitchRef = ref<HTMLElement>();
const savedTheme = window.localStorage.getItem("theme-is-dark");
if (savedTheme === "false") {
theme.value = false;
}
if (savedTheme === "") {
// read the theme preference from system setting if there is no user setting
theme.value = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
}
changeTheme();
resetDuration(); resetDuration();
getVersion(); getVersion();
getNavPaths(); getNavPaths();
function changeTheme() {
const root = document.documentElement;
if (theme.value) {
root.classList.add(Themes.Dark);
root.classList.remove(Themes.Light);
appStore.setTheme(Themes.Dark);
} else {
root.classList.add(Themes.Light);
root.classList.remove(Themes.Dark);
appStore.setTheme(Themes.Light);
}
window.localStorage.setItem("theme-is-dark", String(theme.value));
}
function handleChangeTheme() {
const x = themeSwitchRef.value?.offsetLeft ?? 0;
const y = themeSwitchRef.value?.offsetTop ?? 0;
const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y));
// compatibility handling
if (!document.startViewTransition) {
changeTheme();
return;
}
// api: https://developer.chrome.com/docs/web-platform/view-transitions
const transition = document.startViewTransition(() => {
changeTheme();
});
transition.ready.then(() => {
const clipPath = [`circle(0px at ${x}px ${y}px)`, `circle(${endRadius}px at ${x}px ${y}px)`];
document.documentElement.animate(
{
clipPath: !theme.value ? clipPath.reverse() : clipPath,
},
{
duration: 500,
easing: "ease-in",
pseudoElement: !theme.value ? "::view-transition-old(root)" : "::view-transition-new(root)",
},
);
});
}
function getName(list: any[]) { function getName(list: any[]) {
return list.find((d: any) => d.selected) || {}; return list.find((d: any) => d.selected) || {};
} }
@@ -287,21 +353,16 @@ limitations under the License. -->
padding: 5px; padding: 5px;
text-align: left; text-align: left;
justify-content: space-between; justify-content: space-between;
background-color: #fafbfc; background-color: $theme-background;
border-bottom: 1px solid #dfe4e8; border-bottom: 1px solid $border-color;
color: #222; color: $font-color;
font-size: $font-size-smaller; font-size: $font-size-smaller;
} }
.nav-bar.dark {
background-color: #333840;
border-bottom: 1px solid #252a2f;
color: #fafbfc;
}
.title { .title {
font-size: $font-size-normal; font-size: $font-size-normal;
font-weight: 500; font-weight: 500;
padding-top: 5px;
} }
.nav-tabs { .nav-tabs {

View File

@@ -94,14 +94,13 @@ const msg = {
editTab: "Enable editing tab names", editTab: "Enable editing tab names",
label: "Service Name", label: "Service Name",
id: "Service ID", id: "Service ID",
setRoot: "Set this to root", setRoot: "Set Normal to Root",
setNormal: "Set this to normal", setNormal: "Set Root to Normal",
export: "Export Dashboard Templates", export: "Export Dashboard Templates",
import: "Import Dashboard Templates", import: "Import Dashboard Templates",
yes: "Yes", yes: "Yes",
no: "No", no: "No",
tableHeaderCol1: "Name of the first column of the table", tableHeaderCol2: "Name of the last column of the table",
tableHeaderCol2: "Name of the second column of the table",
showXAxis: "Show X Axis", showXAxis: "Show X Axis",
showYAxis: "Show Y Axis", showYAxis: "Show Y Axis",
nameError: "The dashboard name cannot be duplicate", nameError: "The dashboard name cannot be duplicate",
@@ -165,7 +164,6 @@ const msg = {
iframeSrc: "Iframe Link", iframeSrc: "Iframe Link",
generateLink: "Generate Link", generateLink: "Generate Link",
setDuration: "Lock Query Duration", setDuration: "Lock Query Duration",
openFunction: "OpenFunction",
period: "Period", period: "Period",
windows: "Windows", windows: "Windows",
seconds: "Seconds", seconds: "Seconds",
@@ -297,7 +295,8 @@ const msg = {
return: "Return", return: "Return",
isError: "Error", isError: "Error",
contentType: "Content Type", contentType: "Content Type",
content: "Content", content: "Timestamp - Content",
level: "Level",
viewLogs: "View Logs", viewLogs: "View Logs",
logsTagsTip: `Only tags defined in the core/default/searchableLogsTags are searchable. logsTagsTip: `Only tags defined in the core/default/searchableLogsTags are searchable.
Check more details on the Configuration Vocabulary page`, Check more details on the Configuration Vocabulary page`,
@@ -378,9 +377,13 @@ const msg = {
menus: "Menus", menus: "Menus",
saveReload: "Save and reload the page", saveReload: "Save and reload the page",
document: "Documentation", document: "Documentation",
metricMode: "Metric Mode",
addExpressions: "Add Expressions", addExpressions: "Add Expressions",
expressions: "Expression", expressions: "Expression",
unhealthyExpression: "Unhealthy Expression", unhealthyExpression: "Unhealthy Expression",
traceDesc:
"The trace segment serves as a representation of a trace portion executed within one single OS process, such as a JVM. It comprises a collection of spans, typically associated with and collected from a single request or execution context.",
tabExpressions: "Tab Expressions",
hierarchyNodeMetrics: "Metrics for Hierarchy Graph Node",
hierarchyNodeDashboard: "As dashboard for Hierarchy Graph Node",
}; };
export default msg; export default msg;

View File

@@ -101,8 +101,7 @@ const msg = {
import: "Importar Plantilla Panel", import: "Importar Plantilla Panel",
yes: "Sí", yes: "Sí",
no: "No", no: "No",
tableHeaderCol1: "Nombre de la primera columna de la tabla", tableHeaderCol2: "Nombre de la Último columna de la tabla",
tableHeaderCol2: "Nombre de la segunda columna de la tabla",
showXAxis: "Mostrar Eje X", showXAxis: "Mostrar Eje X",
showYAxis: "Mostrar Eje Y", showYAxis: "Mostrar Eje Y",
nameError: "El nombre del panel no puede ser duplicado", nameError: "El nombre del panel no puede ser duplicado",
@@ -149,7 +148,6 @@ const msg = {
iframeSrc: "Enlace Iframe", iframeSrc: "Enlace Iframe",
generateLink: "Generar enlaces", generateLink: "Generar enlaces",
setDuration: "Duración de la consulta de bloqueo", setDuration: "Duración de la consulta de bloqueo",
openFunction: "OpenFunction",
seconds: "Segundos", seconds: "Segundos",
hourTip: "Seleccione Hora", hourTip: "Seleccione Hora",
minuteTip: "Seleccione Minuto", minuteTip: "Seleccione Minuto",
@@ -297,6 +295,7 @@ const msg = {
isError: "Error", isError: "Error",
contentType: "Tipo de Contenido", contentType: "Tipo de Contenido",
content: "Contenido", content: "Contenido",
level: "Level",
viewLogs: "Ver Registro de Datos", viewLogs: "Ver Registro de Datos",
logsTagsTip: `Solamente etiquetas definidas en core/default/searchableLogsTags pueden ser buscadas. logsTagsTip: `Solamente etiquetas definidas en core/default/searchableLogsTags pueden ser buscadas.
Más información en la página de Vocabulario de Configuración`, Más información en la página de Vocabulario de Configuración`,
@@ -378,9 +377,13 @@ const msg = {
menus: "Menus", menus: "Menus",
saveReload: "Save and reload the page", saveReload: "Save and reload the page",
document: "Documentation", document: "Documentation",
metricMode: "Metric Mode",
addExpressions: "Add Expressions", addExpressions: "Add Expressions",
expressions: "Expression", expressions: "Expression",
unhealthyExpression: "Unhealthy Expression", unhealthyExpression: "Unhealthy Expression",
traceDesc:
"The trace segment serves as a representation of a trace portion executed within one single OS process, such as a JVM. It comprises a collection of spans, typically associated with and collected from a single request or execution context.",
tabExpressions: "Tab Expressions",
hierarchyNodeMetrics: "Metrics for Hierarchy Graph Node",
hierarchyNodeDashboard: "As dashboard for Hierarchy Graph Node",
}; };
export default msg; export default msg;

View File

@@ -30,6 +30,11 @@ const titles = {
general_service_virtual_mq: "Virtual MQ", general_service_virtual_mq: "Virtual MQ",
general_service_virtual_mq_desc: general_service_virtual_mq_desc:
"Observe the virtual message queue servers which are conjectured by language agents through various plugins.", "Observe the virtual message queue servers which are conjectured by language agents through various plugins.",
// Workflow Scheduler
workflow_scheduler: "Workflow Scheduler",
workflow_scheduler_desc: "Provide monitoring for workflow scheduling systems.",
workflow_scheduler_airflow: "Airflow",
workflow_scheduler_airflow_desc: "Observe tasks through telemetry data collected from Apache Airflow.",
// Service Mesh // Service Mesh
service_mesh: "Service Mesh", service_mesh: "Service Mesh",
service_mesh_desc: service_mesh_desc:
@@ -41,13 +46,6 @@ const titles = {
service_mesh_control_plane_desc: "Provide monitoring of the behavior of Istio through its self-monitoring metrics.", service_mesh_control_plane_desc: "Provide monitoring of the behavior of Istio through its self-monitoring metrics.",
service_mesh_data_plane: "Data Plane", service_mesh_data_plane: "Data Plane",
service_mesh_data_plane_desc: "Observe Envoy Proxy through Envoy Metrics Service.", service_mesh_data_plane_desc: "Observe Envoy Proxy through Envoy Metrics Service.",
// Functions
functions: "Functions",
functions_desc:
"FaaS (Function-as-a-Service) is a type of cloud-computing service that allows you to execute code in response to events without the complex infrastructure typically associated with building and launching microservices applications.",
functions_openfunction: "OpenFunction",
functions_openfunction_desc:
"OpenFunction as a FaaS platform, provides out-of-box observability with SkyWalking integration.",
// Kubernetes // Kubernetes
kubernetes: "Kubernetes", kubernetes: "Kubernetes",
kubernetes_desc: kubernetes_desc:
@@ -82,6 +80,8 @@ const titles = {
gateway: "Gateway", gateway: "Gateway",
gateway_desc: gateway_desc:
"API gateway is an API management tool that sits between a client and a collection of backend services.", "API gateway is an API management tool that sits between a client and a collection of backend services.",
gateway_nginx: "Nginx",
gateway_nginx_desc: "Provide Nginx monitoring through OpenTelemetry's Prometheus Receiver.",
gateway_apisix: "APISIX", gateway_apisix: "APISIX",
gateway_apisix_desc: "Provide APISIX monitoring through OpenTelemetry's Prometheus Receiver.", gateway_apisix_desc: "Provide APISIX monitoring through OpenTelemetry's Prometheus Receiver.",
gateway_aws_api_gateway: "AWS API Gateway", gateway_aws_api_gateway: "AWS API Gateway",
@@ -109,6 +109,12 @@ const titles = {
"A message queue is a form of asynchronous service-to-service communication used in serverless and microservices architectures.", "A message queue is a form of asynchronous service-to-service communication used in serverless and microservices architectures.",
mq_rabbitmq: "RabbitMQ", mq_rabbitmq: "RabbitMQ",
mq_rabbitmq_desc: "Provide RabbitMQ monitoring through OpenTelemetry's Prometheus Receiver.", mq_rabbitmq_desc: "Provide RabbitMQ monitoring through OpenTelemetry's Prometheus Receiver.",
mq_kafka: "Kafka",
mq_kafka_desc: "Provide Kafka monitoring through OpenTelemetry's Prometheus Receiver.",
mq_pulsar: "Pulsar",
mq_pulsar_desc: "Provide Pulsar monitoring through OpenTelemetry's Prometheus Receiver.",
mq_rocketmq: "RocketMQ",
mq_rocketmq_desc: "Provide RocketMQ monitoring through OpenTelemetry's Prometheus Receiver.",
// self observability // self observability
self_observability: "Self Observability", self_observability: "Self Observability",
self_observability_desc: self_observability_desc:

View File

@@ -30,6 +30,12 @@ const titles = {
general_service_virtual_mq: "MQ virtual", general_service_virtual_mq: "MQ virtual",
general_service_virtual_mq_desc: general_service_virtual_mq_desc:
"Observe the virtual message queue servers which are conjectured by language agents through various plugins.", "Observe the virtual message queue servers which are conjectured by language agents through various plugins.",
// Workflow Scheduler
workflow_scheduler: "Flujo de trabajo",
workflow_scheduler_desc: "Proporcionar monitoreo para sistemas de programación de flujos de trabajo.",
workflow_scheduler_airflow: "Airflow",
workflow_scheduler_airflow_desc:
"Observando tareas a través de los datos de telemetría recopilados desde Apache Airflow.",
// Service Mesh // Service Mesh
service_mesh: "Malla de Servicios", service_mesh: "Malla de Servicios",
service_mesh_desc: service_mesh_desc:
@@ -41,13 +47,6 @@ const titles = {
service_mesh_control_plane_desc: "Provide monitoring of the behavior of Istio through its self-monitoring metrics.", service_mesh_control_plane_desc: "Provide monitoring of the behavior of Istio through its self-monitoring metrics.",
service_mesh_data_plane: "Plano de Datos", service_mesh_data_plane: "Plano de Datos",
service_mesh_data_plane_desc: "Observe Envoy Proxy through Envoy Metrics Service.", service_mesh_data_plane_desc: "Observe Envoy Proxy through Envoy Metrics Service.",
// Functions
functions: "Funciones",
functions_desc:
"FaaS (Function-as-a-Service) is a type of cloud-computing service that allows you to execute code in response to events without the complex infrastructure typically associated with building and launching microservices applications.",
functions_openfunction: "OpenFunction",
functions_openfunction_desc:
"OpenFunction as a FaaS platform, provides out-of-box observability with SkyWalking integration.",
// Kubernetes // Kubernetes
kubernetes: "Kubernetes", kubernetes: "Kubernetes",
kubernetes_desc: kubernetes_desc:
@@ -82,6 +81,8 @@ const titles = {
gateway: "Puerta", gateway: "Puerta",
gateway_desc: gateway_desc:
"API gateway is an API management tool that sits between a client and a collection of backend services.", "API gateway is an API management tool that sits between a client and a collection of backend services.",
gateway_nginx: "Nginx",
gateway_nginx_desc: "Provide Nginx monitoring through OpenTelemetry's Prometheus Receiver.",
gateway_apisix: "APISIX", gateway_apisix: "APISIX",
gateway_apisix_desc: "Provide APISIX monitoring through OpenTelemetry's Prometheus Receiver.", gateway_apisix_desc: "Provide APISIX monitoring through OpenTelemetry's Prometheus Receiver.",
gateway_aws_api_gateway: "AWS API Gateway", gateway_aws_api_gateway: "AWS API Gateway",
@@ -109,6 +110,12 @@ const titles = {
"A message queue is a form of asynchronous service-to-service communication used in serverless and microservices architectures.", "A message queue is a form of asynchronous service-to-service communication used in serverless and microservices architectures.",
mq_rabbitmq: "RabbitMQ", mq_rabbitmq: "RabbitMQ",
mq_rabbitmq_desc: "Provide RabbitMQ monitoring through OpenTelemetry's Prometheus Receiver.", mq_rabbitmq_desc: "Provide RabbitMQ monitoring through OpenTelemetry's Prometheus Receiver.",
mq_kafka: "Kafka",
mq_kafka_desc: "Provide Kafka monitoring through OpenTelemetry's Prometheus Receiver.",
mq_pulsar: "Pulsar",
mq_pulsar_desc: "Provide Pulsar monitoring through OpenTelemetry's Prometheus Receiver.",
mq_rocketmq: "RocketMQ",
mq_rocketmq_desc: "Provide RocketMQ monitoring through OpenTelemetry's Prometheus Receiver.",
// self observability // self observability
self_observability: "Self Observability", self_observability: "Self Observability",
self_observability_desc: self_observability_desc:

View File

@@ -26,6 +26,11 @@ const titles = {
general_service_virtual_cache_desc: "观察语言代理通过各种插件推测的虚拟缓存服务器。", general_service_virtual_cache_desc: "观察语言代理通过各种插件推测的虚拟缓存服务器。",
general_service_virtual_mq: "虚拟消息队列", general_service_virtual_mq: "虚拟消息队列",
general_service_virtual_mq_desc: "观察语言代理通过各种插件推测的虚拟消息队列服务器。", general_service_virtual_mq_desc: "观察语言代理通过各种插件推测的虚拟消息队列服务器。",
// Workflow Scheduler
workflow_scheduler: "工作流调度",
workflow_scheduler_desc: "提供工作流调度系统监控。",
workflow_scheduler_airflow: "Airflow",
workflow_scheduler_airflow_desc: "通过从Apache Airflow收集的遥测数据观察任务。",
// Service Mesh // Service Mesh
service_mesh: "服务网格", service_mesh: "服务网格",
service_mesh_desc: "服务网格Istio通过分布式或微服务架构解决了开发人员和运营商面临的挑战。", service_mesh_desc: "服务网格Istio通过分布式或微服务架构解决了开发人员和运营商面临的挑战。",
@@ -35,12 +40,6 @@ const titles = {
service_mesh_control_plane_desc: "通过Istio的自我监控指标提供对其行为的监控。", service_mesh_control_plane_desc: "通过Istio的自我监控指标提供对其行为的监控。",
service_mesh_data_plane: "数据平面", service_mesh_data_plane: "数据平面",
service_mesh_data_plane_desc: "通过Envoy Metrics Service观察Envoy Proxy。", service_mesh_data_plane_desc: "通过Envoy Metrics Service观察Envoy Proxy。",
// Functions
functions: "Functions",
functions_desc:
"FaaS功能即服务是一种云计算服务允许您在没有通常与构建和启动微服务应用程序相关的复杂基础设施的情况下执行代码以响应事件。",
functions_openfunction: "OpenFunction",
functions_openfunction_desc: "OpenFunction作为一个FaaS平台通过SkyWalking集成提供开箱即用的可观察性。",
// Kubernetes // Kubernetes
kubernetes: "Kubernetes", kubernetes: "Kubernetes",
kubernetes_desc: "Kubernetes是一个开源的容器编排系统用于自动化软件部署、扩展和管理。", kubernetes_desc: "Kubernetes是一个开源的容器编排系统用于自动化软件部署、扩展和管理。",
@@ -72,6 +71,8 @@ const titles = {
// Gateway // Gateway
gateway: "网关", gateway: "网关",
gateway_desc: "API网关是位于客户端和后端服务集合之间的API管理工具。", gateway_desc: "API网关是位于客户端和后端服务集合之间的API管理工具。",
gateway_nginx: "Nginx",
gateway_nginx_desc: "通过OpenTelemetry的Prometheus接收器提供Nginx监控。",
gateway_apisix: "APISIX", gateway_apisix: "APISIX",
gateway_apisix_desc: "通过OpenTelemetry的Prometheus接收器提供APISIX监控。", gateway_apisix_desc: "通过OpenTelemetry的Prometheus接收器提供APISIX监控。",
gateway_aws_api_gateway: "AWS API Gateway", gateway_aws_api_gateway: "AWS API Gateway",
@@ -96,6 +97,12 @@ const titles = {
mq_desc: "消息队列是无服务器和微服务架构中使用的异步服务对服务通信的一种形式。", mq_desc: "消息队列是无服务器和微服务架构中使用的异步服务对服务通信的一种形式。",
mq_rabbitmq: "RabbitMQ", mq_rabbitmq: "RabbitMQ",
mq_rabbitmq_desc: "通过OpenTelemetry的Prometheus接收器提供RabbitMQ监控。", mq_rabbitmq_desc: "通过OpenTelemetry的Prometheus接收器提供RabbitMQ监控。",
mq_kafka: "Kafka",
mq_Kafka_desc: "通过OpenTelemetry的Prometheus接收器提供Kafka监控。",
mq_pulsar: "Pulsar",
mq_Pulsar_desc: "通过OpenTelemetry的Prometheus接收器提供Pulsar监控。",
mq_rocketmq: "RocketMQ",
mq_rocketmq_desc: "通过OpenTelemetry的Prometheus接收器提供RocketMQ监控。",
// self observability // self observability
self_observability: "自监控", self_observability: "自监控",
self_observability_desc: "自观察性为运行SkyWalking生态系统中的组件和服务器提供了可观察性。", self_observability_desc: "自观察性为运行SkyWalking生态系统中的组件和服务器提供了可观察性。",

View File

@@ -99,8 +99,7 @@ const msg = {
import: "导入仪表板模板", import: "导入仪表板模板",
yes: "是", yes: "是",
no: "否", no: "否",
tableHeaderCol1: "表格的一列的名称", tableHeaderCol2: "表格的最后一列的名称",
tableHeaderCol2: "表格的第二列的名称",
showXAxis: "显示X轴", showXAxis: "显示X轴",
showYAxis: "显示Y轴", showYAxis: "显示Y轴",
nameError: "仪表板名称不能重复", nameError: "仪表板名称不能重复",
@@ -163,7 +162,6 @@ const msg = {
iframeSrc: "Iframe链接", iframeSrc: "Iframe链接",
generateLink: "生成链接", generateLink: "生成链接",
setDuration: "锁定查询持续时间", setDuration: "锁定查询持续时间",
openFunction: "OpenFunction",
period: "周期", period: "周期",
windows: "Windows", windows: "Windows",
seconds: "秒", seconds: "秒",
@@ -294,7 +292,8 @@ const msg = {
return: "返回", return: "返回",
isError: "错误", isError: "错误",
contentType: "内容类型", contentType: "内容类型",
content: "内容", content: "时间戳 - 内容",
level: "Level",
viewLogs: "查看日志", viewLogs: "查看日志",
logsTagsTip: "只有core/default/searchableLogsTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。", logsTagsTip: "只有core/default/searchableLogsTags中定义的标记才可搜索。查看配置词汇表页面上的更多详细信息。",
keywordsOfContentLogTips: "SkyWalking OAP服务器的当前存储不支持此操作", keywordsOfContentLogTips: "SkyWalking OAP服务器的当前存储不支持此操作",
@@ -376,9 +375,13 @@ const msg = {
menusManagement: "菜单", menusManagement: "菜单",
saveReload: "保存并重新加载页面", saveReload: "保存并重新加载页面",
document: "文档", document: "文档",
metricMode: "指标模式",
addExpressions: "添加表达式", addExpressions: "添加表达式",
expressions: "表达式", expressions: "表达式",
unhealthyExpression: "非健康表达式", unhealthyExpression: "非健康表达式",
traceDesc:
"Trace Segment代表在单一操作系统进程例如JVM中执行的追踪部分。它包含了一组跨度spans这些跨度通常与单一请求或执行上下文关联。",
tabExpressions: "Tab表达式",
hierarchyNodeMetrics: "层次图节点的指标",
hierarchyNodeDashboard: "作为层次图节点的dashboard",
}; };
export default msg; export default msg;

View File

@@ -16,6 +16,7 @@
*/ */
import type { RouteRecordRaw } from "vue-router"; import type { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue"; import Layout from "@/layout/Index.vue";
import Alarm from "@/views/Alarm.vue";
export const routesAlarm: Array<RouteRecordRaw> = [ export const routesAlarm: Array<RouteRecordRaw> = [
{ {
@@ -33,7 +34,7 @@ export const routesAlarm: Array<RouteRecordRaw> = [
{ {
path: "/alerting", path: "/alerting",
name: "Alarm", name: "Alarm",
component: () => import("@/views/Alarm.vue"), component: Alarm,
}, },
], ],
}, },

View File

@@ -16,6 +16,10 @@
*/ */
import type { RouteRecordRaw } from "vue-router"; import type { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue"; import Layout from "@/layout/Index.vue";
import List from "@/views/dashboard/List.vue";
import New from "@/views/dashboard/New.vue";
import Edit from "@/views/dashboard/Edit.vue";
import Widget from "@/views/dashboard/Widget.vue";
export const routesDashboard: Array<RouteRecordRaw> = [ export const routesDashboard: Array<RouteRecordRaw> = [
{ {
@@ -32,7 +36,7 @@ export const routesDashboard: Array<RouteRecordRaw> = [
children: [ children: [
{ {
path: "/dashboard/list", path: "/dashboard/list",
component: () => import("@/views/dashboard/List.vue"), component: List,
name: "List", name: "List",
meta: { meta: {
i18nKey: "dashboardList", i18nKey: "dashboardList",
@@ -42,7 +46,7 @@ export const routesDashboard: Array<RouteRecordRaw> = [
}, },
{ {
path: "/dashboard/new", path: "/dashboard/new",
component: () => import("@/views/dashboard/New.vue"), component: New,
name: "New", name: "New",
meta: { meta: {
i18nKey: "dashboardNew", i18nKey: "dashboardNew",
@@ -54,26 +58,26 @@ export const routesDashboard: Array<RouteRecordRaw> = [
path: "", path: "",
redirect: "/dashboard/:layerId/:entity/:name", redirect: "/dashboard/:layerId/:entity/:name",
name: "Create", name: "Create",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
meta: { meta: {
notShow: true, notShow: true,
}, },
children: [ children: [
{ {
path: "/dashboard/:layerId/:entity/:name", path: "/dashboard/:layerId/:entity/:name",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
name: "CreateChild", name: "CreateChild",
}, },
{ {
path: "/dashboard/:layerId/:entity/:name/tab/:activeTabIndex", path: "/dashboard/:layerId/:entity/:name/tab/:activeTabIndex",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
name: "CreateActiveTabIndex", name: "CreateActiveTabIndex",
}, },
], ],
}, },
{ {
path: "", path: "",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
name: "View", name: "View",
redirect: "/dashboard/:layerId/:entity/:serviceId/:name", redirect: "/dashboard/:layerId/:entity/:serviceId/:name",
meta: { meta: {
@@ -82,12 +86,12 @@ export const routesDashboard: Array<RouteRecordRaw> = [
children: [ children: [
{ {
path: "/dashboard/:layerId/:entity/:serviceId/:name", path: "/dashboard/:layerId/:entity/:serviceId/:name",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
name: "ViewChild", name: "ViewChild",
}, },
{ {
path: "/dashboard/:layerId/:entity/:serviceId/:name/tab/:activeTabIndex", path: "/dashboard/:layerId/:entity/:serviceId/:name/tab/:activeTabIndex",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
name: "ViewActiveTabIndex", name: "ViewActiveTabIndex",
}, },
], ],
@@ -95,7 +99,7 @@ export const routesDashboard: Array<RouteRecordRaw> = [
{ {
path: "", path: "",
redirect: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name", redirect: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
name: "ViewServiceRelation", name: "ViewServiceRelation",
meta: { meta: {
notShow: true, notShow: true,
@@ -103,12 +107,12 @@ export const routesDashboard: Array<RouteRecordRaw> = [
children: [ children: [
{ {
path: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name", path: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
name: "ViewServiceRelation", name: "ViewServiceRelation",
}, },
{ {
path: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name/tab/:activeTabIndex", path: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name/tab/:activeTabIndex",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
name: "ViewServiceRelationActiveTabIndex", name: "ViewServiceRelationActiveTabIndex",
}, },
], ],
@@ -116,7 +120,7 @@ export const routesDashboard: Array<RouteRecordRaw> = [
{ {
path: "", path: "",
redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:name", redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:name",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
name: "ViewPod", name: "ViewPod",
meta: { meta: {
notShow: true, notShow: true,
@@ -124,12 +128,12 @@ export const routesDashboard: Array<RouteRecordRaw> = [
children: [ children: [
{ {
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:name", path: "/dashboard/:layerId/:entity/:serviceId/:podId/:name",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
name: "ViewPod", name: "ViewPod",
}, },
{ {
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:name/tab/:activeTabIndex", path: "/dashboard/:layerId/:entity/:serviceId/:podId/:name/tab/:activeTabIndex",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
name: "ViewPodActiveTabIndex", name: "ViewPodActiveTabIndex",
}, },
], ],
@@ -137,7 +141,7 @@ export const routesDashboard: Array<RouteRecordRaw> = [
{ {
path: "", path: "",
redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:name", redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:name",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
name: "ViewProcess", name: "ViewProcess",
meta: { meta: {
notShow: true, notShow: true,
@@ -145,12 +149,12 @@ export const routesDashboard: Array<RouteRecordRaw> = [
children: [ children: [
{ {
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:name", path: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:name",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
name: "ViewProcess", name: "ViewProcess",
}, },
{ {
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:name/tab/:activeTabIndex", path: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:name/tab/:activeTabIndex",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
name: "ViewProcessActiveTabIndex", name: "ViewProcessActiveTabIndex",
}, },
], ],
@@ -158,7 +162,7 @@ export const routesDashboard: Array<RouteRecordRaw> = [
{ {
path: "", path: "",
redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name", redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
name: "PodRelation", name: "PodRelation",
meta: { meta: {
notShow: true, notShow: true,
@@ -166,12 +170,12 @@ export const routesDashboard: Array<RouteRecordRaw> = [
children: [ children: [
{ {
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name", path: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
name: "ViewPodRelation", name: "ViewPodRelation",
}, },
{ {
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name/tab/:activeTabIndex", path: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name/tab/:activeTabIndex",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
name: "ViewPodRelationActiveTabIndex", name: "ViewPodRelationActiveTabIndex",
}, },
], ],
@@ -180,7 +184,7 @@ export const routesDashboard: Array<RouteRecordRaw> = [
path: "", path: "",
redirect: redirect:
"/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name", "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
name: "ProcessRelation", name: "ProcessRelation",
meta: { meta: {
notShow: true, notShow: true,
@@ -188,17 +192,17 @@ export const routesDashboard: Array<RouteRecordRaw> = [
children: [ children: [
{ {
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name", path: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
name: "ViewProcessRelation", name: "ViewProcessRelation",
}, },
{ {
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name/tab/:activeTabIndex", path: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name/tab/:activeTabIndex",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
name: "ViewProcessRelationActiveTabIndex", name: "ViewProcessRelationActiveTabIndex",
}, },
{ {
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name/duration/:duration", path: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name/duration/:duration",
component: () => import("@/views/dashboard/Edit.vue"), component: Edit,
name: "ViewProcessRelationDuration", name: "ViewProcessRelationDuration",
}, },
], ],
@@ -206,14 +210,14 @@ export const routesDashboard: Array<RouteRecordRaw> = [
{ {
path: "", path: "",
name: "Widget", name: "Widget",
component: () => import("@/views/dashboard/Widget.vue"), component: Widget,
meta: { meta: {
notShow: true, notShow: true,
}, },
children: [ children: [
{ {
path: "/page/:layer/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:config/:duration?", path: "/page/:layer/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:config/:duration?",
component: () => import("@/views/dashboard/Widget.vue"), component: Widget,
name: "ViewWidget", name: "ViewWidget",
}, },
], ],

View File

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

View File

@@ -17,6 +17,7 @@
import Layout from "@/layout/Index.vue"; import Layout from "@/layout/Index.vue";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import type { MenuOptions } from "@/types/app"; import type { MenuOptions } from "@/types/app";
import Layer from "@/views/Layer.vue";
function layerDashboards() { function layerDashboards() {
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
@@ -47,13 +48,13 @@ function layerDashboards() {
descKey: child.descKey, descKey: child.descKey,
i18nKey: child.i18nKey, i18nKey: child.i18nKey,
}, },
component: () => import("@/views/Layer.vue"), component: Layer,
}; };
route.children.push(d); route.children.push(d);
const tab = { const tab = {
name: `${child.name}ActiveTabIndex`, name: `${child.name}ActiveTabIndex`,
path: `/${child.name}/tab/:activeTabIndex`, path: `/${child.path}/tab/:activeTabIndex`,
component: () => import("@/views/Layer.vue"), component: Layer,
meta: { meta: {
notShow: true, notShow: true,
layer: child.layer, layer: child.layer,
@@ -74,7 +75,7 @@ function layerDashboards() {
descKey: item.descKey, descKey: item.descKey,
i18nKey: item.i18nKey, i18nKey: item.i18nKey,
}, },
component: () => import("@/views/Layer.vue"), component: Layer,
}, },
]; ];
} }

View File

@@ -16,6 +16,7 @@
*/ */
import type { RouteRecordRaw } from "vue-router"; import type { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue"; import Layout from "@/layout/Index.vue";
import Marketplace from "@/views/Marketplace.vue";
export const routesMarketplace: Array<RouteRecordRaw> = [ export const routesMarketplace: Array<RouteRecordRaw> = [
{ {
@@ -33,7 +34,7 @@ export const routesMarketplace: Array<RouteRecordRaw> = [
{ {
path: "/marketplace", path: "/marketplace",
name: "MenusManagement", name: "MenusManagement",
component: () => import("@/views/Marketplace.vue"), component: Marketplace,
}, },
], ],
}, },

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

@@ -16,6 +16,7 @@
*/ */
import type { RouteRecordRaw } from "vue-router"; import type { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue"; import Layout from "@/layout/Index.vue";
import Settings from "@/views/Settings.vue";
export const routesSettings: Array<RouteRecordRaw> = [ export const routesSettings: Array<RouteRecordRaw> = [
{ {
@@ -33,7 +34,7 @@ export const routesSettings: Array<RouteRecordRaw> = [
{ {
path: "/settings", path: "/settings",
name: "Settings", name: "Settings",
component: () => import("@/views/Settings.vue"), component: Settings,
}, },
], ],
}, },

View File

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

View File

@@ -23,6 +23,7 @@ import type { AxiosResponse } from "axios";
import dateFormatStep, { dateFormatTime } from "@/utils/dateFormat"; import dateFormatStep, { dateFormatTime } from "@/utils/dateFormat";
import { TimeType } from "@/constants/data"; import { TimeType } from "@/constants/data";
import type { MenuOptions, SubItem } from "@/types/app"; import type { MenuOptions, SubItem } from "@/types/app";
import { Themes } from "@/constants/data";
/*global Nullable*/ /*global Nullable*/
interface AppState { interface AppState {
durationRow: Recordable; durationRow: Recordable;
@@ -36,6 +37,7 @@ interface AppState {
isMobile: boolean; isMobile: boolean;
reloadTimer: Nullable<IntervalHandle>; reloadTimer: Nullable<IntervalHandle>;
allMenus: MenuOptions[]; allMenus: MenuOptions[];
theme: string;
} }
export const appStore = defineStore({ export const appStore = defineStore({
@@ -56,6 +58,7 @@ export const appStore = defineStore({
isMobile: false, isMobile: false,
reloadTimer: null, reloadTimer: null,
allMenus: [], allMenus: [],
theme: Themes.Dark,
}), }),
getters: { getters: {
duration(): Duration { duration(): Duration {
@@ -126,6 +129,9 @@ export const appStore = defineStore({
updateDurationRow(data: Duration) { updateDurationRow(data: Duration) {
this.durationRow = data; this.durationRow = data;
}, },
setTheme(data: string) {
this.theme = data;
},
setUTC(utcHour: number, utcMin: number): void { setUTC(utcHour: number, utcMin: number): void {
this.runEventStack(); this.runEventStack();
this.utcMin = utcMin; this.utcMin = utcMin;

View File

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

View File

@@ -163,7 +163,7 @@ export const profileStore = defineStore({
return res.data; return res.data;
}, },
async getSegmentSpans(params: { segmentId: string }) { async getSegmentSpans(params: { segmentId: string }) {
if (!params.segmentId) { if (!(params && params.segmentId)) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql.query("queryProfileSegment").params(params); const res: AxiosResponse = await graphql.query("queryProfileSegment").params(params);

View File

@@ -16,14 +16,13 @@
*/ */
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { store } from "@/store"; import { store } from "@/store";
import type { Service } from "@/types/selector"; import type { Node, Call, HierarchyNode, ServiceHierarchy, InstanceHierarchy } from "@/types/topology";
import type { Node, Call } from "@/types/topology";
import graphql from "@/graphql"; import graphql from "@/graphql";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import type { AxiosResponse } from "axios"; import type { AxiosResponse } from "axios";
import query from "@/graphql/fetch"; import query from "@/graphql/fetch";
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor"; import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
@@ -35,9 +34,15 @@ interface TopologyState {
call: Nullable<Call>; call: Nullable<Call>;
calls: Call[]; calls: Call[];
nodes: Node[]; nodes: Node[];
hierarchyServiceCalls: Call[];
hierarchyServiceNodes: HierarchyNode[];
hierarchyInstanceCalls: Call[];
hierarchyInstanceNodes: HierarchyNode[];
nodeMetricValue: MetricVal; nodeMetricValue: MetricVal;
linkServerMetrics: MetricVal; linkServerMetrics: MetricVal;
linkClientMetrics: MetricVal; linkClientMetrics: MetricVal;
hierarchyNodeMetrics: { [key: string]: MetricVal };
hierarchyInstanceNodeMetrics: { [key: string]: MetricVal };
} }
export const topologyStore = defineStore({ export const topologyStore = defineStore({
@@ -45,11 +50,17 @@ export const topologyStore = defineStore({
state: (): TopologyState => ({ state: (): TopologyState => ({
calls: [], calls: [],
nodes: [], nodes: [],
hierarchyServiceCalls: [],
hierarchyServiceNodes: [],
hierarchyInstanceCalls: [],
hierarchyInstanceNodes: [],
node: null, node: null,
call: null, call: null,
nodeMetricValue: {}, nodeMetricValue: {},
linkServerMetrics: {}, linkServerMetrics: {},
linkClientMetrics: {}, linkClientMetrics: {},
hierarchyNodeMetrics: {},
hierarchyInstanceNodeMetrics: {},
}), }),
actions: { actions: {
setNode(node: Node) { setNode(node: Node) {
@@ -75,12 +86,9 @@ export const topologyStore = defineStore({
}, },
setTopology(data: { nodes: Node[]; calls: Call[] }) { setTopology(data: { nodes: Node[]; calls: Call[] }) {
const obj = {} as Recordable; const obj = {} as Recordable;
const services = useSelectorStore().services;
const nodes = (data.nodes || []).reduce((prev: Node[], next: Node) => { const nodes = (data.nodes || []).reduce((prev: Node[], next: Node) => {
if (!obj[next.id]) { if (!obj[next.id]) {
obj[next.id] = true; 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); prev.push(next);
} }
return prev; return prev;
@@ -106,8 +114,107 @@ export const topologyStore = defineStore({
this.calls = calls; this.calls = calls;
this.nodes = nodes; this.nodes = nodes;
}, },
setNodeMetricValue(m: MetricVal) { setHierarchyInstanceTopology(data: InstanceHierarchy, levels: { layer: string; level: number }[]) {
this.nodeMetricValue = m; 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) { setLinkServerMetrics(m: MetricVal) {
this.linkServerMetrics = m; this.linkServerMetrics = m;
@@ -115,12 +222,16 @@ export const topologyStore = defineStore({
setLinkClientMetrics(m: MetricVal) { setLinkClientMetrics(m: MetricVal) {
this.linkClientMetrics = m; this.linkClientMetrics = m;
}, },
setNodeMetricValue(m: MetricVal) {
this.nodeMetricValue = m;
},
setLegendValues(expressions: string, data: { [key: string]: any }) { setLegendValues(expressions: string, data: { [key: string]: any }) {
for (let idx = 0; idx < this.nodes.length; idx++) { const nodeArr = this.nodes.filter((d: Node) => d.isReal);
for (let idx = 0; idx < nodeArr.length; idx++) {
for (let index = 0; index < expressions.length; index++) { for (let index = 0; index < expressions.length; index++) {
const k = "expression" + idx + index; const k = "expression" + idx + index;
if (expressions[index]) { if (expressions[index]) {
this.nodes[idx][expressions[index]] = Number(data[k].results[0].values[0].value); nodeArr[idx][expressions[index]] = Number(data[k].results[0].values[0].value);
} }
} }
} }
@@ -191,6 +302,9 @@ export const topologyStore = defineStore({
} }
}, },
async getServicesTopology(serviceIds: string[]) { async getServicesTopology(serviceIds: string[]) {
if (!serviceIds.length) {
return new Promise((resolve) => resolve({}));
}
const duration = useAppStoreWithOut().durationTime; const duration = useAppStoreWithOut().durationTime;
const res: AxiosResponse = await graphql.query("getServicesTopology").params({ const res: AxiosResponse = await graphql.query("getServicesTopology").params({
serviceIds, serviceIds,
@@ -207,7 +321,7 @@ export const topologyStore = defineStore({
const clientServiceId = (currentDestService && currentDestService.id) || ""; const clientServiceId = (currentDestService && currentDestService.id) || "";
const duration = useAppStoreWithOut().durationTime; const duration = useAppStoreWithOut().durationTime;
if (!(serverServiceId && clientServiceId)) { if (!(serverServiceId && clientServiceId)) {
return; return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql.query("getInstanceTopology").params({ const res: AxiosResponse = await graphql.query("getInstanceTopology").params({
clientServiceId, clientServiceId,
@@ -220,6 +334,9 @@ export const topologyStore = defineStore({
return res.data; return res.data;
}, },
async updateEndpointTopology(endpointIds: string[], depth: number) { async updateEndpointTopology(endpointIds: string[], depth: number) {
if (!endpointIds.length) {
return new Promise((resolve) => resolve({}));
}
const res = await this.getEndpointTopology(endpointIds); const res = await this.getEndpointTopology(endpointIds);
if (depth > 1) { if (depth > 1) {
const ids = res.nodes.map((item: Node) => item.id).filter((d: string) => !endpointIds.includes(d)); const ids = res.nodes.map((item: Node) => item.id).filter((d: string) => !endpointIds.includes(d));
@@ -285,6 +402,9 @@ export const topologyStore = defineStore({
} }
}, },
async getEndpointTopology(endpointIds: string[]) { async getEndpointTopology(endpointIds: string[]) {
if (!endpointIds.length) {
return new Promise((resolve) => resolve({}));
}
const duration = useAppStoreWithOut().durationTime; const duration = useAppStoreWithOut().durationTime;
const variables = ["$duration: Duration!"]; const variables = ["$duration: Duration!"];
const fragment = endpointIds.map((id: string, index: number) => { const fragment = endpointIds.map((id: string, index: number) => {
@@ -323,15 +443,6 @@ export const topologyStore = defineStore({
return { calls, nodes }; return { calls, nodes };
}, },
async getNodeMetricValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const res: AxiosResponse = await query(param);
if (res.data.errors) {
return res.data;
}
this.setNodeMetricValue(res.data.data);
return res.data;
},
async getNodeExpressionValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) { async getNodeExpressionValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const res: AxiosResponse = await query(param); const res: AxiosResponse = await query(param);
@@ -341,38 +452,6 @@ export const topologyStore = defineStore({
return res.data; return res.data;
}, },
async getLinkClientMetrics(linkClientMetrics: string[]) {
if (!linkClientMetrics.length) {
this.setLinkClientMetrics({});
return;
}
const idsC = this.calls.filter((i: Call) => i.detectPoints.includes("CLIENT")).map((b: Call) => b.id);
if (!idsC.length) {
return;
}
const param = await useQueryTopologyMetrics(linkClientMetrics, idsC);
const res = await this.getCallClientMetrics(param);
if (res.errors) {
ElMessage.error(res.errors);
}
},
async getLinkServerMetrics(linkServerMetrics: string[]) {
if (!linkServerMetrics.length) {
this.setLinkServerMetrics({});
return;
}
const idsS = this.calls.filter((i: Call) => i.detectPoints.includes("SERVER")).map((b: Call) => b.id);
if (!idsS.length) {
return;
}
const param = await useQueryTopologyMetrics(linkServerMetrics, idsS);
const res = await this.getCallServerMetrics(param);
if (res.errors) {
ElMessage.error(res.errors);
}
},
async getLinkExpressions(expressions: string[], type: string) { async getLinkExpressions(expressions: string[], type: string) {
if (!expressions.length) { if (!expressions.length) {
this.setLinkServerMetrics({}); this.setLinkServerMetrics({});
@@ -396,22 +475,6 @@ export const topologyStore = defineStore({
this.setLinkClientMetrics(metrics); this.setLinkClientMetrics(metrics);
} }
}, },
async queryNodeMetrics(nodeMetrics: string[]) {
if (!nodeMetrics.length) {
this.setNodeMetricValue({});
return;
}
const ids = this.nodes.map((d: Node) => d.id);
if (!ids.length) {
return;
}
const param = await useQueryTopologyMetrics(nodeMetrics, ids);
const res = await this.getNodeMetricValue(param);
if (res.errors) {
ElMessage.error(res.errors);
}
},
async queryNodeExpressions(expressions: string[]) { async queryNodeExpressions(expressions: string[]) {
if (!expressions.length) { if (!expressions.length) {
this.setNodeMetricValue({}); this.setNodeMetricValue({});
@@ -423,7 +486,7 @@ export const topologyStore = defineStore({
} }
const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor( const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor(
expressions, expressions,
this.nodes, this.nodes.filter((d: Node) => d.isReal),
); );
const param = getExpressionQuery(); const param = getExpressionQuery();
const res = await this.getNodeExpressionValue(param); const res = await this.getNodeExpressionValue(param);
@@ -434,44 +497,97 @@ export const topologyStore = defineStore({
const metrics = handleExpressionValues(res.data); const metrics = handleExpressionValues(res.data);
this.setNodeMetricValue(metrics); this.setNodeMetricValue(metrics);
}, },
async getLegendMetrics(param: { queryStr: string; conditions: { [key: string]: unknown } }) { async getHierarchyServiceTopology() {
const res: AxiosResponse = await query(param); 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) { if (res.data.errors) {
return res.data; return res.data;
} }
const data = res.data.data; const resp = await this.getListLayerLevels();
const metrics = Object.keys(data); if (resp.errors) {
this.nodes = this.nodes.map((d: Node & Recordable) => { return resp;
for (const m of metrics) { }
for (const val of data[m].values) { const levels = resp.data.levels || [];
if (d.id === val.id) { this.setHierarchyServiceTopology(res.data.data.hierarchyServiceTopology || {}, levels);
d[m] = val.value;
}
}
}
return d;
});
return res.data; return res.data;
}, },
async getCallServerMetrics(param: { queryStr: string; conditions: { [key: string]: unknown } }) { async getListLayerLevels() {
const res: AxiosResponse = await query(param); const res: AxiosResponse = await graphql.query("queryListLayerLevels").params({});
if (res.data.errors) {
return res.data;
}
this.setLinkServerMetrics(res.data.data);
return res.data; return res.data;
}, },
async getCallClientMetrics(param: { queryStr: string; conditions: { [key: string]: unknown } }) { async getHierarchyInstanceTopology() {
const res: AxiosResponse = await query(param); 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) { if (res.data.errors) {
return res.data; return res.data;
} }
this.setLinkClientMetrics(res.data.data); const resp = await this.getListLayerLevels();
if (resp.errors) {
return resp;
}
const levels = resp.data.levels || [];
this.setHierarchyInstanceTopology(res.data.data.hierarchyInstanceTopology || {}, levels);
return res.data; return res.data;
}, },
async queryHierarchyExpressions(expressions: string[], nodes: Node[]) {
const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor(expressions, nodes);
const param = getExpressionQuery();
const res = await this.getNodeExpressionValue(param);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
const metrics = handleExpressionValues(res.data);
return metrics;
},
async queryHierarchyNodeExpressions(expressions: string[], layer: string) {
const nodes = this.hierarchyServiceNodes.filter((n: HierarchyNode) => n.layer === layer);
if (!nodes.length) {
this.setHierarchyNodeMetricValue({}, layer);
return;
}
if (!expressions.length) {
this.setHierarchyNodeMetricValue({}, layer);
return;
}
const metrics = await this.queryHierarchyExpressions(expressions, nodes);
this.setHierarchyNodeMetricValue(metrics, layer);
},
async queryHierarchyInstanceNodeExpressions(expressions: string[], layer: string) {
const nodes = this.hierarchyInstanceNodes.filter((n: HierarchyNode) => n.layer === layer);
if (!expressions.length) {
this.setHierarchyInstanceNodeMetricValue({}, layer);
return;
}
if (!nodes.length) {
this.setHierarchyInstanceNodeMetricValue({}, layer);
return;
}
const metrics = await this.queryHierarchyExpressions(expressions, nodes);
this.setHierarchyInstanceNodeMetricValue(metrics, layer);
},
}, },
}); });

View File

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

View File

@@ -123,96 +123,3 @@ code,
pre { pre {
font-family: Consolas, Menlo, Courier, monospace; font-family: Consolas, Menlo, Courier, monospace;
} }
.el-menu {
--el-menu-item-height: 50px;
}
.el-menu-item-group__title {
display: none;
}
.el-sub-menu .el-menu-item {
height: 40px;
line-height: 40px;
padding: 0 0 0 56px !important;
}
.el-sub-menu__title {
.el-icon.menu-icons {
margin-top: -5px !important;
}
}
.el-switch__label--left {
margin-right: 5px;
}
.el-switch__label--right {
margin-left: 5px;
}
.el-switch__label * {
font-size: $font-size-smaller;
}
.el-drawer__header {
margin-bottom: 0;
padding-left: 10px;
font-size: 16px;
}
.el-drawer__body {
padding: 0;
}
.switch {
margin: 0 5px;
}
div.vis-tooltip {
max-width: 600px;
overflow: hidden;
background-color: $theme-background !important;
white-space: normal !important;
font-size: $font-size-smaller !important;
}
.vis-item {
cursor: pointer;
height: 20px;
}
.vis-item.Error {
background-color: #e66;
opacity: 0.8;
border-color: #e66;
color: $text-color !important;
}
.vis-item.Normal {
background-color: #fac858;
border-color: #fac858;
color: #666 !important;
}
.vis-item .vis-item-content {
padding: 0 3px !important;
}
.vis-item.vis-selected.Error,
.vis-item.vis-selected.Normal {
color: #1a1a1a !important;
}
.el-menu--vertical.sub-list {
display: none;
}
div:has(> a.menu-title) {
display: none;
}
.el-input-number .el-input__inner {
text-align: left !important;
}

286
src/styles/theme.scss Normal file
View File

@@ -0,0 +1,286 @@
/**
* 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.
*/
@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 {
--el-color-primary: #409eff;
--theme-background: #fff;
--font-color: #3d444f;
--disabled-color: #ccc;
--dashboard-tool-bg: rgb(240 242 245);
--text-color-placeholder: #666;
--border-color: #dcdfe6;
--border-color-primary: #eee;
--layout-background: #f7f9fa;
--box-shadow-color: #ccc;
--sw-bg-color-overlay: #fff;
--sw-border-color-light: #e4e7ed;
--popper-hover-bg: #eee;
--sw-icon-btn-bg: #eee;
--sw-icon-btn-color: #666;
--sw-icon-btn-border: #ccc;
--sw-table-col: #fff;
--sw-config-header: aliceblue;
--sw-topology-color: #666;
--vis-tooltip-bg: #fff;
--sw-topology-switch-icon: rgba(0, 0, 0, 0.3);
--sw-topology-box-shadow: #eee 1px 2px 10px;
--sw-topology-setting-bg: #fff;
--sw-topology-border: 1px solid #999;
--sw-trace-success: rgb(46 47 51 / 10%);
--sw-trace-list-border: rgb(0 0 0 / 10%);
--sw-list-selected: #ededed;
--sw-table-header: #f3f4f9;
--sw-list-hover: rgb(0 0 0 / 4%);
--sw-setting-color: #606266;
--sw-alarm-tool: #f0f2f5;
--sw-alarm-tool-border: #c1c5ca41;
--sw-table-color: #000;
--sw-event-vis-selected: #1a1a1a;
--sw-time-axis-text: #4d4d4d;
--sw-drawer-header: #72767b;
--sw-marketplace-border: #dedfe0;
--sw-grid-item-active: #d4d7de;
}
html.dark {
--el-color-primary: #409eff;
--theme-background: #212224;
--font-color: #fafbfc;
--disabled-color: #999;
--dashboard-tool-bg: #000;
--text-color-placeholder: #ccc;
--border-color: #262629;
--border-color-primary: #4b4b52;
--layout-background: #000;
--box-shadow-color: #606266;
--sw-bg-color-overlay: #1d1e1f;
--sw-border-color-light: #414243;
--popper-hover-bg: rgb(64, 158, 255, 0.1);
--sw-icon-btn-bg: #222;
--sw-icon-btn-color: #ccc;
--sw-icon-btn-border: #999;
--sw-table-col: none;
--sw-config-header: #303133;
--sw-topology-color: #ccc;
--vis-tooltip-bg: #414243;
--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;
--sw-trace-success: #aaa;
--sw-trace-list-border: #333133;
--sw-list-hover: #262629;
--sw-table-header: #303133;
--sw-list-selected: #3d444f;
--sw-setting-color: #eee;
--sw-alarm-tool: #303133;
--sw-alarm-tool-border: #444;
--sw-table-color: #fff;
--sw-event-vis-selected: #fff;
--sw-time-axis-text: #aaa;
--sw-drawer-header: #e9e9eb;
--sw-marketplace-border: #606266;
--sw-grid-item-active: #73767a;
}
.el-drawer__header {
color: var(--sw-drawer-header);
}
.el-table tr {
background-color: var(--el-table-tr-bg-color);
}
.el-popper.is-light {
background: var(--sw-bg-color-overlay);
border: 1px solid var(--sw-border-color-light);
}
.el-switch {
--el-switch-off-color: #aaa;
}
.el-table__body-wrapper tr td.el-table-fixed-column--left,
.el-table__body-wrapper tr td.el-table-fixed-column--right,
.el-table__body-wrapper tr th.el-table-fixed-column--left,
.el-table__body-wrapper tr th.el-table-fixed-column--right,
.el-table__footer-wrapper tr td.el-table-fixed-column--left,
.el-table__footer-wrapper tr td.el-table-fixed-column--right,
.el-table__footer-wrapper tr th.el-table-fixed-column--left,
.el-table__footer-wrapper tr th.el-table-fixed-column--right,
.el-table__header-wrapper tr td.el-table-fixed-column--left,
.el-table__header-wrapper tr td.el-table-fixed-column--right,
.el-table__header-wrapper tr th.el-table-fixed-column--left,
.el-table__header-wrapper tr th.el-table-fixed-column--right {
background-color: var(--sw-table-col);
}
.el-table.is-scrolling-none th.el-table-fixed-column--left,
.el-table.is-scrolling-none th.el-table-fixed-column--right,
.el-table th.el-table__cell {
background-color: var(--sw-table-col);
}
$tool-icon-btn-bg: var(--sw-icon-btn-bg);
$tool-icon-btn-color: var(--sw-icon-btn-color);
$popper-hover-bg-color: var(--popper-hover-bg);
$box-shadow-color: var(--box-shadow-color);
$border-color-primary: var(--border-color-primary);
$layout-background: var(--layout-background);
$border-color: var(--border-color);
$dashboard-tool-bg-color: var(--dashboard-tool-bg);
$font-color: var(--font-color);
$text-color: var(--theme-background);
$disabled-color: var(--disabled-color);
$active-color: var(--el-color-primary);
$theme-background: var(--theme-background);
$active-background: var(--el-color-primary);
$font-size-smaller: 12px;
$font-size-normal: 14px;
.opt:hover {
background-color: var(--sw-list-hover) !important;
}
.el-loading-mask {
background-color: var(--theme-background);
}
.el-menu {
--el-menu-item-height: 50px;
}
.el-menu-item-group__title {
display: none;
}
.el-sub-menu .el-menu-item {
height: 40px;
line-height: 40px;
padding: 0 0 0 56px !important;
}
.el-sub-menu__title {
.el-icon.menu-icons {
margin-top: -5px !important;
}
}
.el-drawer__header {
margin-bottom: 0;
padding-left: 10px;
font-size: 16px;
}
.el-drawer__body {
padding: 0;
}
.switch {
margin: 0 5px;
}
div.vis-tooltip {
max-width: 600px;
overflow: hidden;
background-color: var(--vis-tooltip-bg) !important;
white-space: normal !important;
font-size: $font-size-smaller !important;
color: var(--font-color) !important;
}
.vis-item {
cursor: pointer;
height: 20px;
}
.vis-item.Error {
background-color: #e66;
opacity: 0.8;
border-color: #e66;
color: var(--sw-event-vis-selected) !important;
}
.vis-item.Normal {
background-color: #fac858;
border-color: #fac858;
color: var(--sw-event-vis-selected) !important;
}
.vis-item .vis-item-content {
padding: 0 3px !important;
}
.vis-item.vis-selected.Error,
.vis-item.vis-selected.Normal {
color: var(--sw-event-vis-selected) !important;
}
.vis-time-axis .vis-text {
color: var(--sw-time-axis-text) !important;
}
.el-menu--vertical.sub-list {
display: none;
}
div:has(> a.menu-title) {
display: none;
}
.el-input-number .el-input__inner {
text-align: left !important;
}
html {
&::view-transition-old(root),
&::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
}
&.dark {
&::view-transition-old(root) {
z-index: 1;
}
&::view-transition-new(root) {
z-index: 999;
}
}
&::view-transition-old(root) {
z-index: 999;
}
&::view-transition-new(root) {
z-index: 1;
}
}

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

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

View File

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

View File

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

View File

@@ -22,6 +22,7 @@ export type Service = {
normal?: boolean; normal?: boolean;
group?: string; group?: string;
merge?: string; merge?: string;
shortName?: string;
}; };
export type Instance = { export type Instance = {

View File

@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
export interface Call { export interface Call {
source: string | any; source: string | any;
target: string | any; target: string | any;
@@ -31,15 +32,48 @@ export interface Call {
targetY?: number; targetY?: number;
targetX?: number; targetX?: number;
} }
export interface HierarchyNode {
id: string;
name: string;
layer: string;
level?: number;
key: string;
}
export interface Node { export interface Node {
id: string; id: string;
name: string; name: string;
type: string; type: string;
isReal: boolean; isReal: boolean;
layer?: string; layers: string[];
serviceName?: string; serviceName?: string;
height?: number; height?: number;
width?: number;
x?: number; x?: number;
y?: number; y?: number;
level?: 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;
} }

View File

@@ -15,11 +15,15 @@
* limitations under the License. * limitations under the License.
*/ */
$font-color: #3d444f; export function debounce(callback: Function, dur: number) {
$text-color: #fff; let timer: any;
$disabled-color: #ccc;
$active-color: #409eff; return function () {
$theme-background: #fff; if (timer) {
$active-background: #409eff; clearTimeout(timer);
$font-size-smaller: 12px; }
$font-size-normal: 14px; timer = setTimeout(function () {
callback();
}, dur);
};
}

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

@@ -0,0 +1,44 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export class mutationObserver {
private static mutationObserverMap: Map<string, MutationObserver> = new Map<string, MutationObserver>();
static create(key: string, callback: MutationCallback) {
const observer = new MutationObserver(callback);
mutationObserver.mutationObserverMap.set(key, observer);
}
static observe(key: string, target: Node, options?: MutationObserverInit) {
const observer = mutationObserver.mutationObserverMap.get(key);
if (observer) {
observer.observe(target, options);
}
}
static deleteObserve(key: string): void {
this.disconnect(key);
this.mutationObserverMap.delete(key);
}
static disconnect(key: string): void {
const observer = this.mutationObserverMap.get(key);
if (observer) {
observer.disconnect();
}
}
}

View File

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

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

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

View File

@@ -156,7 +156,7 @@ limitations under the License. -->
} }
.settings { .settings {
color: #606266; color: var(--sw-setting-color);
font-size: 13px; font-size: 13px;
padding: 20px; padding: 20px;
@@ -177,7 +177,7 @@ limitations under the License. -->
width: 180px; width: 180px;
display: inline-block; display: inline-block;
font-weight: 500; font-weight: 500;
color: #000; color: $font-color;
line-height: 25px; line-height: 25px;
} }
} }

View File

@@ -157,7 +157,7 @@ limitations under the License. -->
.timeline-table-i { .timeline-table-i {
padding: 10px 15px; padding: 10px 15px;
border-left: 4px solid #eee; border-left: 4px solid var(--border-color-primary);
position: relative; position: relative;
&::after { &::after {

View File

@@ -39,7 +39,11 @@ limitations under the License. -->
@current-change="changePage" @current-change="changePage"
:pager-count="5" :pager-count="5"
small small
:style="`--el-pagination-bg-color: #f0f2f5; --el-pagination-button-disabled-bg-color: #f0f2f5;`" :style="
appStore.theme === Themes.Light
? `--el-pagination-bg-color: #f0f2f5; --el-pagination-button-disabled-bg-color: #f0f2f5;`
: ''
"
/> />
</div> </div>
</div> </div>
@@ -54,6 +58,7 @@ limitations under the License. -->
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { useAlarmStore } from "@/store/modules/alarm"; import { useAlarmStore } from "@/store/modules/alarm";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { Themes } from "@/constants/data";
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const alarmStore = useAlarmStore(); const alarmStore = useAlarmStore();
@@ -99,8 +104,8 @@ limitations under the License. -->
<style lang="scss" scoped> <style lang="scss" scoped>
.alarm-tool { .alarm-tool {
font-size: $font-size-smaller; font-size: $font-size-smaller;
border-bottom: 1px solid #c1c5ca41; border-bottom: 1px solid var(--sw-alarm-tool-border);
background-color: #f0f2f5; background-color: var(--sw-alarm-tool);
padding: 10px; padding: 10px;
position: relative; position: relative;
} }

View File

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

View File

@@ -32,10 +32,7 @@ limitations under the License. -->
:config="{ :config="{
i: 0, i: 0,
...graph, ...graph,
metrics: config.metrics,
metricTypes: config.metricTypes,
metricConfig: config.metricConfig, metricConfig: config.metricConfig,
metricMode: config.metricMode,
expressions: config.expressions || [], expressions: config.expressions || [],
typesOfMQE: typesOfMQE || [], typesOfMQE: typesOfMQE || [],
subExpressions: config.subExpressions || [], subExpressions: config.subExpressions || [],
@@ -56,12 +53,10 @@ limitations under the License. -->
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { useQueryProcessor, useSourceProcessor } from "@/hooks/useMetricsProcessor";
import { useExpressionsQueryProcessor } from "@/hooks/useExpressionsProcessor"; import { useExpressionsQueryProcessor } from "@/hooks/useExpressionsProcessor";
import graphs from "./graphs"; import graphs from "./graphs";
import { EntityType } from "./data"; import { EntityType } from "./data";
import timeFormat from "@/utils/timeFormat"; import timeFormat from "@/utils/timeFormat";
import { MetricModes } from "./data";
export default defineComponent({ export default defineComponent({
name: "WidgetPage", name: "WidgetPage",
@@ -87,10 +82,12 @@ limitations under the License. -->
dashboardStore.setLayer(route.params.layer); dashboardStore.setLayer(route.params.layer);
dashboardStore.setEntity(route.params.entity); dashboardStore.setEntity(route.params.entity);
const { auto, autoPeriod } = config.value; const { auto, autoPeriod } = config.value;
if (auto) { if (auto) {
await setDuration(); await setDuration();
appStoreWithOut.setReloadTimer(setInterval(setDuration, autoPeriod * 1000)); appStoreWithOut.setReloadTimer(setInterval(setDuration, autoPeriod * 1000));
} else {
const duration = JSON.parse(route.params.duration as string);
appStoreWithOut.setDuration(duration);
} }
await setSelector(); await setSelector();
await queryMetrics(); await queryMetrics();
@@ -130,37 +127,16 @@ limitations under the License. -->
} }
} }
async function queryMetrics() { async function queryMetrics() {
const isExpression = config.value.metricMode === MetricModes.Expression;
if (isExpression) {
loading.value = true;
const params = await useExpressionsQueryProcessor({
metrics: config.value.expressions || [],
metricConfig: config.value.metricConfig || [],
subExpressions: config.value.subExpressions || [],
});
loading.value = false;
source.value = params.source || {};
typesOfMQE.value = params.typesOfMQE;
return;
}
const params = await useQueryProcessor({ ...config.value });
if (!params) {
source.value = {};
return;
}
loading.value = true; loading.value = true;
const json = await dashboardStore.fetchMetricValue(params); const params = await useExpressionsQueryProcessor({
loading.value = false; metrics: config.value.expressions || [],
if (!json) {
return;
}
const d = {
metrics: config.value.metrics || [],
metricTypes: config.value.metricTypes || [],
metricConfig: config.value.metricConfig || [], metricConfig: config.value.metricConfig || [],
}; subExpressions: config.value.subExpressions || [],
source.value = await useSourceProcessor(json, d); });
loading.value = false;
source.value = params.source || {};
typesOfMQE.value = params.typesOfMQE;
} }
watch( watch(
() => appStoreWithOut.durationTime, () => appStoreWithOut.durationTime,
@@ -184,7 +160,7 @@ limitations under the License. -->
<style lang="scss" scoped> <style lang="scss" scoped>
.content { .content {
min-width: 100px; min-width: 100px;
border: 1px solid #eee; border: 1px solid $border-color-primary;
background-color: $theme-background; background-color: $theme-background;
position: relative; position: relative;
} }
@@ -207,7 +183,7 @@ limitations under the License. -->
height: 25px; height: 25px;
line-height: 25px; line-height: 25px;
text-align: center; text-align: center;
background-color: aliceblue; background-color: var(--sw-config-header);
font-size: $font-size-smaller; font-size: $font-size-smaller;
position: relative; position: relative;
} }

View File

@@ -20,14 +20,9 @@ limitations under the License. -->
</div> </div>
<div v-if="hasDuration"> <div v-if="hasDuration">
<label>{{ t("duration") }}</label> <label>{{ t("duration") }}</label>
<TimePicker <TimePicker :value="duration" position="right" format="YYYY-MM-DD HH:mm" @input="changeTimeRange" />
:value="[appStore.durationRow.start, appStore.durationRow.end]"
position="right"
format="YYYY-MM-DD HH:mm"
@input="changeTimeRange"
/>
</div> </div>
<div v-if="!hasDuration"> <div v-else>
<label>{{ t("auto") }}</label> <label>{{ t("auto") }}</label>
<el-switch class="mr-5" v-model="auto" style="height: 25px" /> <el-switch class="mr-5" v-model="auto" style="height: 25px" />
<Selector v-model="freshOpt" :options="RefreshOptions" size="small" /> <Selector v-model="freshOpt" :options="RefreshOptions" size="small" />
@@ -59,7 +54,6 @@ limitations under the License. -->
import copy from "@/utils/copy"; import copy from "@/utils/copy";
import { RefreshOptions } from "@/views/dashboard/data"; import { RefreshOptions } from "@/views/dashboard/data";
import { TimeType } from "@/constants/data"; import { TimeType } from "@/constants/data";
import { MetricModes } from "../data";
const { t } = useI18n(); const { t } = useI18n();
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
@@ -72,6 +66,7 @@ limitations under the License. -->
const auto = ref<boolean>(true); const auto = ref<boolean>(true);
const period = ref<number>(6); const period = ref<number>(6);
const freshOpt = ref<string>(RefreshOptions[0].value); const freshOpt = ref<string>(RefreshOptions[0].value);
const duration = ref<string>(JSON.parse(JSON.stringify([appStore.durationRow.start, appStore.durationRow.end])));
function changeTimeRange(val: Date[] | any) { function changeTimeRange(val: Date[] | any) {
dates.value = val; dates.value = val;
@@ -92,8 +87,7 @@ limitations under the License. -->
step: appStore.durationRow.step, step: appStore.durationRow.step,
utc: appStore.utc, utc: appStore.utc,
}); });
const { widget, graph, metrics, metricTypes, metricConfig, metricMode, expressions, typesOfMQE, subExpressions } = const { widget, graph, metricConfig, expressions, typesOfMQE, subExpressions } = dashboardStore.selectedGrid;
dashboardStore.selectedGrid;
const c = (metricConfig || []).map((d: any) => { const c = (metricConfig || []).map((d: any) => {
const t: any = {}; const t: any = {};
if (d.label) { if (d.label) {
@@ -107,19 +101,13 @@ limitations under the License. -->
const opt: any = { const opt: any = {
type: dashboardStore.selectedGrid.type, type: dashboardStore.selectedGrid.type,
graph: graph, graph: graph,
metricMode,
metricConfig: c, metricConfig: c,
height: dashboardStore.selectedGrid.h * 20 + 60, height: dashboardStore.selectedGrid.h * 20 + 60,
}; };
if (metricMode === MetricModes.Expression) { opt.expressions = expressions;
opt.expressions = expressions; opt.typesOfMQE = typesOfMQE;
opt.typesOfMQE = typesOfMQE; if (subExpressions && subExpressions.length) {
if (subExpressions && subExpressions.length) { opt.subExpressions = subExpressions;
opt.subExpressions = subExpressions;
}
} else {
opt.metrics = metrics;
opt.metricTypes = metricTypes;
} }
if (widget) { if (widget) {
opt.widget = { opt.widget = {
@@ -127,7 +115,10 @@ limitations under the License. -->
tips: encodeURIComponent(widget.tips || ""), tips: encodeURIComponent(widget.tips || ""),
}; };
} }
if (auto.value) { if (hasDuration.value) {
opt.auto = 0;
opt.autoPeriod = 0;
} else {
const f = RefreshOptions.filter((d: { value: string }) => d.value === freshOpt.value)[0] || {}; const f = RefreshOptions.filter((d: { value: string }) => d.value === freshOpt.value)[0] || {};
opt.auto = Number(f.value) * 60 * 1000; opt.auto = Number(f.value) * 60 * 1000;
opt.autoPeriod = period.value; opt.autoPeriod = period.value;

View File

@@ -91,7 +91,7 @@ limitations under the License. -->
position: fixed; position: fixed;
bottom: 0; bottom: 0;
right: 0; right: 0;
border-top: 1px solid #eee; border-top: 1px solid $border-color;
padding: 10px; padding: 10px;
text-align: right; text-align: right;
width: 100%; width: 100%;

View File

@@ -68,7 +68,7 @@ limitations under the License. -->
position: fixed; position: fixed;
bottom: 0; bottom: 0;
right: 0; right: 0;
border-top: 1px solid #eee; border-top: 1px solid $border-color-primary;
padding: 10px; padding: 10px;
text-align: right; text-align: right;
width: 100%; width: 100%;

View File

@@ -0,0 +1,110 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="item">
<span class="label">{{ t("tabExpressions") }}</span>
<div class="mt-10" v-for="(child, index) in widgetTabs || []" :key="index">
<span class="name">{{ child.name }}</span>
<el-input class="input" size="small" v-model="expressions[child.name]" @change="changeExpression(child.name)" />
</div>
</div>
<div class="footer">
<el-button size="small" @click="cancelConfig">
{{ t("cancel") }}
</el-button>
<el-button size="small" type="primary" @click="applyConfig">
{{ t("apply") }}
</el-button>
</div>
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { reactive, computed } from "vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import { ElMessage } from "element-plus";
import { WidgetType, ListEntity } from "@/views/dashboard/data";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const originConfig = dashboardStore.selectedGrid;
const expressions = reactive<{ [key: string]: string }>({});
const widgetTabs = computed(() =>
(dashboardStore.selectedGrid.children || []).filter((child: any) =>
child.children.find(
(item: any) =>
item.type === WidgetType.Widget &&
!(Object.keys(ListEntity).includes(item.graph.type as string) && child.children.length === 1),
),
),
);
for (const child of originConfig.children || []) {
expressions[child.name] = child.expression || "";
}
function changeExpression(name: string) {
if (expressions[name] && !expressions[name].includes("is_present")) {
ElMessage.error("Only support the is_present function");
return;
}
const children = JSON.parse(JSON.stringify(dashboardStore.selectedGrid.children || []));
for (const item of children) {
if (item.name === name) {
item.expression = expressions[name];
}
}
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, children });
}
function applyConfig() {
dashboardStore.setConfigPanel(false);
dashboardStore.setConfigs(dashboardStore.selectedGrid);
}
function cancelConfig() {
dashboardStore.selectWidget(originConfig);
dashboardStore.setConfigPanel(false);
}
</script>
<style lang="scss" scoped>
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.item {
margin-bottom: 10px;
}
.input {
width: 500px;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid $border-color-primary;
padding: 10px;
text-align: right;
width: 100%;
background-color: $theme-background;
}
.name {
width: 180px;
display: inline-block;
}
</style>

View File

@@ -89,6 +89,7 @@ limitations under the License. -->
const fontSize = ref<number>(graph.fontSize || 12); const fontSize = ref<number>(graph.fontSize || 12);
const textAlign = ref(graph.textAlign || "left"); const textAlign = ref(graph.textAlign || "left");
const Colors = [ const Colors = [
{ label: "Theme", value: "theme" },
{ {
label: "Green", label: "Green",
value: "green", value: "green",
@@ -151,7 +152,7 @@ limitations under the License. -->
position: fixed; position: fixed;
bottom: 0; bottom: 0;
right: 0; right: 0;
border-top: 1px solid #eee; border-top: 1px solid $border-color-primary;
padding: 10px; padding: 10px;
text-align: right; text-align: right;
width: 100%; width: 100%;

View File

@@ -80,7 +80,7 @@ limitations under the License. -->
position: fixed; position: fixed;
bottom: 0; bottom: 0;
right: 0; right: 0;
border-top: 1px solid #eee; border-top: 1px solid $border-color-primary;
padding: 10px; padding: 10px;
text-align: right; text-align: right;
width: 100%; width: 100%;

View File

@@ -147,7 +147,7 @@ limitations under the License. -->
position: fixed; position: fixed;
bottom: 0; bottom: 0;
right: 0; right: 0;
border-top: 1px solid #eee; border-top: 1px solid $border-color-primary;
padding: 10px; padding: 10px;
text-align: right; text-align: right;
width: 100%; width: 100%;

View File

@@ -63,7 +63,7 @@ limitations under the License. -->
position: fixed; position: fixed;
bottom: 0; bottom: 0;
right: 0; right: 0;
border-top: 1px solid #eee; border-top: 1px solid $border-color-primary;
padding: 10px; padding: 10px;
text-align: right; text-align: right;
width: 100%; width: 100%;

View File

@@ -34,11 +34,8 @@ limitations under the License. -->
...graph, ...graph,
legend: (dashboardStore.selectedGrid.graph || {}).legend, legend: (dashboardStore.selectedGrid.graph || {}).legend,
i: dashboardStore.selectedGrid.i, i: dashboardStore.selectedGrid.i,
metrics: dashboardStore.selectedGrid.metrics,
metricTypes: dashboardStore.selectedGrid.metricTypes,
metricConfig: dashboardStore.selectedGrid.metricConfig, metricConfig: dashboardStore.selectedGrid.metricConfig,
relatedTrace: dashboardStore.selectedGrid.relatedTrace, relatedTrace: dashboardStore.selectedGrid.relatedTrace,
metricMode: dashboardStore.selectedGrid.metricMode,
expressions: dashboardStore.selectedGrid.expressions || [], expressions: dashboardStore.selectedGrid.expressions || [],
typesOfMQE: dashboardStore.selectedGrid.typesOfMQE || [], typesOfMQE: dashboardStore.selectedGrid.typesOfMQE || [],
subExpressions: dashboardStore.selectedGrid.subExpressions || [], subExpressions: dashboardStore.selectedGrid.subExpressions || [],
@@ -89,7 +86,6 @@ limitations under the License. -->
import type { Option } from "@/types/app"; import type { Option } from "@/types/app";
import graphs from "../graphs"; import graphs from "../graphs";
import CustomOptions from "./widget/index"; import CustomOptions from "./widget/index";
import { MetricModes } from "../data";
export default defineComponent({ export default defineComponent({
name: "WidgetEdit", name: "WidgetEdit",
@@ -142,23 +138,6 @@ limitations under the License. -->
function applyConfig() { function applyConfig() {
dashboardStore.setConfigPanel(false); dashboardStore.setConfigPanel(false);
const { metricMode } = dashboardStore.selectedGrid;
let p = {};
if (metricMode === MetricModes.Expression) {
p = {
metrics: [],
metricTypes: [],
};
} else {
p = {
expressions: [],
typesOfMQE: [],
};
}
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
...p,
});
dashboardStore.setConfigs(dashboardStore.selectedGrid); dashboardStore.setConfigs(dashboardStore.selectedGrid);
} }
@@ -201,7 +180,7 @@ limitations under the License. -->
.graph { .graph {
position: relative; position: relative;
min-width: 1280px; min-width: 1280px;
border: 1px solid #eee; border: 1px solid $border-color-primary;
background-color: $theme-background; background-color: $theme-background;
} }
@@ -209,7 +188,7 @@ limitations under the License. -->
height: 25px; height: 25px;
line-height: 25px; line-height: 25px;
text-align: center; text-align: center;
background-color: aliceblue; background-color: var(--sw-config-header);
font-size: $font-size-smaller; font-size: $font-size-smaller;
position: relative; position: relative;
} }
@@ -249,7 +228,7 @@ limitations under the License. -->
position: fixed; position: fixed;
bottom: 0; bottom: 0;
right: 0; right: 0;
border-top: 1px solid #eee; border-top: 1px solid $border-color-primary;
padding: 10px; padding: 10px;
text-align: right; text-align: right;
width: 100%; width: 100%;

View File

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

View File

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

View File

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

View File

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

View File

@@ -75,7 +75,7 @@ limitations under the License. -->
.header { .header {
padding: 10px; padding: 10px;
font-size: $font-size-smaller; font-size: $font-size-smaller;
border-bottom: 1px solid #dcdfe6; border-bottom: 1px solid $border-color;
} }
.tools { .tools {
@@ -87,7 +87,7 @@ limitations under the License. -->
&:hover { &:hover {
color: $active-color; color: $active-color;
background-color: #eee; background-color: $popper-hover-bg-color;
} }
} }
@@ -95,6 +95,6 @@ limitations under the License. -->
font-weight: bold; font-weight: bold;
line-height: 40px; line-height: 40px;
padding: 0 10px; padding: 0 10px;
border-bottom: 1px solid #dcdfe6; border-bottom: 1px solid $border-color;
} }
</style> </style>

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