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
strategy:
matrix:
node-version: [14.x, 16.x, 18.x]
node-version: [16.x, 18.x, 20.x]
steps:
- uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node-version }}

View File

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

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"
},
"dependencies": {
"axios": "^0.24.0",
"axios": "^1.6.0",
"d3": "^7.3.0",
"d3-flame-graph": "^4.1.3",
"d3-tip": "^0.9.1",
"echarts": "^5.2.2",
"element-plus": "^2.1.0",
"element-plus": "^2.2.5",
"lodash": "^4.17.21",
"monaco-editor": "^0.34.1",
"pinia": "^2.0.28",
@@ -52,7 +52,7 @@
"@vue/test-utils": "^2.2.6",
"@vue/tsconfig": "^0.1.3",
"@vueuse/core": "^9.6.0",
"cypress": "^12.0.2",
"cypress": "^13.3.2",
"eslint": "^8.22.0",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-vue": "^9.3.0",
@@ -74,7 +74,7 @@
"typescript": "~4.7.4",
"unplugin-auto-import": "^0.7.0",
"unplugin-vue-components": "^0.19.2",
"vite": "^4.0.5",
"vite": "^4.5.3",
"vite-plugin-monaco-editor": "^1.1.0",
"vite-plugin-svg-icons": "^2.0.1",
"vitest": "^0.25.6",

View File

@@ -27,10 +27,11 @@ limitations under the License. -->
}
}, 500);
</script>
<style>
<style lang="scss">
#app {
color: #2c3e50;
color: $font-color;
height: 100%;
overflow: hidden;
background-color: $layout-background;
}
</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.
See the License for the specific language governing permissions and
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.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M856.32 428.064a32 32 0 0 0-32 32v163.328H372.48c-0.896 0-1.664 0.448-2.56 0.512v-177.696h244.48a32 32 0 1 0 0-64H130.56c-0.896 0-1.664 0.448-2.56 0.512V231.68h488.16a32 32 0 1 0 0-64H96a32 32 0 0 0-32 32v701.824a32 32 0 0 0 32 32h760.32a32 32 0 0 0 32-32V460.064a32 32 0 0 0-32-32zM128 445.728c0.896 0.064 1.664 0.512 2.56 0.512h175.36v423.264H128V445.728z m241.92 423.776v-182.624c0.896 0.064 1.664 0.512 2.56 0.512h451.84v182.08h-454.4zM960 174.656h-61.376V113.28a32 32 0 1 0-64 0v61.344H752.64a32 32 0 1 0 0 64h81.984v81.984a32 32 0 1 0 64 0V238.656H960a32 32 0 1 0 0-64z" fill="#2c2c2c"></path></svg>
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M856.32 428.064a32 32 0 0 0-32 32v163.328H372.48c-0.896 0-1.664 0.448-2.56 0.512v-177.696h244.48a32 32 0 1 0 0-64H130.56c-0.896 0-1.664 0.448-2.56 0.512V231.68h488.16a32 32 0 1 0 0-64H96a32 32 0 0 0-32 32v701.824a32 32 0 0 0 32 32h760.32a32 32 0 0 0 32-32V460.064a32 32 0 0 0-32-32zM128 445.728c0.896 0.064 1.664 0.512 2.56 0.512h175.36v423.264H128V445.728z m241.92 423.776v-182.624c0.896 0.064 1.664 0.512 2.56 0.512h451.84v182.08h-454.4zM960 174.656h-61.376V113.28a32 32 0 1 0-64 0v61.344H752.64a32 32 0 1 0 0 64h81.984v81.984a32 32 0 1 0 64 0V238.656H960a32 32 0 1 0 0-64z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

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

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

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

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

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

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

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

After

Width:  |  Height:  |  Size: 2.2 KiB

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,32 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export enum MetricQueryTypes {
ReadMetricsValue = "readMetricsValue",
ReadMetricsValues = "readMetricsValues",
SortMetrics = "sortMetrics",
ReadLabeledMetricsValues = "readLabeledMetricsValues",
READHEATMAP = "readHeatMap",
ReadSampledRecords = "readSampledRecords",
ReadRecords = "readRecords",
ReadNullableMetricsValue = "readNullableMetricsValue",
}
export enum Calculations {
Percentage = "percentage",
ByteToKB = "byteToKB",
ByteToMB = "byteToMB",
ByteToGB = "byteToGB",
Apdex = "apdex",
ConvertSeconds = "convertSeconds",
ConvertMilliseconds = "convertMilliseconds",
MsToS = "msTos",
Average = "average",
PercentageAvg = "percentageAvg",
ApdexAvg = "apdexAvg",
SecondToDay = "secondToDay",
NanosecondToMillisecond = "nanosecondToMillisecond",
}
export enum sizeEnum {
XS = "XS",
SM = "SM",
@@ -68,50 +43,6 @@ screenMap.set(sizeEnum.XL, screenEnum.XL);
screenMap.set(sizeEnum.XXL, screenEnum.XXL);
export const RespFields: Indexable = {
readMetricsValues: `{
label
values {
values {value isEmptyValue}
}
}`,
readMetricsValue: ``,
readNullableMetricsValue: `{
value
isEmptyValue
}`,
sortMetrics: `{
name
id
value
refId
}`,
readLabeledMetricsValues: `{
label
values {
values {value isEmptyValue}
}
}`,
readHeatMap: `{
values {
id
values
}
buckets {
min
max
}
}`,
readSampledRecords: `{
name
value
refId
}`,
readRecords: `{
id
name
value
refId
}`,
execExpression: `{
type
results {
@@ -130,3 +61,43 @@ export const RespFields: Indexable = {
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 { useDashboardStore } from "@/store/modules/dashboard";
import type { LayoutConfig } from "@/types/dashboard";
import { ConfigFieldTypes } from "@/views/dashboard/data";
export default function getDashboard(param?: { name: string; layer: string; entity: string }) {
export default function getDashboard(param?: { name?: string; layer: string; entity: string }, t?: string) {
const type = t || ConfigFieldTypes.NAME; // "NAME" or "ISDEFAULT"
const dashboardStore = useDashboardStore();
const opt = param || dashboardStore.currentDashboard;
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
const dashboard = list.find(
let dashboard: Recordable;
if (type === ConfigFieldTypes.NAME) {
dashboard = list.find(
(d: { name: string; layer: string; entity: string }) =>
d.name === opt.name && d.entity === opt.entity && d.layer === opt.layer,
);
} else {
dashboard = list.find(
(d: { name: string; layer: string; entity: string; isDefault: boolean }) =>
d.isDefault && d.entity === opt.entity && d.layer === opt.layer,
);
}
const all = dashboardStore.layout;
const widgets: LayoutConfig[] = [];
for (const item of all) {
@@ -52,6 +62,9 @@ export default function getDashboard(param?: { name: string; layer: string; enti
filters,
};
dashboardStore.setWidget(item);
if (widget.id === sourceId) {
return;
}
const targetTabIndex = (widget.id || "").split("-");
const sourceTabindex = (sourceId || "").split("-") || [];
let container: Nullable<Element>;

View File

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

View File

@@ -112,27 +112,19 @@ export async function useExpressionsQueryProcessor(config: Indexable) {
tips.push(obj.error);
typesOfMQE.push(type);
if (!obj.error) {
if (type === ExpressionResultType.TIME_SERIES_VALUES) {
if (results.length === 1) {
const label = results[0].metric && results[0].metric.labels[0] && results[0].metric.labels[0].value;
source[c.label || label || name] = results[0].values.map((d: { value: unknown }) => d.value) || [];
} else {
const labels = (c.label || "").split(",").map((item: string) => item.replace(/^\s*|\s*$/g, ""));
if ([ExpressionResultType.SINGLE_VALUE, ExpressionResultType.TIME_SERIES_VALUES].includes(type)) {
for (const item of results) {
const label =
item.metric &&
item.metric.labels.map((d: { key: string; value: string }) => `${d.key}=${d.value}`).join(",");
const values = item.values.map((d: { value: unknown }) => d.value) || [];
const index = item.metric.labels[0].value;
const indexNum = labels.findIndex((_, i: number) => i === Number(index));
if (labels[indexNum] && indexNum > -1) {
source[labels[indexNum]] = values;
if (results.length === 1) {
source[label || c.label || name] = values;
} else {
source[index] = values;
source[label] = values;
}
}
}
}
if (type === ExpressionResultType.SINGLE_VALUE) {
source[c.label || name] = (results[0].values[0] || {}).value;
}
if (([ExpressionResultType.RECORD_LIST, ExpressionResultType.SORTED_LIST] as string[]).includes(type)) {
source[name] = results[0].values;
}
@@ -141,6 +133,7 @@ export async function useExpressionsQueryProcessor(config: Indexable) {
return { source, tips, typesOfMQE };
}
const params = await expressionsGraphqlPods();
if (!params) {
return { source: {}, tips: [], typesOfMQE: [] };
@@ -152,9 +145,14 @@ export async function useExpressionsQueryProcessor(config: Indexable) {
ElMessage.error(json.errors);
return { source: {}, tips: [], typesOfMQE: [] };
}
try {
const data = expressionsSource(json);
return data;
} catch (error) {
console.error(error);
return { source: {}, tips: [], typesOfMQE: [] };
}
}
export async function useExpressionsQueryPodsMetrics(
@@ -301,6 +299,7 @@ export async function useExpressionsQueryPodsMetrics(
return { data, names, subNames, metricConfigArr, metricTypesArr, expressionsTips, subExpressionsTips };
}
const dashboardStore = useDashboardStore();
const params = await expressionsGraphqlPods();
const json = await dashboardStore.fetchMetricValue(params);
@@ -330,10 +329,14 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
let serviceInstanceName;
let destServiceInstanceName;
let destEndpointName;
let normal = false;
let destNormal;
if (d.sourceObj && d.targetObj) {
// instances = Calls
serviceName = d.sourceObj.serviceName || d.sourceObj.name;
destServiceName = d.targetObj.serviceName || d.targetObj.name;
normal = d.sourceObj.normal || d.sourceObj.isReal || false;
destNormal = d.targetObj.normal || d.targetObj.isReal || false;
if (EntityType[4].value === dashboardStore.entity) {
serviceInstanceName = d.sourceObj.name;
destServiceInstanceName = d.targetObj.name;
@@ -345,6 +348,10 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
} else {
// instances = Nodes
serviceName = d.serviceName || d.name;
normal = d.normal || d.isReal || false;
if (EntityType[3].value === dashboardStore.entity) {
serviceInstanceName = d.name;
}
if (EntityType[4].value === dashboardStore.entity) {
serviceInstanceName = d.name;
}
@@ -354,11 +361,11 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
}
const entity = {
serviceName,
normal: true,
normal,
serviceInstanceName,
endpointName,
destServiceName,
destNormal: destServiceName ? true : undefined,
destNormal: destServiceName ? destNormal : undefined,
destServiceInstanceName,
destEndpointName,
};
@@ -389,7 +396,10 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
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 { 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) {
let isRight = false;
@@ -96,37 +99,11 @@ export default function useLegendProcess(legend?: LegendOptions) {
return { source, headers };
}
function chartColors(keys: string[]) {
let color: string[] = [];
switch (keys.length) {
case 2:
color = ["#FF6A84", "#a0b1e6"];
break;
case 1:
color = ["#3f96e3"];
break;
default:
color = [
"#30A4EB",
"#45BFC0",
"#FFCC55",
"#FF6A84",
"#a0a7e6",
"#c23531",
"#2f4554",
"#61a0a8",
"#d48265",
"#91c7ae",
"#749f83",
"#ca8622",
"#bda29a",
"#6e7074",
"#546570",
"#c4ccd3",
];
break;
}
return color;
function chartColors() {
const appStore = useAppStoreWithOut();
const list = appStore.theme === Themes.Dark ? DarkChartColors : LightChartColors;
return list;
}
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>
.app-main {
height: calc(100% - 40px);
background: #f7f9fa;
background: $layout-background;
overflow: auto;
}
</style>

View File

@@ -48,11 +48,20 @@ limitations under the License. -->
@input="changeTimeRange"
/>
<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">
<Icon iconName="retry" :loading="appStore.autoRefresh" class="middle" />
</span>
<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>
<span>
<Icon iconName="info_outline" size="middle" />
@@ -64,17 +73,18 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
import timeFormat from "@/utils/timeFormat";
import { Themes } from "@/constants/data";
import router from "@/router";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useDashboardStore } from "@/store/modules/dashboard";
import { ElMessage } from "element-plus";
import { MetricCatalog } from "@/views/dashboard/data";
import type { DashboardItem } from "@/types/dashboard";
import router from "@/router";
import { ArrowRight } from "@element-plus/icons-vue";
import timeFormat from "@/utils/timeFormat";
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 */
const { t, te } = useI18n();
@@ -84,11 +94,67 @@ limitations under the License. -->
const pathNames = ref<{ path?: string; name: string; selected: boolean }[][]>([]);
const timeRange = ref<number>(0);
const pageTitle = ref<string>("");
const theme = ref<boolean>(true);
const themeSwitchRef = ref<HTMLElement>();
const savedTheme = window.localStorage.getItem("theme-is-dark");
if (savedTheme === "false") {
theme.value = false;
}
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();
getVersion();
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[]) {
return list.find((d: any) => d.selected) || {};
}
@@ -287,21 +353,16 @@ limitations under the License. -->
padding: 5px;
text-align: left;
justify-content: space-between;
background-color: #fafbfc;
border-bottom: 1px solid #dfe4e8;
color: #222;
background-color: $theme-background;
border-bottom: 1px solid $border-color;
color: $font-color;
font-size: $font-size-smaller;
}
.nav-bar.dark {
background-color: #333840;
border-bottom: 1px solid #252a2f;
color: #fafbfc;
}
.title {
font-size: $font-size-normal;
font-weight: 500;
padding-top: 5px;
}
.nav-tabs {

View File

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

View File

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

View File

@@ -30,6 +30,11 @@ const titles = {
general_service_virtual_mq: "Virtual MQ",
general_service_virtual_mq_desc:
"Observe the virtual message queue servers which are conjectured by language agents through various plugins.",
// Workflow Scheduler
workflow_scheduler: "Workflow Scheduler",
workflow_scheduler_desc: "Provide monitoring for workflow scheduling systems.",
workflow_scheduler_airflow: "Airflow",
workflow_scheduler_airflow_desc: "Observe tasks through telemetry data collected from Apache Airflow.",
// Service Mesh
service_mesh: "Service Mesh",
service_mesh_desc:
@@ -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_data_plane: "Data Plane",
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_desc:
@@ -82,6 +80,8 @@ const titles = {
gateway: "Gateway",
gateway_desc:
"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_desc: "Provide APISIX monitoring through OpenTelemetry's Prometheus Receiver.",
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.",
mq_rabbitmq: "RabbitMQ",
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_desc:

View File

@@ -30,6 +30,12 @@ const titles = {
general_service_virtual_mq: "MQ virtual",
general_service_virtual_mq_desc:
"Observe the virtual message queue servers which are conjectured by language agents through various plugins.",
// Workflow Scheduler
workflow_scheduler: "Flujo de trabajo",
workflow_scheduler_desc: "Proporcionar monitoreo para sistemas de programación de flujos de trabajo.",
workflow_scheduler_airflow: "Airflow",
workflow_scheduler_airflow_desc:
"Observando tareas a través de los datos de telemetría recopilados desde Apache Airflow.",
// Service Mesh
service_mesh: "Malla de Servicios",
service_mesh_desc:
@@ -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_data_plane: "Plano de Datos",
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_desc:
@@ -82,6 +81,8 @@ const titles = {
gateway: "Puerta",
gateway_desc:
"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_desc: "Provide APISIX monitoring through OpenTelemetry's Prometheus Receiver.",
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.",
mq_rabbitmq: "RabbitMQ",
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_desc:

View File

@@ -26,6 +26,11 @@ const titles = {
general_service_virtual_cache_desc: "观察语言代理通过各种插件推测的虚拟缓存服务器。",
general_service_virtual_mq: "虚拟消息队列",
general_service_virtual_mq_desc: "观察语言代理通过各种插件推测的虚拟消息队列服务器。",
// Workflow Scheduler
workflow_scheduler: "工作流调度",
workflow_scheduler_desc: "提供工作流调度系统监控。",
workflow_scheduler_airflow: "Airflow",
workflow_scheduler_airflow_desc: "通过从Apache Airflow收集的遥测数据观察任务。",
// Service Mesh
service_mesh: "服务网格",
service_mesh_desc: "服务网格Istio通过分布式或微服务架构解决了开发人员和运营商面临的挑战。",
@@ -35,12 +40,6 @@ const titles = {
service_mesh_control_plane_desc: "通过Istio的自我监控指标提供对其行为的监控。",
service_mesh_data_plane: "数据平面",
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_desc: "Kubernetes是一个开源的容器编排系统用于自动化软件部署、扩展和管理。",
@@ -72,6 +71,8 @@ const titles = {
// Gateway
gateway: "网关",
gateway_desc: "API网关是位于客户端和后端服务集合之间的API管理工具。",
gateway_nginx: "Nginx",
gateway_nginx_desc: "通过OpenTelemetry的Prometheus接收器提供Nginx监控。",
gateway_apisix: "APISIX",
gateway_apisix_desc: "通过OpenTelemetry的Prometheus接收器提供APISIX监控。",
gateway_aws_api_gateway: "AWS API Gateway",
@@ -96,6 +97,12 @@ const titles = {
mq_desc: "消息队列是无服务器和微服务架构中使用的异步服务对服务通信的一种形式。",
mq_rabbitmq: "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_desc: "自观察性为运行SkyWalking生态系统中的组件和服务器提供了可观察性。",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,6 +16,7 @@
*/
import type { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
import Marketplace from "@/views/Marketplace.vue";
export const routesMarketplace: Array<RouteRecordRaw> = [
{
@@ -33,7 +34,7 @@ export const routesMarketplace: Array<RouteRecordRaw> = [
{
path: "/marketplace",
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 Layout from "@/layout/Index.vue";
import Settings from "@/views/Settings.vue";
export const routesSettings: Array<RouteRecordRaw> = [
{
@@ -33,7 +34,7 @@ export const routesSettings: Array<RouteRecordRaw> = [
{
path: "/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
* limitations under the License.
*/
import { WidgetType } from "@/views/dashboard/data";
export const NewControl = {
x: 0,
y: 0,
w: 24,
h: 12,
i: "0",
type: "Widget",
type: WidgetType.Widget,
};
export const TextConfig = {
fontColor: "white",
@@ -39,15 +41,15 @@ export const TimeRangeConfig = {
};
export const ControlsTypes = [
"Trace",
"Profile",
"Log",
"DemandLog",
"Ebpf",
"NetworkProfiling",
"ThirdPartyApp",
"ContinuousProfiling",
"TaskTimeline",
WidgetType.Trace,
WidgetType.Profile,
WidgetType.Log,
WidgetType.DemandLog,
WidgetType.Ebpf,
WidgetType.NetworkProfiling,
WidgetType.ThirdPartyApp,
WidgetType.ContinuousProfiling,
WidgetType.TaskTimeline,
];
export enum EBPFProfilingTriggerType {
FIXED_TIME = "FIXED_TIME",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -123,96 +123,3 @@ code,
pre {
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;
title: string;
activate: boolean;
name?: string;
name: string;
path?: string;
notShow?: boolean;
id?: string;

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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