88 Commits

Author SHA1 Message Date
Fine0830
1f98619a5b fix: update attached event's details (#200) 2022-12-02 15:03:29 +08:00
Fine0830
aab44626da fix: update attached event details (#199) 2022-12-01 13:06:46 +08:00
Fine0830
0ff5d4d6bb fix: update attached event‘s occurrence date (#198) 2022-12-01 10:47:30 +08:00
Fine0830
611731d6d0 fix: update attached event details (#197) 2022-11-30 16:48:49 +08:00
Fine0830
221751f034 fix: optimize metrics association (#196) 2022-11-29 18:47:53 +08:00
Fine0830
d4dde7e73b fix: optimize config UI for related trace and metrics (#195) 2022-11-29 14:24:15 +08:00
Fine0830
5be106fc4f fix: add ProcessRelation to entity types (#194) 2022-11-28 22:49:23 +08:00
Fine0830
d8f91bbdf3 feat: Implement creating tasks UI for network profiling widget (#193) 2022-11-28 17:57:23 +08:00
Fine0830
23e9742946 feat: enhance associating metrics with traces by refId (#192) 2022-11-25 17:33:51 +08:00
Fine0830
7a1c83b5fb fix: update metric processor for the readRecords and remove readSampledRecords from metrics selector (#191) 2022-11-24 18:51:21 +08:00
Fine0830
7d802d490e feat: visualize attached events on the trace widget (#190) 2022-11-24 11:19:25 +08:00
Fine0830
da1db8def6 fix: update query conditions for metrics related traces (#189) 2022-11-22 22:39:22 +08:00
Fine0830
2230d05508 fix: optimize metrics related trace (#188) 2022-11-21 14:28:37 +08:00
WD
e8d909792d chore: fix typo (#187) 2022-11-21 10:07:01 +08:00
Fine0830
dc842609ba fix: update condition logic for trace tree data (#186) 2022-11-19 16:53:15 +08:00
drgnchan
670bef1d69 fix typo (#185) 2022-11-18 11:55:52 +08:00
Fine0830
882828b04a feat: enhance tags component to search tags with the input value (#184) 2022-11-17 17:57:23 +08:00
WD
ed6fb0448b feat: solve the problem of floating loading (#183) 2022-11-17 16:07:18 +08:00
Fine0830
a0fc879eb1 feat: enhance graph legend for the single metric (#182) 2022-11-10 15:13:17 +08:00
Fine0830
b37d65eaac feat: enhance the legend of metrics graph widget with the summary table (#181) 2022-11-10 14:55:19 +08:00
heihei180
fd46211a37 add apache eventMesh logo file (#180) 2022-11-06 15:12:30 +08:00
Fine0830
ae0b8c056d fix trace profiling widget, select the first span by default (#179) 2022-11-04 20:31:46 +08:00
Fine0830
4b88d8bbb3 fix: reset tag keys list and duration condition (#178) 2022-10-31 12:05:25 +08:00
Fine0830
09051e916b feat: support labeled value on the service/instance/endpoint list widgets (#177) 2022-10-31 10:27:37 +08:00
Fine0830
e597f91448 remove unuse icon (#176) 2022-10-26 21:53:27 +08:00
Fine0830
4232161d36 fix: set selector props and update configuration panel styles (#175) 2022-10-25 16:48:49 +08:00
Fine0830
eda44db0cd feat: associate metrics with trace widget on dashboards (#174) 2022-10-25 11:36:49 +08:00
pg.yang
78f0096c00 add menu for virtual mq (#173) 2022-10-20 20:36:28 +08:00
Fine0830
5e161f17c2 feat: add readRecords to metric types (#172) 2022-10-17 21:55:14 +08:00
WD
77d189cdfb fix: added name verification to avoid creating blank dashboard name (#171) 2022-10-17 21:25:59 +08:00
Fine0830
9f57e35119 revert logs on trace widget (#170) 2022-10-14 11:28:10 +08:00
dependabot[bot]
2bf90d6a6d build(deps): bump d3-color from 3.0.1 to 3.1.0 (#166) 2022-10-01 10:33:48 +08:00
Fine0830
0f4319499a fix: query logs with the specific service ID (#165) 2022-09-30 10:25:05 +08:00
pg.yang
5bb58a00cd feat: add gateway,apisix menu (#163) 2022-09-27 10:36:15 +08:00
吴晟 Wu Sheng
d50e9fc261 Remove commented codes (#162) 2022-09-21 12:34:06 +08:00
Fine0830
b235929c77 feat: enhance menu configuration to make it easier to change (#161) 2022-09-20 16:24:42 +08:00
Fine0830
4561e2e374 fix: remove All from the endpoints selector from profiling 2022-09-19 14:05:09 +08:00
pg.yang
214b34ddfd feat: add virtual cache dashboard (#159) 2022-09-19 10:02:22 +08:00
Fine0830
26817e9f92 feat: enhance the process topology graph to support dragging nodes (#158) 2022-09-15 17:18:39 +08:00
Fine0830
9ed0121fd0 fix: update styles for an adaptive height (#157) 2022-09-13 16:31:30 +08:00
Fine0830
0d63d538c3 fix: set up a new time range after clicking the refresh button (#156) 2022-09-08 22:41:02 +08:00
Fine0830
5da441ff9a fix: polish the endpoint list graph (#155) 2022-09-08 21:46:37 +08:00
kezhenxu94
49bc349064 Keep package.json and package-lock.json in sync (#154) 2022-09-06 14:31:52 +08:00
Lv Lifeng
61a4d2f759 Add impala icon 4 impala jdbc plugin (#153) 2022-09-03 19:45:54 +08:00
云泥
0b4e738699 fix: tab active incorrectly, when click tab space (#152) 2022-09-01 15:49:15 +08:00
Fine0830
b88356ba46 fix: polish the network profiling widget, bugfix (#151) 2022-08-30 13:20:45 +08:00
Fine0830
d8889f1787 fix: set configurations for process topology (#150) 2022-08-29 17:32:49 +08:00
Fine0830
1a989a1434 fix widget name (#149) 2022-08-29 17:02:42 +08:00
Fine0830
82b348a766 feat: add a calculation to convert nanoseconds to milliseconds (#148) 2022-08-26 16:26:25 +08:00
Fine0830
42b20660e4 fix: set selector parameters for metrics, optimize the process topology (#147) 2022-08-26 12:42:12 +08:00
Fine0830
cb3aa940b3 feat: create markers on process topology calls (#146) 2022-08-25 12:03:13 +08:00
Fine0830
87a5553e6d fix: update process widget and query process metrics (#145) 2022-08-24 17:39:40 +08:00
dependabot[bot]
e17562a766 build(deps): bump moment and @vue/cli-plugin-e2e-cypress (#144) 2022-08-23 13:56:13 +08:00
pg.yang
fdfdaab47b Add Nats icon for Java plugin (#142)
Co-authored-by: 吴晟 Wu Sheng <wu.sheng@foxmail.com>
2022-08-23 13:48:54 +08:00
Fine0830
a4fc5192ac feat: Implement the network profiling widget (#132) 2022-08-23 13:41:05 +08:00
yswdqz
ffabc7c7a7 Fix a typo about PostgreSQL Monitoring. (#141) 2022-08-22 23:21:27 +08:00
Fine0830
2fd5fb9b1e add sub menu for PostgreSQL layer (#140) 2022-08-22 21:48:02 +08:00
Fine0830
adb457d660 fix: pick calendar with a wrong time range and set a unique value for dashboard grid key (#139) 2022-08-18 16:29:36 +08:00
Fine0830
9c0bb988e6 feat: support the process dashboard and create the time range text widget (#138) 2022-08-15 16:49:00 +08:00
pg.yang
973b51e9ca Add Micronaut icon for Java plugin (#137) 2022-08-10 17:00:22 +08:00
Fine0830
f5bcd5da2e feat: add a calculation to convert seconds to days (#135) 2022-08-08 10:26:36 +08:00
Fine0830
732b834749 feat: add the MYSQL layer and update layer routers (#134) 2022-08-07 17:16:44 +08:00
Fine0830
4b43196bc2 fix query order for trace list (#133) 2022-08-05 17:02:29 +08:00
Fine0830
b01565b2b8 fix: set the value(SECOND) of the step filed for queries (#131) 2022-07-29 20:54:57 +08:00
Fine0830
3b3e790dd9 feat: event widget associates with trace and log widget (#130) 2022-07-29 16:34:36 +08:00
Fine0830
dc8e4bf273 fix: the log details don't display when the log fields don't have value (#129) 2022-07-27 19:31:17 +08:00
Fine0830
2ba3c67d31 feat: the log widget and the trace widget associate with each other, remove log tables on the trace widget (#128) 2022-07-27 16:24:34 +08:00
Fine0830
673b1a41a8 refactor: update the tags component (#127) 2022-07-25 17:34:05 +08:00
Fine0830
c7079ea17c fix short time range (#125) 2022-07-21 10:06:51 +08:00
Fine0830
017f5bf709 fix errors (#124) 2022-07-20 19:42:41 +08:00
Fine0830
bec86e80fd fix: update dashboard list with using the search box (#123) 2022-07-20 16:53:01 +08:00
Fine0830
ec7a8bbfa9 fix: update event associations with the duration step (#122) 2022-07-19 14:11:41 +08:00
Fine0830
4e022ff29a fix short time range (#121) 2022-07-19 12:47:03 +08:00
Fine0830
42ead4a572 feat: Enhance associations for the Event widget (#120) 2022-07-19 11:57:26 +08:00
Fine0830
e144b43267 fix: update widget name rules and the association selector options (#119) 2022-07-14 16:40:06 +08:00
Fine0830
b5c07e023b fix view mode (#118) 2022-07-13 10:34:30 +08:00
horochx
04d109d0e3 fix: hide the copy button when db.statement is empty (#117) 2022-07-11 14:53:03 +08:00
Fine0830
024cd1195d fix tag dropdown (#116) 2022-07-11 10:22:09 +08:00
Fine0830
7fbd6170de feat: Implement an association between widgets(line, bar, area graphs) with time (#115) 2022-07-08 16:17:17 +08:00
jiang1997
3ff3d5d1cd Add Python Bottle Plugin Logo (#114) 2022-07-05 18:43:32 +08:00
Fine0830
2230702d97 feat: Implement the Event widget (#112) 2022-06-22 19:11:02 +08:00
horochx
9ad9362935 fix: SpanDetail text overlap (#113) 2022-06-22 18:54:31 +08:00
Fine0830
2be0a84d48 fix: optimize widgets (#111) 2022-06-16 15:19:17 +08:00
Fine0830
4c762a2458 fix: optimize widgets (#110) 2022-06-15 19:24:07 +08:00
drgnchan
7813c92673 fix:clear interval fail when close autoRefresh (#108) 2022-06-15 10:06:53 +08:00
Fine0830
f12d7899e4 fix router (#109) 2022-06-14 22:23:23 +08:00
Fine0830
b697fe4713 feat: set a url parameter to activate tab index (#107) 2022-06-14 17:01:11 +08:00
Fine0830
0828f8a7aa fix: update query conditions for the browser logs (#106) 2022-06-14 11:26:53 +08:00
191 changed files with 9578 additions and 2015 deletions

View File

@@ -44,9 +44,9 @@ jobs:
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- name: npm install, lint, build, and test - name: npm ci, lint, build, and test
run: | run: |
npm install npm ci
npm run lint npm run lint
npm run build --if-present npm run build --if-present
npm run test:unit npm run test:unit

1577
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "skywalking-booster-ui", "name": "skywalking-booster-ui",
"version": "9.1.0", "version": "9.3.0",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
@@ -19,12 +19,12 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"monaco-editor": "^0.27.0", "monaco-editor": "^0.27.0",
"pinia": "^2.0.5", "pinia": "^2.0.5",
"vis-timeline": "^7.5.1",
"vue": "^3.0.0", "vue": "^3.0.0",
"vue-grid-layout": "^3.0.0-beta1", "vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.1.9", "vue-i18n": "^9.1.9",
"vue-router": "^4.0.0-0", "vue-router": "^4.0.0-0",
"vue-types": "^4.1.1", "vue-types": "^4.1.1"
"vuex": "^4.0.0-0"
}, },
"devDependencies": { "devDependencies": {
"@types/d3": "^7.1.0", "@types/d3": "^7.1.0",
@@ -36,7 +36,7 @@
"@typescript-eslint/eslint-plugin": "^4.18.0", "@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0", "@typescript-eslint/parser": "^4.18.0",
"@vue/cli-plugin-babel": "~4.5.0", "@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-e2e-cypress": "~4.5.0", "@vue/cli-plugin-e2e-cypress": "~5.0.8",
"@vue/cli-plugin-eslint": "~4.5.0", "@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0", "@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0", "@vue/cli-plugin-typescript": "~4.5.0",

View File

@@ -19,7 +19,7 @@ limitations under the License. -->
#app { #app {
color: #2c3e50; color: #2c3e50;
height: 100%; height: 100%;
overflow: auto; overflow: hidden;
min-width: 1024px; min-width: 1024px;
} }
</style> </style>

View File

@@ -0,0 +1,15 @@
<!-- 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="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>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,15 @@
<!-- 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="1667899293763" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4705" width="16" height="16"><path d="M512 512m-368 0a368 368 0 1 0 736 0 368 368 0 1 0-736 0Z" p-id="4706"></path></svg>

After

Width:  |  Height:  |  Size: 1001 B

View File

@@ -0,0 +1,16 @@
<!-- 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="1666624449554" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2649" width="48" height="48"><path d="M381.482667 673.877333a90.389333 90.389333 0 0 1 85.226666 60.245334H853.333333v64H465.28a90.389333 90.389333 0 0 1-167.573333 0H170.666667v-64h125.610666a90.389333 90.389333 0 0 1 85.205334-60.245334z m0 64a26.346667 26.346667 0 1 0 0 52.693334 26.346667 26.346667 0 0 0 0-52.693334z m261.034666-304.938666a90.389333 90.389333 0 0 1 85.205334 60.245333H853.333333v64h-127.04a90.389333 90.389333 0 0 1-167.573333 0H170.666667v-64h386.624a90.389333 90.389333 0 0 1 85.226666-60.245333z m0 64a26.346667 26.346667 0 1 0 0 52.693333 26.346667 26.346667 0 0 0 0-52.693333zM381.482667 192a90.389333 90.389333 0 0 1 85.226666 60.224H853.333333v64H465.28a90.389333 90.389333 0 0 1-167.573333 0H170.666667v-64h125.610666A90.389333 90.389333 0 0 1 381.482667 192z m0 64a26.346667 26.346667 0 1 0 0 52.693333 26.346667 26.346667 0 0 0 0-52.693333z" p-id="2650"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -12,4 +12,4 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<svg t="1648717513168" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15451" width="48" height="48"><path d="M810.666667 213.333333v597.333334H213.333333V213.333333h597.333334m85.333333-85.333333H128v768h768V128z m-512 170.666667h-85.333333v85.333333h85.333333z m170.666667 0h-85.333334v85.333333h85.333334z m-170.666667 170.666666h-85.333333v85.333334h85.333333z m170.666667 0h-85.333334v85.333334h85.333334z m-170.666667 170.666667h-85.333333v85.333333h85.333333z m170.666667 0h-85.333334v85.333333h85.333334z m170.666666-341.333333h-85.333333v85.333333h85.333333z m0 170.666666h-85.333333v85.333334h85.333333z m0 170.666667h-85.333333v85.333333h85.333333z" p-id="15452" fill="#515151"></path></svg> <svg t="1648717513168" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15451" width="48" height="48"><path d="M810.666667 213.333333v597.333334H213.333333V213.333333h597.333334m85.333333-85.333333H128v768h768V128z m-512 170.666667h-85.333333v85.333333h85.333333z m170.666667 0h-85.333334v85.333333h85.333334z m-170.666667 170.666666h-85.333333v85.333334h85.333333z m170.666667 0h-85.333334v85.333334h85.333334z m-170.666667 170.666667h-85.333333v85.333333h85.333333z m170.666667 0h-85.333334v85.333333h85.333334z m170.666666-341.333333h-85.333333v85.333333h85.333333z m0 170.666666h-85.333333v85.333334h85.333333z m0 170.666667h-85.333333v85.333333h85.333333z" p-id="15452"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

15
src/assets/icons/copy.svg Normal file
View File

@@ -0,0 +1,15 @@
<!-- 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="1664265269855" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4109" width="48" height="48"><path d="M866.461538 39.384615H354.461538c-43.323077 0-78.769231 35.446154-78.76923 78.769231v39.384616h472.615384c43.323077 0 78.769231 35.446154 78.769231 78.76923v551.384616h39.384615c43.323077 0 78.769231-35.446154 78.769231-78.769231V118.153846c0-43.323077-35.446154-78.769231-78.769231-78.769231z m-118.153846 275.692308c0-43.323077-35.446154-78.769231-78.76923-78.769231H157.538462c-43.323077 0-78.769231 35.446154-78.769231 78.769231v590.769231c0 43.323077 35.446154 78.769231 78.769231 78.769231h512c43.323077 0 78.769231-35.446154 78.76923-78.769231V315.076923z m-354.461538 137.846154c0 11.815385-7.876923 19.692308-19.692308 19.692308h-157.538461c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h157.538461c11.815385 0 19.692308 7.876923 19.692308 19.692308v39.384615z m157.538461 315.076923c0 11.815385-7.876923 19.692308-19.692307 19.692308H216.615385c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h315.076923c11.815385 0 19.692308 7.876923 19.692307 19.692308v39.384615z m78.769231-157.538462c0 11.815385-7.876923 19.692308-19.692308 19.692308H216.615385c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h393.846153c11.815385 0 19.692308 7.876923 19.692308 19.692308v39.384615z" p-id="4110"></path></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,15 @@
<!-- 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="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>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,15 @@
<!-- 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="1664244255409" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2266" width="48" height="48"><path d="M523.776 430.592l-153.088 88.576 153.088 88.576 152.576-88.576-152.576-88.576z m-165.888 108.544l0.512 177.152 153.6 87.552-1.024-176.64-153.088-88.064z m330.24 0l-153.6 87.552-1.024 176.64 153.6-87.552 1.024-176.64z m131.072 205.824l-68.096-40.96 39.936-8.704-5.632-26.112-67.072 14.848-13.824 23.04 101.376 60.928 13.312-23.04z m-142.848 7.68l68.096 40.96-39.936 8.704 5.632 26.112 67.072-14.848 13.824-23.04-101.888-60.928-12.8 23.04zM481.28 424.96h26.624V306.176H481.28v79.36l-27.648-29.696-19.456 18.432L481.28 424.96z m53.76-118.784V424.96h26.624V345.088l27.648 29.696 19.456-18.432-47.104-50.176h-26.624z m-190.464 401.92l-13.312-23.04-68.608 40.448 11.264-38.912-25.6-7.168-18.944 66.048 13.312 23.04 101.888-60.416z m-89.088 82.944l13.312 23.04 68.608-39.936-11.264 38.912 25.6 7.168 19.456-66.048-13.312-23.04-102.4 59.904z m622.08-45.056c-45.568 0-82.432 36.864-82.432 82.432 0 45.568 36.864 82.432 82.432 82.432 45.568 0 82.432-36.864 82.432-82.432 0-45.568-36.864-82.432-82.432-82.432z m0 122.88c-22.528 0-40.448-17.92-40.448-40.448s17.92-40.448 40.448-40.448 40.448 17.92 40.448 40.448-17.92 40.448-40.448 40.448zM521.728 292.864c45.568 0 82.432-36.864 82.432-82.432 0-45.568-36.864-82.432-82.432-82.432-45.568 0-82.432 36.864-82.432 82.432 0 45.568 36.864 82.432 82.432 82.432z m0-122.88c22.528 0 40.448 17.92 40.448 40.448s-17.92 40.448-40.448 40.448-40.448-17.92-40.448-40.448 18.432-40.448 40.448-40.448zM167.936 749.056c-45.568 0-82.432 36.864-82.432 82.432 0 45.568 36.864 82.432 82.432 82.432 45.568 0 82.432-36.864 82.432-82.432 0-45.568-36.864-82.432-82.432-82.432z m0 122.88c-22.528 0-40.448-17.92-40.448-40.448s17.92-40.448 40.448-40.448 40.448 17.92 40.448 40.448-18.432 40.448-40.448 40.448z" p-id="2267"></path></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -13,6 +13,5 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<title>info_outline</title>
<path d="M11.016 9v-2.016h1.969v2.016h-1.969zM12 20.016q3.281 0 5.648-2.367t2.367-5.648-2.367-5.648-5.648-2.367-5.648 2.367-2.367 5.648 2.367 5.648 5.648 2.367zM12 2.016q4.125 0 7.055 2.93t2.93 7.055-2.93 7.055-7.055 2.93-7.055-2.93-2.93-7.055 2.93-7.055 7.055-2.93zM11.016 17.016v-6h1.969v6h-1.969z"></path> <path d="M11.016 9v-2.016h1.969v2.016h-1.969zM12 20.016q3.281 0 5.648-2.367t2.367-5.648-2.367-5.648-5.648-2.367-5.648 2.367-2.367 5.648 2.367 5.648 5.648 2.367zM12 2.016q4.125 0 7.055 2.93t2.93 7.055-2.93 7.055-7.055 2.93-7.055-2.93-2.93-7.055 2.93-7.055 7.055-2.93zM11.016 17.016v-6h1.969v6h-1.969z"></path>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 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 version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M7.406 8.578l4.594 4.594 4.594-4.594 1.406 1.406-6 6-6-6z"></path>
</svg>

After

Width:  |  Height:  |  Size: 946 B

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 version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M7.406 15.422l-1.406-1.406 6-6 6 6-1.406 1.406-4.594-4.594z"></path>
</svg>

After

Width:  |  Height:  |  Size: 948 B

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 version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M18.984 11.016v-2.016h-3.984v-3.984h-2.016v3.984h-3.984v2.016h3.984v3.984h2.016v-3.984h3.984zM20.016 2.016q0.797 0 1.383 0.586t0.586 1.383v12q0 0.797-0.586 1.406t-1.383 0.609h-12q-0.797 0-1.406-0.609t-0.609-1.406v-12q0-0.797 0.609-1.383t1.406-0.586h12zM3.984 6v14.016h14.016v1.969h-14.016q-0.797 0-1.383-0.586t-0.586-1.383v-14.016h1.969z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,15 @@
<!-- 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="1664266918236" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5378" width="48" height="48"><path d="M571.178667 643.328a144 144 0 0 1-189.098667-193.450667l77.781333 77.866667a48 48 0 1 0 67.882667-67.84l-77.824-77.909333a144 144 0 0 1 193.450667 189.141333l226.517333 207.061333a64.896 64.896 0 1 1-91.690667 91.690667l-207.018666-226.56z m51.498666 134.656a288.298667 288.298667 0 0 1-38.656 12.928v95.488c0 5.290667-4.309333 9.6-9.642666 9.6h-124.757334a9.6 9.6 0 0 1-9.6-9.6v-95.488a286.293333 286.293333 0 0 1-74.325333-30.805333l-67.541333 67.541333a9.6 9.6 0 0 1-13.568 0L196.352 739.413333a9.6 9.6 0 0 1 0-13.568l67.541333-67.541333a286.293333 286.293333 0 0 1-30.805333-74.325333H137.6A9.6 9.6 0 0 1 128 574.378667v-124.757334c0-5.290667 4.309333-9.6 9.6-9.6h95.488c6.826667-26.453333 17.28-51.370667 30.805333-74.325333L196.352 298.154667a9.6 9.6 0 0 1 0-13.568L284.586667 196.352a9.6 9.6 0 0 1 13.568 0l67.541333 67.498667a287.146667 287.146667 0 0 1 74.325333-30.848V137.6c0-5.290667 4.266667-9.6 9.6-9.6h124.8c5.248 0 9.6 4.309333 9.6 9.6v95.488c26.368 6.826667 51.328 17.28 74.282667 30.805333l67.541333-67.541333a9.6 9.6 0 0 1 13.568 0l88.234667 88.234667a9.6 9.6 0 0 1 0 13.568l-67.498667 67.541333a287.146667 287.146667 0 0 1 30.848 74.282667h95.402667c5.290667 0 9.6 4.352 9.6 9.642666v124.757334c0 5.333333-4.266667 9.6-9.6 9.6h-95.488c-4.693333 18.133333-11.178667 35.754667-19.328 52.650666a9.6 9.6 0 0 1-15.018667 2.986667l-10.112-9.173333-38.314666-34.261334-12.16-10.88a9.6 9.6 0 0 1-2.688-10.24A192.298667 192.298667 0 0 0 512 320a192 192 0 1 0 63.018667 373.333333 9.6 9.6 0 0 1 10.24 2.645334l10.837333 12.074666 35.285333 39.338667 8.149334 9.130667a9.6 9.6 0 0 1-2.901334 15.061333 283.306667 283.306667 0 0 1-13.952 6.4z" p-id="5379"></path></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -0,0 +1,19 @@
<!-- 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="1660976558460" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="813" width="48" height="48">
<path d="M498.596 482.29H345.42v57.308h210.478V274.197h-57.301V482.29z m0 0M577.685 644.985h379.88v57.302h-379.88v-57.302z m0 0M577.685 773.765h379.88v57.307h-379.88v-57.307z m0 0M577.685 902.55h379.88v57.307h-379.88V902.55z m0 0" p-id="814"></path>
<path d="M102.523 382.29a28.668 28.668 0 0 0 23.367 2.56l190.81-61.886c15.053-4.883 23.298-21.04 18.415-36.09-4.882-15.052-21.04-23.297-36.093-18.415l-123.346 40c15.994-26.117 35.17-50.538 57.37-72.745 73.768-73.767 171.847-114.388 276.169-114.388 104.32 0 202.395 40.622 276.161 114.388S899.77 407.56 899.77 511.882c0 26.428-2.616 52.45-7.71 77.78h58.303c4.465-25.499 6.709-51.47 6.709-77.78 0-60.45-11.846-119.102-35.205-174.336-22.56-53.335-54.85-101.227-95.969-142.35-41.122-41.122-89.017-73.408-142.348-95.968-55.233-23.361-113.89-35.207-174.334-35.207-60.45 0-119.107 11.846-174.337 35.208-53.335 22.56-101.23 54.846-142.35 95.969-23.98 23.98-44.933 50.278-62.727 78.6l-20.738-105.654c-3.043-15.528-18.105-25.642-33.632-22.6-15.528 3.048-25.643 18.105-22.6 33.637l36.103 183.932a28.666 28.666 0 0 0 13.588 19.178z m0 0M126.02 587.942H67.768c5.76 33.679 15.368 66.544 28.79 98.278 22.56 53.334 54.85 101.225 95.972 142.348 41.123 41.123 89.014 73.409 142.349 95.969 54.112 22.888 111.518 34.711 170.668 35.182v-57.324c-102.95-0.941-199.595-41.446-272.5-114.349-55.501-55.502-92.237-124.77-107.027-200.104z m0 0" p-id="815">
</path>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 895 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 626 B

View File

@@ -552,7 +552,7 @@ const ok = (info: any) => {
if (props.right && _time.getTime() / 100000 > start.value) { if (props.right && _time.getTime() / 100000 > start.value) {
emit("setDates", _time, "right"); emit("setDates", _time, "right");
} }
if (!(props.left && props.right)) { if (!(props.left || props.right)) {
emit("setDates", _time); emit("setDates", _time);
} }
emit("ok", info === "h"); emit("ok", info === "h");
@@ -632,11 +632,6 @@ onMounted(() => {
right: 24px; right: 24px;
} }
.calendar-next-month-btn .middle,
.calendar-prev-month-btn .middle {
margin-top: 8px;
}
.calendar-body { .calendar-body {
position: relative; position: relative;
width: 196px; width: 196px;

View File

@@ -15,6 +15,28 @@ limitations under the License. -->
<template> <template>
<div class="chart" ref="chartRef" :style="`height:${height};width:${width};`"> <div class="chart" ref="chartRef" :style="`height:${height};width:${width};`">
<div v-if="!available" class="no-data">No Data</div> <div v-if="!available" class="no-data">No Data</div>
<div class="menus" v-show="visMenus" ref="menus">
<div class="tools" @click="associateMetrics" v-if="associate.length">
{{ t("associateMetrics") }}
</div>
<div
class="tools"
@click="viewTrace"
v-if="relatedTrace && relatedTrace.enableRelate"
>
{{ t("viewTrace") }}
</div>
</div>
<el-drawer
v-model="showTrace"
size="100%"
:destroy-on-close="true"
:before-close="() => (showTrace = false)"
:append-to-body="true"
title="The Related Traces"
>
<Trace :data="traceOptions" />
</el-drawer>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@@ -28,15 +50,28 @@ import {
computed, computed,
} from "vue"; } from "vue";
import type { PropType } from "vue"; import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { EventParams } from "@/types/app";
import { Filters, RelatedTrace } from "@/types/dashboard";
import { useECharts } from "@/hooks/useEcharts"; import { useECharts } from "@/hooks/useEcharts";
import { addResizeListener, removeResizeListener } from "@/utils/event"; import { addResizeListener, removeResizeListener } from "@/utils/event";
import Trace from "@/views/dashboard/related/trace/Index.vue";
import associateProcessor from "@/hooks/useAssociateProcessor";
/*global Nullable, defineProps, defineEmits*/ /*global Nullable, defineProps, defineEmits*/
const emits = defineEmits(["select"]); const emits = defineEmits(["select"]);
const { t } = useI18n();
const chartRef = ref<Nullable<HTMLDivElement>>(null); const chartRef = ref<Nullable<HTMLDivElement>>(null);
const menus = ref<Nullable<HTMLDivElement>>(null);
const visMenus = ref<boolean>(false);
const { setOptions, resize, getInstance } = useECharts( const { setOptions, resize, getInstance } = useECharts(
chartRef as Ref<HTMLDivElement> chartRef as Ref<HTMLDivElement>
); );
const currentParams = ref<Nullable<EventParams>>(null);
const showTrace = ref<boolean>(false);
const traceOptions = ref<{ type: string; filters?: unknown }>({
type: "Trace",
});
const props = defineProps({ const props = defineProps({
height: { type: String, default: "100%" }, height: { type: String, default: "100%" },
width: { type: String, default: "100%" }, width: { type: String, default: "100%" },
@@ -44,6 +79,16 @@ const props = defineProps({
type: Object as PropType<{ [key: string]: any }>, type: Object as PropType<{ [key: string]: any }>,
default: () => ({}), default: () => ({}),
}, },
filters: {
type: Object as PropType<Filters>,
},
relatedTrace: {
type: Object as PropType<RelatedTrace>,
},
associate: {
type: Array as PropType<{ widgetId: string }[]>,
default: () => [],
},
}); });
const available = computed( const available = computed(
() => () =>
@@ -55,17 +100,97 @@ const available = computed(
onMounted(async () => { onMounted(async () => {
await setOptions(props.option); await setOptions(props.option);
chartRef.value && addResizeListener(unref(chartRef), resize); chartRef.value && addResizeListener(unref(chartRef), resize);
instanceEvent();
});
function instanceEvent() {
setTimeout(() => { setTimeout(() => {
const instance = getInstance(); const instance = getInstance();
if (!instance) { if (!instance) {
return; return;
} }
instance.on("click", (params: any) => { instance.on("click", (params: EventParams) => {
emits("select", params); currentParams.value = params;
if (!menus.value || !chartRef.value) {
return;
}
visMenus.value = true;
const w = chartRef.value.getBoundingClientRect().width || 0;
const h = chartRef.value.getBoundingClientRect().height || 0;
if (w - params.event.offsetX > 120) {
menus.value.style.left = params.event.offsetX + "px";
} else {
menus.value.style.left = params.event.offsetX - 120 + "px";
}
if (h - params.event.offsetY < 50) {
menus.value.style.top = params.event.offsetY - 40 + "px";
} else {
menus.value.style.top = params.event.offsetY + 2 + "px";
}
}); });
document.addEventListener(
"click",
() => {
if (instance.isDisposed()) {
return;
}
visMenus.value = false;
instance.dispatchAction({
type: "updateAxisPointer",
currTrigger: "leave",
});
},
true
);
}, 1000); }, 1000);
}); }
function associateMetrics() {
emits("select", currentParams.value);
const { dataIndex, seriesIndex } = currentParams.value || {
dataIndex: 0,
seriesIndex: 0,
};
updateOptions({ dataIndex, seriesIndex });
}
function updateOptions(params?: { dataIndex: number; seriesIndex: number }) {
const instance = getInstance();
if (!instance) {
return;
}
if (!props.filters) {
return;
}
if (props.filters.isRange) {
const { eventAssociate } = associateProcessor(props);
const options = eventAssociate();
setOptions(options || props.option);
} else {
instance.dispatchAction({
type: "updateAxisPointer",
dataIndex: params ? params.dataIndex : props.filters.dataIndex,
seriesIndex: params ? params.seriesIndex : 0,
});
const ids = props.option.series.map((_: unknown, index: number) => index);
instance.dispatchAction({
type: "highlight",
dataIndex: params ? params.dataIndex : props.filters.dataIndex,
seriesIndex: ids,
});
}
}
function viewTrace() {
const item = associateProcessor(props).traceFilters(currentParams.value);
traceOptions.value = {
...traceOptions.value,
filters: item,
};
showTrace.value = true;
visMenus.value = true;
}
watch( watch(
() => props.option, () => props.option,
@@ -76,7 +201,18 @@ watch(
if (JSON.stringify(newVal) === JSON.stringify(oldVal)) { if (JSON.stringify(newVal) === JSON.stringify(oldVal)) {
return; return;
} }
setOptions(newVal); let options;
if (props.filters && props.filters.isRange) {
const { eventAssociate } = associateProcessor(props);
options = eventAssociate();
}
setOptions(options || props.option);
}
);
watch(
() => props.filters,
() => {
updateOptions();
} }
); );
@@ -98,5 +234,30 @@ onBeforeUnmount(() => {
.chart { .chart {
overflow: hidden; overflow: hidden;
flex: 1;
}
.menus {
position: absolute;
display: block;
white-space: nowrap;
z-index: 9999999;
box-shadow: #ddd 1px 2px 10px;
transition: all cubic-bezier(0.075, 0.82, 0.165, 1) linear;
background-color: rgb(255, 255, 255);
border-radius: 4px;
color: rgb(51, 51, 51);
padding: 5px;
}
.tools {
padding: 5px;
color: #999;
cursor: pointer;
&:hover {
color: #409eff;
background-color: #eee;
}
} }
</style> </style>

View File

@@ -24,8 +24,8 @@ import { ref } from "vue";
import type { PropType } from "vue"; import type { PropType } from "vue";
interface Option { interface Option {
label: string; label: string | number;
value: string; value: string | number;
} }
/*global defineProps, defineEmits */ /*global defineProps, defineEmits */

View File

@@ -18,7 +18,6 @@ limitations under the License. -->
v-model="selected" v-model="selected"
:placeholder="placeholder" :placeholder="placeholder"
@change="changeSelected" @change="changeSelected"
filterable
:multiple="multiple" :multiple="multiple"
:disabled="disabled" :disabled="disabled"
:style="{ borderRadius }" :style="{ borderRadius }"
@@ -26,12 +25,13 @@ limitations under the License. -->
:remote="isRemote" :remote="isRemote"
:reserve-keyword="isRemote" :reserve-keyword="isRemote"
:remote-method="remoteMethod" :remote-method="remoteMethod"
:filterable="filterable"
> >
<el-option <el-option
v-for="item in options" v-for="item in options"
:key="item.value" :key="item.value || ''"
:label="item.label" :label="item.label || ''"
:value="item.value" :value="item.value || ''"
> >
</el-option> </el-option>
</el-select> </el-select>
@@ -66,6 +66,7 @@ const props = defineProps({
disabled: { type: Boolean, default: false }, disabled: { type: Boolean, default: false },
clearable: { type: Boolean, default: false }, clearable: { type: Boolean, default: false },
isRemote: { type: Boolean, default: false }, isRemote: { type: Boolean, default: false },
filterable: { type: Boolean, default: true },
}); });
const selected = ref<string[] | string>(props.value); const selected = ref<string[] | string>(props.value);

View File

@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
export enum TimeType { export enum TimeType {
MINUTE_TIME = "MINUTE", MINUTE_TIME = "MINUTE",
HOUR_TIME = "HOUR", HOUR_TIME = "HOUR",
@@ -25,18 +26,3 @@ export const Languages = [
{ label: "Chinese", value: "zh" }, { label: "Chinese", value: "zh" },
{ label: "Spanish", value: "es" }, { label: "Spanish", value: "es" },
]; ];
export const RoutesMap: { [key: string]: string } = {
GeneralServices: "GENERAL",
Database: "VIRTUAL_DATABASE",
MeshServices: "MESH",
ControlPanel: "MESH_CP",
DataPanel: "MESH_DP",
Linux: "OS_LINUX",
SkyWalkingServer: "SO11Y_OAP",
Satellite: "SO11Y_SATELLITE",
Functions: "FAAS",
Browser: "BROWSER",
KubernetesCluster: "K8S",
KubernetesService: "K8S_SERVICE",
};

View File

@@ -33,12 +33,15 @@ export const createEBPFTask = {
}`, }`,
}; };
export const queryEBPFTasks = { export const queryEBPFTasks = {
variable: "$serviceId: ID!", variable:
"$serviceId: ID, $serviceInstanceId: ID, $targets: [EBPFProfilingTargetType!]",
query: ` query: `
queryEBPFTasks: queryEBPFProfilingTasks(serviceId: $serviceId) { queryEBPFTasks: queryEBPFProfilingTasks(serviceId: $serviceId, serviceInstanceId: $serviceInstanceId, targets: $targets) {
taskId taskId
serviceName serviceName
serviceId serviceId
serviceInstanceId
serviceInstanceName
processLabels processLabels
taskStartTime taskStartTime
triggerType triggerType
@@ -90,3 +93,22 @@ export const analysisEBPFResult = {
} }
}`, }`,
}; };
export const createNetworkProfiling = {
variable: "$request: EBPFProfilingNetworkTaskRequest!",
query: `
createEBPFNetworkProfiling(request: $request) {
status
errorReason
id
}`,
};
export const keepNetworkProfiling = {
variable: "$taskId: ID!",
query: `
keepEBPFNetworkProfiling(taskId: $taskId) {
status
errorReason
}`,
};

View File

@@ -48,6 +48,29 @@ export const Instances = {
} }
`, `,
}; };
export const Processes = {
variable: "$instanceId: ID!, $duration: Duration!",
query: `
processes: listProcesses(instanceId: $instanceId, duration: $duration) {
id
value: name
label: name
serviceId
serviceName
instanceId
instanceName
agentId
detectType
attributes {
name
value
}
labels
}
`,
};
export const Endpoints = { export const Endpoints = {
variable: "$serviceId: ID!, $keyword: String!", variable: "$serviceId: ID!, $keyword: String!",
query: ` query: `
@@ -102,3 +125,24 @@ export const getEndpoint = {
} }
`, `,
}; };
export const getProcess = {
variable: "$processId: ID!",
query: `
process: getProcess(processId: $processId) {
id
value: name
label: name
serviceId
serviceName
instanceId
instanceName
agentId
detectType
attributes {
name
value
}
}
`,
};

View File

@@ -75,3 +75,28 @@ export const InstanceTopology = {
} }
`, `,
}; };
export const ProcessTopology = {
variable: "$serviceInstanceId: ID!, $duration: Duration!",
query: `
topology: getProcessTopology(serviceInstanceId: $serviceInstanceId,
duration: $duration) {
nodes {
id
name
isReal
serviceName
serviceId
serviceInstanceId
serviceInstanceName
}
calls {
id
source
detectPoints
target
sourceComponents
targetComponents
}
}
`,
};

View File

@@ -69,6 +69,25 @@ export const TraceSpans = {
value value
} }
} }
attachedEvents {
startTime {
seconds
nanos
}
event
endTime {
seconds
nanos
}
tags {
key
value
}
summary {
key
value
}
}
} }
} }
`, `,

View File

@@ -21,6 +21,8 @@ import {
queryEBPFTasks, queryEBPFTasks,
queryEBPFSchedules, queryEBPFSchedules,
analysisEBPFResult, analysisEBPFResult,
createNetworkProfiling,
keepNetworkProfiling,
} from "../fragments/ebpf"; } from "../fragments/ebpf";
export const getCreateTaskData = `query queryCreateTaskData(${queryCreateTaskData.variable}) {${queryCreateTaskData.query}}`; export const getCreateTaskData = `query queryCreateTaskData(${queryCreateTaskData.variable}) {${queryCreateTaskData.query}}`;
@@ -32,3 +34,7 @@ export const getEBPFTasks = `query queryEBPFTasks(${queryEBPFTasks.variable}) {$
export const getEBPFSchedules = `query queryEBPFSchedules(${queryEBPFSchedules.variable}) {${queryEBPFSchedules.query}}`; export const getEBPFSchedules = `query queryEBPFSchedules(${queryEBPFSchedules.variable}) {${queryEBPFSchedules.query}}`;
export const getEBPFResult = `query analysisEBPFResult(${analysisEBPFResult.variable}) {${analysisEBPFResult.query}}`; export const getEBPFResult = `query analysisEBPFResult(${analysisEBPFResult.variable}) {${analysisEBPFResult.query}}`;
export const newNetworkProfiling = `mutation createNetworkProfiling(${createNetworkProfiling.variable}) {${createNetworkProfiling.query}}`;
export const aliveNetworkProfiling = `mutation keepNetworkProfiling(${keepNetworkProfiling.variable}) {${keepNetworkProfiling.query}}`;

View File

@@ -22,6 +22,8 @@ import {
getService, getService,
getInstance, getInstance,
getEndpoint, getEndpoint,
Processes,
getProcess,
} from "../fragments/selector"; } from "../fragments/selector";
export const queryServices = `query queryServices(${Services.variable}) {${Services.query}}`; export const queryServices = `query queryServices(${Services.variable}) {${Services.query}}`;
@@ -30,4 +32,6 @@ export const queryInstances = `query queryInstances(${Instances.variable}) {${In
export const queryLayers = `query listLayer {${Layers.query}}`; export const queryLayers = `query listLayer {${Layers.query}}`;
export const queryService = `query queryService(${getService.variable}) {${getService.query}}`; export const queryService = `query queryService(${getService.variable}) {${getService.query}}`;
export const queryInstance = `query queryInstance(${getInstance.variable}) {${getInstance.query}}`; export const queryInstance = `query queryInstance(${getInstance.variable}) {${getInstance.query}}`;
export const queryEndpoint = `query queryInstance(${getEndpoint.variable}) {${getEndpoint.query}}`; export const queryEndpoint = `query queryEndpoint(${getEndpoint.variable}) {${getEndpoint.query}}`;
export const queryProcesses = `query queryProcesses(${Processes.variable}) {${Processes.query}}`;
export const queryProcess = `query queryProcess(${getProcess.variable}) {${getProcess.query}}`;

View File

@@ -18,8 +18,10 @@ import {
InstanceTopology, InstanceTopology,
EndpointTopology, EndpointTopology,
ServicesTopology, ServicesTopology,
ProcessTopology,
} from "../fragments/topology"; } from "../fragments/topology";
export const getInstanceTopology = `query queryData(${InstanceTopology.variable}) {${InstanceTopology.query}}`; export const getInstanceTopology = `query queryData(${InstanceTopology.variable}) {${InstanceTopology.query}}`;
export const getEndpointTopology = `query queryData(${EndpointTopology.variable}) {${EndpointTopology.query}}`; export const getEndpointTopology = `query queryData(${EndpointTopology.variable}) {${EndpointTopology.query}}`;
export const getServicesTopology = `query queryData(${ServicesTopology.variable}) {${ServicesTopology.query}}`; export const getServicesTopology = `query queryData(${ServicesTopology.variable}) {${ServicesTopology.query}}`;
export const getProcessTopology = `query queryData(${ProcessTopology.variable}) {${ProcessTopology.query}}`;

View File

@@ -21,6 +21,7 @@ export enum MetricQueryTypes {
ReadLabeledMetricsValues = "readLabeledMetricsValues", ReadLabeledMetricsValues = "readLabeledMetricsValues",
READHEATMAP = "readHeatMap", READHEATMAP = "readHeatMap",
ReadSampledRecords = "readSampledRecords", ReadSampledRecords = "readSampledRecords",
ReadRecords = "readRecords",
} }
export enum Calculations { export enum Calculations {
@@ -32,10 +33,12 @@ export enum Calculations {
Precision = "precision", Precision = "precision",
ConvertSeconds = "convertSeconds", ConvertSeconds = "convertSeconds",
ConvertMilliseconds = "convertMilliseconds", ConvertMilliseconds = "convertMilliseconds",
MsTos = "msTos", MsToS = "msTos",
Average = "average", Average = "average",
PercentageAvg = "percentageAvg", PercentageAvg = "percentageAvg",
ApdexAvg = "apdexAvg", ApdexAvg = "apdexAvg",
SecondToDay = "secondToDay",
NanosecondToMillisecond = "nanosecondToMillisecond",
} }
export enum sizeEnum { export enum sizeEnum {
XS = "XS", XS = "XS",
@@ -99,4 +102,10 @@ export const RespFields: any = {
value value
refId refId
}`, }`,
readRecords: `{
id
name
value
refId
}`,
}; };

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.
*/
import { useAppStoreWithOut } from "@/store/modules/app";
import dateFormatStep from "@/utils/dateFormat";
import getLocalTime from "@/utils/localtime";
import { EventParams } from "@/types/app";
export default function associateProcessor(props: any) {
function eventAssociate() {
if (!props.filters) {
return;
}
if (!props.filters.duration) {
return props.option;
}
if (!props.option.series[0]) {
return;
}
const list = props.option.series[0].data.map(
(d: (number | string)[]) => d[0]
);
if (!list.includes(props.filters.duration.endTime)) {
return;
}
const markArea = {
silent: true,
itemStyle: {
opacity: 0.3,
},
data: [
[
{
xAxis: props.filters.duration.startTime,
},
{
xAxis: props.filters.duration.endTime,
},
],
],
};
const series = (window as any).structuredClone(props.option.series);
for (const [key, temp] of series.entries()) {
if (key === 0) {
temp.markArea = markArea;
}
}
const options = {
...props.option,
series,
};
return options;
}
function traceFilters(currentParams: Nullable<EventParams>) {
const appStore = useAppStoreWithOut();
if (!currentParams) {
return;
}
const start = appStore.intervalUnix[currentParams.dataIndex];
const { step } = appStore.durationRow;
let duration = undefined;
if (start) {
const end = start;
duration = {
start: dateFormatStep(
getLocalTime(appStore.utc, new Date(start)),
step,
true
),
end: dateFormatStep(
getLocalTime(appStore.utc, new Date(end)),
step,
true
),
step,
};
}
const relatedTrace = props.relatedTrace || {};
const status = relatedTrace.status;
const queryOrder = relatedTrace.queryOrder;
const latency = relatedTrace.latency;
const series = props.option.series || [];
const item: any = {
duration,
queryOrder,
status,
};
if (latency) {
const latencyList = series.map(
(d: { name: string; data: number[][] }, index: number) => {
const data = [
d.data[currentParams.dataIndex][1],
series[index + 1]
? series[index + 1].data[currentParams.dataIndex][1]
: Infinity,
];
return {
label:
d.name +
"--" +
(series[index + 1] ? series[index + 1].name : "Infinity"),
value: String(index),
data,
};
}
);
item.latency = latencyList;
}
const value = series.map(
(d: { name: string; data: number[][] }, index: number) => {
return {
label: d.name,
value: String(index),
data: d.data[currentParams.dataIndex][1],
date: d.data[currentParams.dataIndex][0],
};
}
);
item.metricValue = value;
return item;
}
return { eventAssociate, traceFilters };
}

View File

@@ -14,19 +14,68 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { ElMessage } from "element-plus";
import { useDashboardStore } from "@/store/modules/dashboard";
import { LayoutConfig } from "@/types/dashboard";
export default function getDashboard(param: { export default function getDashboard(param?: {
name: string; name: string;
layer: string; layer: string;
entity: string; entity: string;
}) { }) {
const dashboardStore = useDashboardStore();
const opt = param || dashboardStore.currentDashboard;
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]"); const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
const dashboard = list.find( const dashboard = list.find(
(d: { name: string; layer: string; entity: string }) => (d: { name: string; layer: string; entity: string }) =>
d.name === param.name && d.name === opt.name && d.entity === opt.entity && d.layer === opt.layer
d.entity === param.entity &&
d.layer === param.layer
); );
const all = dashboardStore.layout;
const widgets: LayoutConfig[] = [];
for (const item of all) {
if (item.type === "Tab") {
widgets.push(item);
if (item.children && item.children.length) {
for (const child of item.children) {
if (child.children && child.children.length) {
widgets.push(...child.children);
}
}
}
} else {
widgets.push(item);
}
}
function associationWidget(sourceId: string, filters: unknown, type: string) {
const widget = widgets.find((d: { type: string }) => d.type === type);
if (!widget) {
return ElMessage.info(`There has no a ${type} widget in the dashboard`);
}
const item = {
...widget,
filters,
};
dashboardStore.setWidget(item);
const targetTabIndex = (widget.id || "").split("-");
const sourceTabindex = (sourceId || "").split("-") || [];
let container: Nullable<Element>;
return dashboard; if (targetTabIndex[1] === undefined) {
container = document.querySelector(".ds-main");
} else {
const w = widgets.find((d: any) => d.id === targetTabIndex[0]);
container = document.querySelector(".tab-layout");
const layout: Nullable<Element> = document.querySelector(".ds-main");
if (w && layout) {
layout.scrollTop = w.y * 10 + w.h * 5;
}
}
if (targetTabIndex[1] && targetTabIndex[1] !== sourceTabindex[1]) {
dashboardStore.setActiveTabIndex(Number(targetTabIndex[1]));
}
if (container && widget) {
container.scrollTop = widget.y * 10 + widget.h * 5;
}
}
return { dashboard, widgets, associationWidget };
} }

View File

@@ -18,7 +18,6 @@ import {
BarSeriesOption, BarSeriesOption,
LineSeriesOption, LineSeriesOption,
HeatmapSeriesOption, HeatmapSeriesOption,
PieSeriesOption,
SankeySeriesOption, SankeySeriesOption,
} from "echarts/charts"; } from "echarts/charts";
import { import {
@@ -46,7 +45,6 @@ export type ECOption = echarts.ComposeOption<
| DatasetComponentOption | DatasetComponentOption
| LegendComponentOption | LegendComponentOption
| HeatmapSeriesOption | HeatmapSeriesOption
| PieSeriesOption
| SankeySeriesOption | SankeySeriesOption
>; >;

View File

@@ -0,0 +1,142 @@
/**
* 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 { LegendOptions } from "@/types/dashboard";
import { isDef } from "@/utils/is";
export default function useLegendProcess(legend?: LegendOptions) {
let isRight = false;
if (legend && legend.toTheRight) {
isRight = true;
}
function showEchartsLegend(keys: string[]) {
if (legend && isDef(legend.show)) {
if (legend.asTable && legend.show) {
return false;
}
return legend.show;
}
if (keys.length === 1) {
return false;
}
if (legend && legend.asTable) {
return false;
}
return true;
}
function aggregations(
data: { [key: string]: number[] },
intervalTime: string[]
) {
const source: { [key: string]: unknown }[] = [];
const keys = Object.keys(data || {}).filter(
(i: any) => Array.isArray(data[i]) && data[i].length
);
const headers = [];
for (const [key, value] of keys.entries()) {
const arr = JSON.parse(JSON.stringify(data[value]));
const item: { [key: string]: unknown } = {
name: value,
topN: arr
.map((d: number, index: number) => {
return {
key: intervalTime[index],
value: d,
};
})
.sort(
(
a: { key: string; value: number },
b: { key: string; value: number }
) => b.value - a.value
)
.filter((_: unknown, index: number) => index < 10),
};
if (legend) {
if (legend.min) {
item.min = Math.min(...data[value]).toFixed(2);
if (key === 0) {
headers.push({ value: "min", label: "Min" });
}
}
if (legend.max) {
item.max = Math.max(...data[value]).toFixed(2);
if (key === 0) {
headers.push({ value: "max", label: "Max" });
}
}
if (legend.mean) {
const total = data[value].reduce((prev: number, next: number) => {
prev += Number(next);
return prev;
}, 0);
item.mean = (total / data[value].length).toFixed(4);
if (key === 0) {
headers.push({ value: "mean", label: "Mean" });
}
}
if (legend.total) {
item.total = data[value]
.reduce((prev: number, next: number) => {
prev += Number(next);
return prev;
}, 0)
.toFixed(2);
if (key === 0) {
headers.push({ value: "total", label: "Total" });
}
}
}
source.push(item);
}
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;
}
return { showEchartsLegend, isRight, aggregations, chartColors };
}

View File

@@ -26,14 +26,18 @@ export function useListConfig(config: any, index: string) {
config.metricConfig && config.metricConfig &&
config.metricConfig[i] && config.metricConfig[i] &&
config.metricConfig[i].calculation; config.metricConfig[i].calculation;
const line = const isLinear =
config.metricTypes[i] === MetricQueryTypes.ReadMetricsValues && [
!types.includes(calculation); MetricQueryTypes.ReadMetricsValues,
MetricQueryTypes.ReadLabeledMetricsValues,
].includes(config.metricTypes[i]) && !types.includes(calculation);
const isAvg = const isAvg =
config.metricTypes[i] === MetricQueryTypes.ReadMetricsValues && [
types.includes(calculation); MetricQueryTypes.ReadMetricsValues,
MetricQueryTypes.ReadLabeledMetricsValues,
].includes(config.metricTypes[i]) && types.includes(calculation);
return { return {
isLinear: line, isLinear,
isAvg, isAvg,
}; };
} }

View File

@@ -46,6 +46,7 @@ export function useQueryProcessor(config: any) {
"ServiceRelation", "ServiceRelation",
"ServiceInstanceRelation", "ServiceInstanceRelation",
"EndpointRelation", "EndpointRelation",
"ProcessRelation",
].includes(dashboardStore.entity); ].includes(dashboardStore.entity);
if (isRelation && !selectorStore.currentDestService) { if (isRelation && !selectorStore.currentDestService) {
return; return;
@@ -73,50 +74,73 @@ export function useQueryProcessor(config: any) {
order: c.sortOrder || "DES", order: c.sortOrder || "DES",
}; };
} else { } else {
if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) { const entity = {
const labels = (c.labelsIndex || "") scope: config.catalog,
.split(",") serviceName:
.map((item: string) => item.replace(/^\s*|\s*$/g, "")); dashboardStore.entity === "All"
variables.push(`$labels${index}: [String!]!`); ? undefined
conditions[`labels${index}`] = labels; : selectorStore.currentService.value,
} normal:
variables.push(`$condition${index}: MetricsCondition!`); dashboardStore.entity === "All"
conditions[`condition${index}`] = { ? undefined
name, : selectorStore.currentService.normal,
entity: { serviceInstanceName: [
scope: dashboardStore.entity, "ServiceInstance",
serviceName: "ServiceInstanceRelation",
dashboardStore.entity === "All" "ProcessRelation",
? undefined ].includes(dashboardStore.entity)
: selectorStore.currentService.value, ? selectorStore.currentPod && selectorStore.currentPod.value
normal: : undefined,
dashboardStore.entity === "All" endpointName: dashboardStore.entity.includes("Endpoint")
? undefined ? selectorStore.currentPod && selectorStore.currentPod.value
: selectorStore.currentService.normal, : undefined,
serviceInstanceName: dashboardStore.entity.includes("ServiceInstance") processName: dashboardStore.entity.includes("Process")
? selectorStore.currentPod && selectorStore.currentPod.value ? 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, : undefined,
endpointName: dashboardStore.entity.includes("Endpoint") destProcessName: dashboardStore.entity.includes("ProcessRelation")
? selectorStore.currentPod && selectorStore.currentPod.value ? selectorStore.currentDestProcess &&
: undefined, selectorStore.currentDestProcess.value
destNormal: isRelation : undefined,
? selectorStore.currentDestService.normal
: undefined,
destServiceName: isRelation
? selectorStore.currentDestService.value
: undefined,
destServiceInstanceName:
dashboardStore.entity === "ServiceInstanceRelation"
? selectorStore.currentDestPod &&
selectorStore.currentDestPod.value
: undefined,
destEndpointName:
dashboardStore.entity === "EndpointRelation"
? selectorStore.currentDestPod &&
selectorStore.currentDestPod.value
: undefined,
},
}; };
if ([MetricQueryTypes.ReadRecords].includes(metricType)) {
variables.push(`$condition${index}: RecordCondition!`);
conditions[`condition${index}`] = {
name,
parentEntity: entity,
topN: c.topN || 10,
order: c.sortOrder || "DES",
};
} else {
entity.scope = dashboardStore.entity;
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) { if (metricType === MetricQueryTypes.ReadLabeledMetricsValues) {
return `${name}${index}: ${metricType}(condition: $condition${index}, labels: $labels${index}, duration: $duration)${RespFields[metricType]}`; return `${name}${index}: ${metricType}(condition: $condition${index}, labels: $labels${index}, duration: $duration)${RespFields[metricType]}`;
@@ -125,6 +149,7 @@ export function useQueryProcessor(config: any) {
} }
}); });
const queryStr = `query queryData(${variables}) {${fragment}}`; const queryStr = `query queryData(${variables}) {${fragment}}`;
return { return {
queryStr, queryStr,
conditions, conditions,
@@ -142,6 +167,10 @@ export function useSourceProcessor(
ElMessage.error(resp.errors); ElMessage.error(resp.errors);
return {}; return {};
} }
if (!resp.data) {
ElMessage.error("The query is wrong");
return {};
}
const source: { [key: string]: unknown } = {}; const source: { [key: string]: unknown } = {};
const keys = Object.keys(resp.data); const keys = Object.keys(resp.data);
@@ -150,7 +179,10 @@ export function useSourceProcessor(
const c = (config.metricConfig && config.metricConfig[index]) || {}; const c = (config.metricConfig && config.metricConfig[index]) || {};
if (type === MetricQueryTypes.ReadMetricsValues) { if (type === MetricQueryTypes.ReadMetricsValues) {
source[m] = calculateExp(resp.data[keys[index]].values.values, c); source[c.label || m] =
(resp.data[keys[index]] &&
calculateExp(resp.data[keys[index]].values.values, c)) ||
[];
} }
if (type === MetricQueryTypes.ReadLabeledMetricsValues) { if (type === MetricQueryTypes.ReadLabeledMetricsValues) {
const resVal = Object.values(resp.data)[0] || []; const resVal = Object.values(resp.data)[0] || [];
@@ -176,8 +208,13 @@ export function useSourceProcessor(
source[m] = aggregation(Number(Object.values(resp.data)[0]), c); source[m] = aggregation(Number(Object.values(resp.data)[0]), c);
} }
if ( if (
type === MetricQueryTypes.SortMetrics || (
type === MetricQueryTypes.ReadSampledRecords [
MetricQueryTypes.ReadRecords,
MetricQueryTypes.ReadSampledRecords,
MetricQueryTypes.SortMetrics,
] as string[]
).includes(type)
) { ) {
source[m] = (Object.values(resp.data)[0] || []).map( source[m] = (Object.values(resp.data)[0] || []).map(
(d: { value: unknown; name: string }) => { (d: { value: unknown; name: string }) => {
@@ -218,13 +255,19 @@ export function useSourceProcessor(
export function useQueryPodsMetrics( export function useQueryPodsMetrics(
pods: Array<Instance | Endpoint | Service | any>, pods: Array<Instance | Endpoint | Service | any>,
config: { metrics: string[]; metricTypes: string[] }, config: {
metrics: string[];
metricTypes: string[];
metricConfig: MetricConfigOpt[];
},
scope: string scope: string
) { ) {
if (!(config.metrics && config.metrics[0])) { const metricTypes = (config.metricTypes || []).filter((m: string) => m);
if (!metricTypes.length) {
return; return;
} }
if (!(config.metricTypes && config.metricTypes[0])) { const metrics = (config.metrics || []).filter((m: string) => m);
if (!metrics.length) {
return; return;
} }
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
@@ -246,14 +289,24 @@ export function useQueryPodsMetrics(
endpointName: scope === "Endpoint" ? d.label : undefined, endpointName: scope === "Endpoint" ? d.label : undefined,
normal: scope === "Service" ? d.normal : currentService.normal, normal: scope === "Service" ? d.normal : currentService.normal,
}; };
const f = config.metrics.map((name: string, idx: number) => { const f = metrics.map((name: string, idx: number) => {
const metricType = config.metricTypes[idx] || ""; const metricType = metricTypes[idx] || "";
variables.push(`$condition${index}${idx}: MetricsCondition!`);
conditions[`condition${index}${idx}`] = { conditions[`condition${index}${idx}`] = {
name, name,
entity: param, entity: param,
}; };
variables.push(`$condition${index}${idx}: MetricsCondition!`); let labelStr = "";
return `${name}${index}${idx}: ${metricType}(condition: $condition${index}${idx}, duration: $duration)${RespFields[metricType]}`; 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;
}
return `${name}${index}${idx}: ${metricType}(condition: $condition${index}${idx}, ${labelStr}duration: $duration)${RespFields[metricType]}`;
}); });
return f; return f;
} }
@@ -263,6 +316,7 @@ export function useQueryPodsMetrics(
return { queryStr, conditions }; return { queryStr, conditions };
} }
export function usePodsSource( export function usePodsSource(
pods: Array<Instance | Endpoint>, pods: Array<Instance | Endpoint>,
resp: { errors: string; data: { [key: string]: any } }, resp: { errors: string; data: { [key: string]: any } },
@@ -276,12 +330,20 @@ export function usePodsSource(
ElMessage.error(resp.errors); ElMessage.error(resp.errors);
return {}; return {};
} }
const names: string[] = [];
const metricConfigArr: MetricConfigOpt[] = [];
const metricTypesArr: string[] = [];
const data = pods.map((d: Instance | any, idx: number) => { const data = pods.map((d: Instance | any, idx: number) => {
config.metrics.map((name: string, index: number) => { config.metrics.map((name: string, index: number) => {
const c: any = (config.metricConfig && config.metricConfig[index]) || {}; const c: any = (config.metricConfig && config.metricConfig[index]) || {};
const key = name + idx + index; const key = name + idx + index;
if (config.metricTypes[index] === MetricQueryTypes.ReadMetricsValue) { if (config.metricTypes[index] === MetricQueryTypes.ReadMetricsValue) {
d[name] = aggregation(resp.data[key], c); d[name] = aggregation(resp.data[key], c);
if (idx === 0) {
names.push(name);
metricConfigArr.push(c);
metricTypesArr.push(config.metricTypes[index]);
}
} }
if (config.metricTypes[index] === MetricQueryTypes.ReadMetricsValues) { if (config.metricTypes[index] === MetricQueryTypes.ReadMetricsValues) {
d[name] = {}; d[name] = {};
@@ -297,12 +359,56 @@ export function usePodsSource(
d[name]["values"] = resp.data[key].values.values.map( d[name]["values"] = resp.data[key].values.values.map(
(val: { value: number }) => aggregation(val.value, c) (val: { value: number }) => 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 }) =>
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 d;
}); });
return data; return { data, names, metricConfigArr, metricTypesArr };
} }
export function useQueryTopologyMetrics(metrics: string[], ids: string[]) { export function useQueryTopologyMetrics(metrics: string[], ids: string[]) {
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
@@ -377,23 +483,26 @@ export function aggregation(
data = (val / 1024 / 1024 / 1024).toFixed(2); data = (val / 1024 / 1024 / 1024).toFixed(2);
break; break;
case Calculations.Apdex: case Calculations.Apdex:
data = val / 10000; data = (val / 10000).toFixed(2);
break;
case Calculations.ApdexAvg:
data = val / 10000;
break; break;
case Calculations.ConvertSeconds: case Calculations.ConvertSeconds:
data = dayjs(val).format("YYYY-MM-DD HH:mm:ss"); data = dayjs(val * 1000).format("YYYY-MM-DD HH:mm:ss");
break; break;
case Calculations.ConvertMilliseconds: case Calculations.ConvertMilliseconds:
data = dayjs.unix(val).format("YYYY-MM-DD HH:mm:ss"); data = dayjs(val).format("YYYY-MM-DD HH:mm:ss");
break; break;
case Calculations.Precision: case Calculations.Precision:
data = data.toFixed(2); data = data.toFixed(2);
break; break;
case Calculations.MsTos: case Calculations.MsToS:
data = (val / 1000).toFixed(2); data = (val / 1000).toFixed(2);
break; break;
case Calculations.SecondToDay:
data = (val / 86400).toFixed(2);
break;
case Calculations.NanosecondToMillisecond:
data = (val / 1000 / 1000).toFixed(2);
break;
default: default:
data; data;
break; break;
@@ -412,6 +521,7 @@ export async function useGetMetricEntity(metric: string, metricType: any) {
[ [
MetricQueryTypes.ReadSampledRecords, MetricQueryTypes.ReadSampledRecords,
MetricQueryTypes.SortMetrics, MetricQueryTypes.SortMetrics,
MetricQueryTypes.ReadRecords,
].includes(metricType) ].includes(metricType)
) { ) {
const res = await dashboardStore.fetchMetricList(metric); const res = await dashboardStore.fetchMetricList(metric);

View File

@@ -13,12 +13,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div class="nav-bar flex-h" :class="{ dark: theme === 'dark' }"> <div class="nav-bar flex-h">
<div class="title">{{ appStore.pageTitle || t(pageName) }}</div> <div class="title">{{ appStore.pageTitle || t(pageName) }}</div>
<div class="app-config"> <div class="app-config">
<span class="red" v-show="timeRange">{{ t("timeTips") }}</span> <span class="red" v-show="timeRange">{{ t("timeTips") }}</span>
<TimePicker <TimePicker
:value="time" :value="[appStore.durationRow.start, appStore.durationRow.end]"
position="bottom" position="bottom"
format="YYYY-MM-DD HH:mm" format="YYYY-MM-DD HH:mm"
@input="changeTimeRange" @input="changeTimeRange"
@@ -49,7 +49,7 @@ limitations under the License. -->
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch, computed } from "vue"; import { ref, watch } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import timeFormat from "@/utils/timeFormat"; import timeFormat from "@/utils/timeFormat";
@@ -61,23 +61,20 @@ const appStore = useAppStoreWithOut();
const route = useRoute(); const route = useRoute();
const pageName = ref<string>(""); const pageName = ref<string>("");
const timeRange = ref<number>(0); const timeRange = ref<number>(0);
const theme = ref<string>("light");
resetDuration();
getVersion(); getVersion();
const setConfig = (value: string) => { const setConfig = (value: string) => {
pageName.value = value || ""; pageName.value = value || "";
// theme.value = route.path.includes("/infrastructure/") ? "dark" : "light";
}; };
const time = computed(() => [
appStore.durationRow.start, function handleReload() {
appStore.durationRow.end,
]);
const handleReload = () => {
const gap = const gap =
appStore.duration.end.getTime() - appStore.duration.start.getTime(); appStore.duration.end.getTime() - appStore.duration.start.getTime();
const time: Date[] = [new Date(new Date().getTime() - gap), new Date()]; const dates: Date[] = [new Date(new Date().getTime() - gap), new Date()];
appStore.setDuration(timeFormat(time)); appStore.setDuration(timeFormat(dates));
}; }
function changeTimeRange(val: Date[] | any) { function changeTimeRange(val: Date[] | any) {
timeRange.value = timeRange.value =
val[1].getTime() - val[0].getTime() > 60 * 24 * 60 * 60 * 1000 ? 1 : 0; val[1].getTime() - val[0].getTime() > 60 * 24 * 60 * 60 * 1000 ? 1 : 0;
@@ -99,6 +96,19 @@ async function getVersion() {
ElMessage.error(res.errors); ElMessage.error(res.errors);
} }
} }
function resetDuration() {
const { duration }: any = route.params;
if (duration) {
const d = JSON.parse(duration);
appStore.updateDurationRow({
start: new Date(d.start),
end: new Date(d.end),
step: d.step,
});
appStore.updateUTC(d.utc);
}
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.nav-bar { .nav-bar {

View File

@@ -33,11 +33,7 @@ limitations under the License. -->
<template v-for="(menu, index) in routes" :key="index"> <template v-for="(menu, index) in routes" :key="index">
<el-sub-menu :index="String(menu.name)" v-if="menu.meta.hasGroup"> <el-sub-menu :index="String(menu.name)" v-if="menu.meta.hasGroup">
<template #title> <template #title>
<router-link <router-link class="items" :to="menu.path">
class="items"
:to="menu.path"
:exact="menu.meta.exact || false"
>
<el-icon class="menu-icons" :style="{ marginRight: '12px' }"> <el-icon class="menu-icons" :style="{ marginRight: '12px' }">
<Icon size="lg" :iconName="menu.meta.icon" /> <Icon size="lg" :iconName="menu.meta.icon" />
</el-icon> </el-icon>
@@ -52,12 +48,8 @@ limitations under the License. -->
:index="m.name" :index="m.name"
:key="idx" :key="idx"
> >
<router-link <router-link class="items" :to="m.path">
class="items" <span class="title">{{ m.meta && t(m.meta.title) }}</span>
:to="m.path"
:exact="m.meta.exact || false"
>
<span class="title">{{ t(m.meta.title) }}</span>
</router-link> </router-link>
</el-menu-item> </el-menu-item>
</el-menu-item-group> </el-menu-item-group>
@@ -68,20 +60,12 @@ limitations under the License. -->
v-else v-else
> >
<el-icon class="menu-icons" :style="{ marginRight: '12px' }"> <el-icon class="menu-icons" :style="{ marginRight: '12px' }">
<router-link <router-link class="items" :to="menu.children[0].path">
class="items"
:to="menu.children[0].path"
:exact="menu.meta.exact"
>
<Icon size="lg" :iconName="menu.meta.icon" /> <Icon size="lg" :iconName="menu.meta.icon" />
</router-link> </router-link>
</el-icon> </el-icon>
<template #title> <template #title>
<router-link <router-link class="items" :to="menu.children[0].path">
class="items"
:to="menu.children[0].path"
:exact="menu.meta.exact"
>
<span class="title">{{ t(menu.meta.title) }}</span> <span class="title">{{ t(menu.meta.title) }}</span>
</router-link> </router-link>
</template> </template>
@@ -123,7 +107,7 @@ if (/Android|webOS|iPhone|iPod|iPad|BlackBerry/i.test(navigator.userAgent)) {
} else { } else {
appStore.setIsMobile(false); appStore.setIsMobile(false);
} }
const isCollapse = ref(appStore.isMobile ? true : false); const isCollapse = ref(false);
const controlMenu = () => { const controlMenu = () => {
isCollapse.value = !isCollapse.value; isCollapse.value = !isCollapse.value;
}; };
@@ -141,13 +125,13 @@ const filterMenus = (menus: any[]) => {
.side-bar { .side-bar {
background: #252a2f; background: #252a2f;
height: 100%; height: 100%;
min-height: 700px;
position: relative;
margin-bottom: 100px; margin-bottom: 100px;
overflow-y: auto;
overflow-x: hidden;
} }
.el-menu-vertical:not(.el-menu--collapse) { .el-menu-vertical:not(.el-menu--collapse) {
width: 200px; width: 220px;
font-size: 16px; font-size: 16px;
} }
@@ -173,7 +157,7 @@ span.collapse {
.menu-control { .menu-control {
position: absolute; position: absolute;
top: 7px; top: 7px;
left: 200px; left: 220px;
cursor: pointer; cursor: pointer;
transition: all 0.2s linear; transition: all 0.2s linear;
z-index: 99; z-index: 99;

View File

@@ -32,6 +32,7 @@ const msg = {
dashboards: "Dashboards", dashboards: "Dashboards",
profiles: "Profiles", profiles: "Profiles",
database: "Database", database: "Database",
mySQL: "MySQL",
serviceName: "Service Name", serviceName: "Service Name",
technologies: "Technologies", technologies: "Technologies",
generalServicePanel: "General Service Panel", generalServicePanel: "General Service Panel",
@@ -51,17 +52,19 @@ const msg = {
instance: "Instance", instance: "Instance",
create: "Create", create: "Create",
loading: "Loading", loading: "Loading",
selectVisualization: "Visualize your metrics", selectVisualization: "Visualize Metrics",
visualization: "Visualization", visualization: "Visualization",
graphStyles: "Graph styles", graphStyles: "Graph Styles",
widgetOptions: "Widget options", widgetOptions: "Widget Options",
standardOptions: "Standard options", standardOptions: "Standard Options",
max: "Max", max: "Max",
min: "Min", min: "Min",
plus: "Plus", plus: "Plus",
mean: "Mean",
minus: "Minus", minus: "Minus",
multiply: "Multiply", multiply: "Multiply",
divide: "Divide", divide: "Divide",
total: "Total",
convertToMilliseconds: "Convert Unix Timestamp(milliseconds)", convertToMilliseconds: "Convert Unix Timestamp(milliseconds)",
convertToSeconds: "Convert Unix Timestamp(seconds)", convertToSeconds: "Convert Unix Timestamp(seconds)",
smooth: "Smooth", smooth: "Smooth",
@@ -107,6 +110,7 @@ const msg = {
showXAxis: "Show X Axis", showXAxis: "Show X Axis",
showYAxis: "Show Y Axis", showYAxis: "Show Y Axis",
nameError: "The dashboard name cannot be duplicate", nameError: "The dashboard name cannot be duplicate",
nameEmptyError: "The dashboard name cannot be empty",
showGroup: "Show Group", showGroup: "Show Group",
noRoot: "Please set a root dashboard for", noRoot: "Please set a root dashboard for",
noWidget: "Please add widgets.", noWidget: "Please add widgets.",
@@ -122,6 +126,7 @@ const msg = {
editWarning: "You are entering edit mode", editWarning: "You are entering edit mode",
viewWarning: "You are entering view mode", viewWarning: "You are entering view mode",
virtualDatabase: "Virtual Database", virtualDatabase: "Virtual Database",
virtualCache: "Virtual Cache",
reloadDashboards: "Reload dashboards", reloadDashboards: "Reload dashboards",
kubernetesService: "Service", kubernetesService: "Service",
kubernetesCluster: "Cluster", kubernetesCluster: "Cluster",
@@ -142,6 +147,40 @@ const msg = {
interval: "Refresh Interval", interval: "Refresh Interval",
pause: "Pause", pause: "Pause",
begin: "Start", begin: "Start",
associateOptions: "Association Options",
associateMetrics: "Association Metrics",
widget: "Widget",
nameTip:
"The name only supports Chinese and English, horizontal lines and underscores. The length of the name is limited to 300 characters",
duplicateName: "Duplicate name",
enableAssociate: "Enable association",
text: "Text",
query: "Query",
postgreSQL: "PostgreSQL",
endpointTips: "The table shows up to 20 pieces of endpoints.",
apisix: "APISIX",
viewTrace: "View Related Traces",
relatedTraceOptions: "Related Trace Options",
setLatencyDuration: "Latency Related Metrics",
queryOrder: "Query By Duration",
setOrder: "Query Order",
latency: "Latency",
metricValues: "Metric Values",
queryConditions: "Query Conditions",
enableRelatedTrace: "Enable Related Trace",
maxTraceDuration: "Maximum Duration",
minTraceDuration: "Minimum Duration",
legendOptions: "Legend Options",
showLegend: "Show Legend",
asTable: "As Table",
toTheRight: "To The Right",
legendValues: "Legend Values",
minDuration: "Minimal Request Duration",
when4xx:
"Sample HTTP requests and responses with tracing when response code between 400 and 499",
when5xx:
"Sample HTTP requests and responses with tracing when response code between 500 and 599",
taskTitle: "HTTP request and response collecting rules",
seconds: "Seconds", seconds: "Seconds",
hourTip: "Select Hour", hourTip: "Select Hour",
minuteTip: "Select Minute", minuteTip: "Select Minute",
@@ -151,8 +190,6 @@ const msg = {
monthsHead: "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec", monthsHead: "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec",
months: "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec", months: "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec",
weeks: "Mon_Tue_Wed_Thu_Fri_Sat_Sun", weeks: "Mon_Tue_Wed_Thu_Fri_Sat_Sun",
hello: "Hello",
helloMessage: "Welcome Back, Apache SkyWalking APM System !",
username: "Username", username: "Username",
password: "Password", password: "Password",
title: "Title", title: "Title",
@@ -161,7 +198,7 @@ const msg = {
dashboard: "Dashboard", dashboard: "Dashboard",
topology: "Topology", topology: "Topology",
trace: "Trace", trace: "Trace",
alarm: "Alarms", alarm: "Alerting",
auto: "Auto", auto: "Auto",
reload: "Reload", reload: "Reload",
version: "Version", version: "Version",
@@ -236,7 +273,7 @@ const msg = {
entityType: "Entity Type", entityType: "Entity Type",
maxItemNum: "Max number of Item", maxItemNum: "Max number of Item",
unknownMetrics: "Unknown Metrics", unknownMetrics: "Unknown Metrics",
labels: "Labels", labels: "Label",
aggregation: "Calculation", aggregation: "Calculation",
unit: "Unit", unit: "Unit",
labelsIndex: "Label Subscript", labelsIndex: "Label Subscript",
@@ -297,6 +334,7 @@ const msg = {
eventsParameters: "Event Parameters", eventsParameters: "Event Parameters",
eventDetail: "Event Detail", eventDetail: "Event Detail",
value: "Value", value: "Value",
key: "Key",
show: "Show", show: "Show",
hide: "Hide", hide: "Hide",
statistics: "Statistics", statistics: "Statistics",
@@ -335,5 +373,7 @@ const msg = {
conditionNotice: conditionNotice:
"Notice: Please press Enter after inputting a key of content, exclude key of content(key=value).", "Notice: Please press Enter after inputting a key of content, exclude key of content(key=value).",
language: "Language", language: "Language",
gateway: "Gateway",
virtualMQ: "Virtual MQ",
}; };
export default msg; export default msg;

View File

@@ -32,6 +32,7 @@ const msg = {
dashboards: "Paneles", dashboards: "Paneles",
profiles: "Perfiles", profiles: "Perfiles",
database: "Base de Datos", database: "Base de Datos",
mySQL: "MySQL",
serviceName: "Nombre Servicio", serviceName: "Nombre Servicio",
technologies: "Tecnologías", technologies: "Tecnologías",
generalServicePanel: "Panel Servicio General", generalServicePanel: "Panel Servicio General",
@@ -58,9 +59,11 @@ const msg = {
standardOptions: "Opciones estandar", standardOptions: "Opciones estandar",
max: "Máx", max: "Máx",
min: "Mín", min: "Mín",
mean: "Promedio",
plus: "Más", plus: "Más",
minus: "Menoss", minus: "Menoss",
multiply: "Multiplcar", multiply: "Multiplcar",
total: "Todo",
divide: "Dividir", divide: "Dividir",
convertToMilliseconds: "Convertir Unix Timestamp(milisegundos)", convertToMilliseconds: "Convertir Unix Timestamp(milisegundos)",
convertToSeconds: "Convertir Unix Timestamp(segundos)", convertToSeconds: "Convertir Unix Timestamp(segundos)",
@@ -109,6 +112,7 @@ const msg = {
showXAxis: "Mostrar Eje X", showXAxis: "Mostrar Eje X",
showYAxis: "Mostrar Eje Y", showYAxis: "Mostrar Eje Y",
nameError: "El nombre del panel no puede ser duplicado", nameError: "El nombre del panel no puede ser duplicado",
nameEmptyError: "El nombre del panel no puede estar vacío",
showGroup: "Mostrar Grupo", showGroup: "Mostrar Grupo",
noRoot: "Por favor ponga la raíz del panel", noRoot: "Por favor ponga la raíz del panel",
noWidget: "Por favor añada widgets.", noWidget: "Por favor añada widgets.",
@@ -124,6 +128,7 @@ const msg = {
editWarning: "Estás entrando en modo edición", editWarning: "Estás entrando en modo edición",
viewWarning: "Estás entrando en modo visualización", viewWarning: "Estás entrando en modo visualización",
virtualDatabase: "Base de Datos Virtual", virtualDatabase: "Base de Datos Virtual",
virtualCache: "Caché virtual",
reloadDashboards: "Recargar Panel", reloadDashboards: "Recargar Panel",
kubernetesService: "Servicio", kubernetesService: "Servicio",
kubernetesCluster: "Cluster", kubernetesCluster: "Cluster",
@@ -142,17 +147,49 @@ const msg = {
interval: "Intervalo de actualización", interval: "Intervalo de actualización",
pause: "Pausa", pause: "Pausa",
begin: "Inicio", begin: "Inicio",
associateOptions: "Opciones de asociación",
associateMetrics: "Índice de correlación",
widget: "Dispositivo pequeño",
text: "Texto",
duplicateName: "Nombre duplicado",
nameTip:
"El nombre sólo admite chino e inglés, líneas horizontales y subrayado, y la longitud del nombre no excederá de 300 caracteres",
enableAssociate: "Activar asociación",
query: "Consulta",
postgreSQL: "PostgreSQL",
endpointTips: "Aquí, la tabla muestra hasta 20 punto final.",
apisix: "APISIX",
queryOrder: "Consulta por duración",
setOrder: "Orden de consulta",
latency: "Retraso",
metricValues: "Valor métrico",
legendValues: "Valor de la leyenda",
seconds: "Segundos", seconds: "Segundos",
hourTip: "Seleccione Hora", hourTip: "Seleccione Hora",
minuteTip: "Seleccione Minuto", minuteTip: "Seleccione Minuto",
secondTip: "Seleccione Segundo", secondTip: "Seleccione Segundo",
viewTrace: "Ver trazas relacionadas",
relatedTraceOptions: "Opciones de seguimiento relacionadas",
setLatencyDuration: "Índice de correlación retardada",
enableRelatedTrace: "Activar trazas relacionadas",
queryConditions: "Condiciones de consulta",
maxTraceDuration: "Duración máxima",
minTraceDuration: "Duración mínima",
legendOptions: "Opciones de leyenda",
showLegend: "Mostrar leyenda",
asTable: "Como tabla",
toTheRight: "Derecha",
minDuration: "Duración mínima de la solicitud",
when4xx:
"Ejemplo de solicitud y respuesta http con seguimiento cuando el Código de respuesta está entre 400 y 499",
when5xx:
"Ejemplo de solicitud y respuesta http con seguimiento cuando el Código de respuesta está entre 500 y 599",
taskTitle: "Reglas de recolección de peticiones y respuestas HTTP",
second: "s", second: "s",
yearSuffix: "Año", yearSuffix: "Año",
monthsHead: "Ene_Feb_Mar_Abr_May_Jun_Jul_Ago_Set_Oct_Nov_Dic", monthsHead: "Ene_Feb_Mar_Abr_May_Jun_Jul_Ago_Set_Oct_Nov_Dic",
months: "Ene_Feb_Mar_Abr_May_Jun_Jul_Ago_Set_Oct_Nov_Dic", months: "Ene_Feb_Mar_Abr_May_Jun_Jul_Ago_Set_Oct_Nov_Dic",
weeks: "Lun_Mar_Mier_Jue_Vie_Sáb_Dom", weeks: "Lun_Mar_Mier_Jue_Vie_Sáb_Dom",
hello: "Hola",
helloMessage: "Bienvenido de vuelta, Apache SkyWalking APM System !",
username: "Usuario", username: "Usuario",
password: "Contraseña", password: "Contraseña",
title: "Título", title: "Título",
@@ -161,7 +198,7 @@ const msg = {
dashboard: "Panel", dashboard: "Panel",
topology: "Topología", topology: "Topología",
trace: "Traza", trace: "Traza",
alarm: "Alarmas", alarm: "Recordatorio en curso",
auto: "Auto", auto: "Auto",
reload: "Recargar", reload: "Recargar",
version: "Versión", version: "Versión",
@@ -297,6 +334,7 @@ const msg = {
eventsParameters: "Parámetro del Evento", eventsParameters: "Parámetro del Evento",
eventDetail: "Detalle del Evento", eventDetail: "Detalle del Evento",
value: "Valor", value: "Valor",
key: "Clave",
show: "Mostrar", show: "Mostrar",
hide: "Oculatr", hide: "Oculatr",
statistics: "Estadísticas", statistics: "Estadísticas",
@@ -338,5 +376,7 @@ const msg = {
conditionNotice: conditionNotice:
"Aviso: Por favor presione Intro después de introducir una clave de contenido, excluir clave de contenido(clave=valor).", "Aviso: Por favor presione Intro después de introducir una clave de contenido, excluir clave de contenido(clave=valor).",
language: "Lenguaje", language: "Lenguaje",
gateway: "Puerta",
virtualMQ: "MQ virtual",
}; };
export default msg; export default msg;

View File

@@ -32,6 +32,7 @@ const msg = {
dashboards: "仪表盘", dashboards: "仪表盘",
profiles: "性能剖析", profiles: "性能剖析",
database: "数据库", database: "数据库",
mySQL: "MySQL",
serviceName: "服务名称", serviceName: "服务名称",
technologies: "技术", technologies: "技术",
health: "健康", health: "健康",
@@ -55,10 +56,12 @@ const msg = {
standardOptions: "标准选项", standardOptions: "标准选项",
max: "最大值", max: "最大值",
min: "最小值", min: "最小值",
mean: "平均值",
plus: "加法", plus: "加法",
minus: "减法", minus: "减法",
multiply: "乘法", multiply: "乘法",
divide: "除法", divide: "除法",
total: "总计",
convertToMilliseconds: "转换Unix时间戳毫秒", convertToMilliseconds: "转换Unix时间戳毫秒",
convertToSeconds: "转换Unix时间戳", convertToSeconds: "转换Unix时间戳",
smooth: "光滑的", smooth: "光滑的",
@@ -105,6 +108,7 @@ const msg = {
showXAxis: "显示X轴", showXAxis: "显示X轴",
showYAxis: "显示Y轴", showYAxis: "显示Y轴",
nameError: "仪表板名称不能重复", nameError: "仪表板名称不能重复",
nameEmptyError: "仪表板名称不能为空",
noRoot: "请设置根仪表板,为", noRoot: "请设置根仪表板,为",
showGroup: "显示分组", showGroup: "显示分组",
noWidget: "请添加组件", noWidget: "请添加组件",
@@ -120,6 +124,7 @@ const msg = {
editWarning: "你正在进入编辑模式", editWarning: "你正在进入编辑模式",
viewWarning: "你正在进入预览模式", viewWarning: "你正在进入预览模式",
virtualDatabase: "虚拟数据库", virtualDatabase: "虚拟数据库",
virtualCache: "虚拟缓存",
reloadDashboards: "重新加载仪表盘", reloadDashboards: "重新加载仪表盘",
kubernetesService: "服务", kubernetesService: "服务",
kubernetesCluster: "集群", kubernetesCluster: "集群",
@@ -140,6 +145,37 @@ const msg = {
interval: "刷新间隔时间", interval: "刷新间隔时间",
pause: "暂停", pause: "暂停",
begin: "开始", begin: "开始",
associateOptions: "关联选项",
associateMetrics: "关联指标",
widget: "部件",
enableAssociate: "启用关联",
nameTip: "该名称仅支持中文和英文、横线和下划线, 并且限制长度为300个字符",
duplicateName: "重复的名称",
text: "文本",
query: "查询",
postgreSQL: "PostgreSQL",
endpointTips: "这里最多展示20条endpoints。",
apisix: "APISIX",
viewTrace: "查看相关Trace",
relatedTraceOptions: "相关的Trace选项",
setLatencyDuration: "延迟相关指标",
queryOrder: "按持续时间查询",
setOrder: "查询顺序",
latency: "延迟",
metricValues: "指标值",
enableRelatedTrace: "启用相关Trace",
queryConditions: "查询条件",
maxTraceDuration: "最大持续时间",
minTraceDuration: "最小持续时间",
legendOptions: "图例选项",
showLegend: "显示图例",
asTable: "作为表格",
toTheRight: "在右边",
legendValues: "图例值",
minDuration: "最小请求持续时间",
when4xx: "当响应代码介于400和499之间时带有跟踪的HTTP请求和响应示例",
when5xx: "当响应代码介于500和599之间时带有跟踪的HTTP请求和响应示例",
taskTitle: "HTTP请求和响应收集规则",
seconds: "秒", seconds: "秒",
hourTip: "选择小时", hourTip: "选择小时",
minuteTip: "选择分钟", minuteTip: "选择分钟",
@@ -149,8 +185,6 @@ const msg = {
monthsHead: "1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月", monthsHead: "1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月",
months: "一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月", months: "一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月",
weeks: "一_二_三_四_五_六_日", weeks: "一_二_三_四_五_六_日",
hello: "你好",
helloMessage: "欢迎来到, Apache SkyWalking APM 系统 !",
username: "用户名", username: "用户名",
password: "密码", password: "密码",
title: "标题", title: "标题",
@@ -296,6 +330,7 @@ const msg = {
eventsParameters: "事件参数", eventsParameters: "事件参数",
eventDetail: "事件详情", eventDetail: "事件详情",
value: "数值", value: "数值",
key: "Key",
tableHeader: "表头名称", tableHeader: "表头名称",
tableValues: "表值", tableValues: "表值",
show: "展示", show: "展示",
@@ -336,5 +371,7 @@ const msg = {
conditionNotice: conditionNotice:
"请输入一个内容关键词或者内容不包含的关键词(key=value)之后回车", "请输入一个内容关键词或者内容不包含的关键词(key=value)之后回车",
language: "语言", language: "语言",
gateway: "网关",
virtualMQ: "虚拟消息队列",
}; };
export default msg; export default msg;

View File

@@ -25,18 +25,14 @@ export const routesAlarm: Array<RouteRecordRaw> = [
title: "alarm", title: "alarm",
icon: "spam", icon: "spam",
hasGroup: false, hasGroup: false,
exact: true,
}, },
component: Layout, component: Layout,
children: [ children: [
{ {
path: "/alarm", path: "/alerting",
name: "Alarm", name: "Alarm",
meta: {
exact: false,
},
component: () => component: () =>
import(/* webpackChunkName: "alarms" */ "@/views/Alarm.vue"), import(/* webpackChunkName: "alerting" */ "@/views/Alarm.vue"),
}, },
], ],
}, },

View File

@@ -26,7 +26,6 @@ export const routesDashboard: Array<RouteRecordRaw> = [
title: "dashboards", title: "dashboards",
icon: "dashboard_customize", icon: "dashboard_customize",
hasGroup: true, hasGroup: true,
exact: true,
}, },
children: [ children: [
{ {
@@ -38,7 +37,6 @@ export const routesDashboard: Array<RouteRecordRaw> = [
name: "List", name: "List",
meta: { meta: {
title: "dashboardList", title: "dashboardList",
exact: false,
}, },
}, },
{ {
@@ -50,73 +48,198 @@ export const routesDashboard: Array<RouteRecordRaw> = [
name: "New", name: "New",
meta: { meta: {
title: "dashboardNew", title: "dashboardNew",
exact: false,
}, },
}, },
{ {
path: "/dashboard/:layerId/:entity/:name", path: "",
redirect: "/dashboard/:layerId/:entity/:name",
name: "Create",
component: () => component: () =>
import( import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue" /* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
), ),
name: "Create",
meta: { meta: {
title: "dashboardEdit",
exact: false,
notShow: true, notShow: true,
}, },
children: [
{
path: "/dashboard/:layerId/:entity/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "CreateChild",
},
{
path: "/dashboard/:layerId/:entity/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "CreateActiveTabIndex",
},
],
}, },
{ {
path: "/dashboard/:layerId/:entity/:serviceId/:name", path: "",
component: () => component: () =>
import( import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue" /* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
), ),
name: "View", name: "View",
redirect: "/dashboard/:layerId/:entity/:serviceId/:name",
meta: { meta: {
title: "dashboardEdit",
exact: false,
notShow: true, notShow: true,
}, },
children: [
{
path: "/dashboard/:layerId/:entity/:serviceId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewChild",
},
{
path: "/dashboard/:layerId/:entity/:serviceId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewActiveTabIndex",
},
],
}, },
{ {
path: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name", path: "",
redirect:
"/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name",
component: () => component: () =>
import( import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue" /* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
), ),
name: "ViewServiceRelation", name: "ViewServiceRelation",
meta: { meta: {
title: "dashboardEdit",
exact: false,
notShow: true, notShow: true,
}, },
children: [
{
path: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewServiceRelation",
},
{
path: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewServiceRelationActiveTabIndex",
},
],
}, },
{ {
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:name", path: "",
redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:name",
component: () => component: () =>
import( import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue" /* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
), ),
name: "ViewPod", name: "ViewPod",
meta: { meta: {
title: "dashboardEdit",
exact: false,
notShow: true, notShow: true,
}, },
children: [
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewPod",
},
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewPodActiveTabIndex",
},
],
}, },
{ {
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name", path: "",
redirect:
"/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name",
component: () => component: () =>
import( import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue" /* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
), ),
name: "ViewPodRelation", name: "PodRelation",
meta: { meta: {
title: "dashboardEdit",
exact: true,
notShow: true, notShow: true,
}, },
children: [
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewPodRelation",
},
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewPodRelationActiveTabIndex",
},
],
},
{
path: "",
redirect:
"/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ProcessRelation",
meta: {
notShow: true,
},
children: [
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewProcessRelation",
},
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewProcessRelationActiveTabIndex",
},
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name/duration/:duration",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewProcessRelationDuration",
},
],
}, },
], ],
}, },

View File

@@ -14,10 +14,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesBrowser: Array<RouteRecordRaw> = [ export default [
{ {
path: "", path: "",
name: "Browser", name: "Browser",
@@ -26,18 +24,22 @@ export const routesBrowser: Array<RouteRecordRaw> = [
icon: "language", icon: "language",
}, },
redirect: "/browser", redirect: "/browser",
component: Layout,
children: [ children: [
{ {
path: "/browser", path: "/browser",
name: "Browser", name: "Browser",
meta: { meta: {
title: "browser", title: "browser",
headPath: "/browser", layer: "BROWSER",
exact: true, },
},
{
path: "/browser/tab/:activeTabIndex",
name: "BrowserActiveTabIndex",
meta: {
notShow: true,
layer: "BROWSER",
}, },
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
}, },
], ],
}, },

View File

@@ -0,0 +1,63 @@
/**
* 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 default [
{
path: "",
name: "Database",
meta: {
title: "database",
icon: "storage",
hasGroup: true,
},
redirect: "/mySQL",
children: [
{
path: "/mySQL",
name: "MySQL",
meta: {
title: "mySQL",
layer: "MYSQL",
},
},
{
path: "/mySQL/tab/:activeTabIndex",
name: "MySQLActiveTabIndex",
meta: {
notShow: true,
layer: "MYSQL",
},
},
{
path: "/postgreSQL",
name: "PostgreSQL",
meta: {
title: "postgreSQL",
layer: "POSTGRESQL",
},
},
{
path: "/postgreSQL/tab/:activeTabIndex",
name: "PostgreSQLActiveTabIndex",
meta: {
notShow: true,
layer: "POSTGRESQL",
},
},
],
},
];

View File

@@ -14,30 +14,25 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesFunctions: Array<RouteRecordRaw> = [ export default [
{ {
path: "", path: "",
name: "Functions", name: "Functions",
meta: { meta: {
title: "functions", title: "functions",
icon: "cloud_queue", icon: "cloud_queue",
layer: "FAAS",
}, },
redirect: "/functions", redirect: "/functions",
component: Layout,
children: [ children: [
{ {
path: "/functions", path: "/functions",
name: "Functions", name: "Functions",
meta: { },
title: "functions", {
headPath: "/functions", path: "/functions/tab/:activeTabIndex",
exact: true, name: "FunctionsActiveTabIndex",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
}, },
], ],
}, },

View File

@@ -14,29 +14,33 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesEvent: Array<RouteRecordRaw> = [ export default [
{ {
path: "", path: "",
name: "Events", name: "Gateway",
meta: { meta: {
title: "events", title: "gateway",
icon: "av_timer", icon: "gateway",
hasGroup: false, hasGroup: true,
exact: true,
}, },
component: Layout, redirect: "/apisix",
children: [ children: [
{ {
path: "/events", path: "/apisix",
name: "Events", name: "APISIX",
meta: { meta: {
exact: false, title: "apisix",
layer: "APISIX",
},
},
{
path: "/apisix/tab/:activeTabIndex",
name: "APISIXActiveTabIndex",
meta: {
notShow: true,
layer: "APISIX",
}, },
component: () =>
import(/* webpackChunkName: "events" */ "@/views/Event.vue"),
}, },
], ],
}, },

View File

@@ -0,0 +1,94 @@
/**
* 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 default [
{
path: "",
name: "General",
meta: {
title: "general",
icon: "chart",
hasGroup: true,
},
children: [
{
path: "/general",
name: "GeneralServices",
meta: {
title: "services",
layer: "GENERAL",
},
},
{
path: "/general/tab/:activeTabIndex",
name: "GeneralServicesActiveTabIndex",
meta: {
notShow: true,
layer: "GENERAL",
},
},
{
path: "/database",
name: "VirtualDatabase",
meta: {
title: "virtualDatabase",
layer: "VIRTUAL_DATABASE",
},
},
{
path: "/database/tab/:activeTabIndex",
name: "VirtualDatabaseActiveTabIndex",
meta: {
notShow: true,
layer: "VIRTUAL_DATABASE",
},
},
{
path: "/cache",
name: "VirtualCache",
meta: {
title: "virtualCache",
layer: "VIRTUAL_CACHE",
},
},
{
path: "/cache/tab/:activeTabIndex",
name: "VirtualCacheActiveTabIndex",
meta: {
notShow: true,
layer: "VIRTUAL_CACHE",
},
},
{
path: "/mq",
name: "VirtualMQ",
meta: {
title: "virtualMQ",
layer: "VIRTUAL_MQ",
},
},
{
path: "/mq/tab/:activeTabIndex",
name: "VirtualMQActiveTabIndex",
meta: {
notShow: true,
layer: "VIRTUAL_MQ",
},
},
],
},
];

37
src/router/data/index.ts Normal file
View File

@@ -0,0 +1,37 @@
/**
* 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 general from "./general";
import serviceMesh from "./serviceMesh";
import database from "./database";
import infrastructure from "./infrastructure";
import selfObservability from "./selfObservability";
import functions from "./functions";
import browser from "./browser";
import k8s from "./k8s";
import gateway from "./gateway";
export default [
...general,
...serviceMesh,
...functions,
...k8s,
...infrastructure,
...browser,
...gateway,
...database,
...selfObservability,
];

View File

@@ -14,31 +14,34 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesDatabase: Array<RouteRecordRaw> = [ export default [
{ {
path: "", path: "",
name: "Database", name: "Infrastructure",
meta: { meta: {
title: "database", title: "infrastructure",
icon: "storage", icon: "scatter_plot",
hasGroup: true, hasGroup: true,
}, },
redirect: "/database", redirect: "/linux",
component: Layout,
children: [ children: [
{ {
path: "/database", path: "/linux",
name: "Database", name: "Linux",
meta: { meta: {
title: "virtualDatabase", title: "linux",
headPath: "/database", layer: "OS_LINUX",
exact: true, },
},
{
path: "/linux/tab/:activeTabIndex",
name: "LinuxActiveTabIndex",
meta: {
title: "linux",
notShow: true,
layer: "OS_LINUX",
}, },
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
}, },
], ],
}, },

View File

@@ -14,10 +14,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesK8s: Array<RouteRecordRaw> = [ export default [
{ {
path: "", path: "",
name: "Kubernetes", name: "Kubernetes",
@@ -27,25 +25,42 @@ export const routesK8s: Array<RouteRecordRaw> = [
hasGroup: true, hasGroup: true,
}, },
redirect: "/kubernetes/cluster", redirect: "/kubernetes/cluster",
component: Layout,
children: [ children: [
{ {
path: "/kubernetes/cluster", path: "/kubernetes/cluster",
name: "KubernetesCluster", name: "KubernetesCluster",
meta: { meta: {
notShow: false,
title: "kubernetesCluster", title: "kubernetesCluster",
layer: "K8S",
},
},
{
path: "/kubernetes/cluster/tab/:activeTabIndex",
name: "KubernetesClusterActiveTabIndex",
meta: {
notShow: true,
title: "kubernetesClusterActiveTabIndex",
layer: "K8S",
}, },
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
}, },
{ {
path: "/kubernetes/service", path: "/kubernetes/service",
name: "KubernetesService", name: "KubernetesService",
meta: { meta: {
notShow: false,
title: "kubernetesService", title: "kubernetesService",
layer: "K8S_SERVICE",
},
},
{
path: "/kubernetes/service/tab/:activeTabIndex",
name: "KubernetesServiceActiveTabIndex",
meta: {
notShow: true,
title: "kubernetesServiceActiveTabIndex",
layer: "K8S_SERVICE",
}, },
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
}, },
], ],
}, },

View File

@@ -14,10 +14,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesSelf: Array<RouteRecordRaw> = [ export default [
{ {
path: "", path: "",
name: "SelfObservability", name: "SelfObservability",
@@ -27,27 +25,38 @@ export const routesSelf: Array<RouteRecordRaw> = [
icon: "logo", icon: "logo",
hasGroup: true, hasGroup: true,
}, },
component: Layout,
children: [ children: [
{ {
path: "/self/skyWalkingServer", path: "/self/skyWalkingServer",
name: "SkyWalkingServer", name: "SkyWalkingServer",
meta: { meta: {
title: "skyWalkingServer", title: "skyWalkingServer",
headPath: "/mesh/services", layer: "SO11Y_OAP",
},
},
{
path: "/self/skyWalkingServer/tab/:activeTabIndex",
name: "SkyWalkingServerActiveTabIndex",
meta: {
notShow: true,
layer: "SO11Y_OAP",
}, },
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
}, },
{ {
path: "/self/satellite", path: "/self/satellite",
name: "Satellite", name: "Satellite",
meta: { meta: {
title: "satellite", title: "satellite",
headPath: "/mesh/controlPanel", layer: "SO11Y_SATELLITE",
},
},
{
path: "/self/satellite/tab/:activeTabIndex",
name: "SatelliteActiveTabIndex",
meta: {
notShow: true,
layer: "SO11Y_SATELLITE",
}, },
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
}, },
], ],
}, },

View File

@@ -14,10 +14,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesMesh: Array<RouteRecordRaw> = [ export default [
{ {
path: "", path: "",
name: "ServiceMesh", name: "ServiceMesh",
@@ -27,36 +25,58 @@ export const routesMesh: Array<RouteRecordRaw> = [
icon: "epic", icon: "epic",
hasGroup: true, hasGroup: true,
}, },
component: Layout,
children: [ children: [
{ {
path: "/mesh/services", path: "/mesh/services",
name: "MeshServices", name: "MeshServices",
meta: { meta: {
notShow: false,
title: "services", title: "services",
headPath: "/mesh/services", layer: "MESH",
},
},
{
path: "/mesh/services/tab/:activeTabIndex",
name: "MeshServicesActiveTabIndex",
meta: {
notShow: true,
layer: "MESH",
}, },
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
}, },
{ {
path: "/mesh/controlPanel", path: "/mesh/controlPanel",
name: "ControlPanel", name: "ControlPanel",
meta: { meta: {
notShow: false,
title: "controlPanel", title: "controlPanel",
headPath: "/mesh/controlPanel", layer: "MESH_CP",
},
},
{
path: "/mesh/controlPanel/tab/:activeTabIndex",
name: "ControlPanelActiveTabIndex",
meta: {
notShow: true,
layer: "MESH_CP",
}, },
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
}, },
{ {
path: "/mesh/dataPanel", path: "/mesh/dataPanel",
name: "DataPanel", name: "DataPanel",
meta: { meta: {
notShow: false,
title: "dataPanel", title: "dataPanel",
headPath: "/mesh/dataPanel", layer: "MESH_DP",
},
},
{
path: "/mesh/dataPanel/tab/:activeTabIndex",
name: "DataPanelActiveTabIndex",
meta: {
notShow: true,
title: "dataPanelActiveTabIndex",
layer: "MESH_DP",
}, },
component: () => import("@/views/Layer.vue"),
}, },
], ],
}, },

View File

@@ -15,31 +15,15 @@
* limitations under the License. * limitations under the License.
*/ */
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router"; import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import { routesGen } from "./general";
import { routesMesh } from "./serviceMesh";
import { routesDatabase } from "./database";
import { routesInfra } from "./infrastructure";
import { routesDashboard } from "./dashboard"; import { routesDashboard } from "./dashboard";
import { routesEvent } from "./event";
import { routesSetting } from "./setting"; import { routesSetting } from "./setting";
import { routesAlarm } from "./alarm"; import { routesAlarm } from "./alarm";
import { routesSelf } from "./selfObservability"; import routesLayers from "./layer";
import { routesFunctions } from "./functions";
import { routesBrowser } from "./browser";
import { routesK8s } from "./k8s";
const routes: Array<RouteRecordRaw> = [ const routes: Array<RouteRecordRaw> = [
...routesGen, ...routesLayers,
...routesMesh,
...routesFunctions,
...routesK8s,
...routesInfra,
...routesBrowser,
...routesDatabase,
...routesSelf,
...routesDashboard, ...routesDashboard,
...routesAlarm, ...routesAlarm,
...routesEvent,
...routesSetting, ...routesSetting,
]; ];

View File

@@ -1,60 +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 { RouteRecordRaw } from "vue-router";
import Layout from "@/layout/Index.vue";
export const routesInfra: Array<RouteRecordRaw> = [
{
path: "",
name: "Infrastructure",
meta: {
title: "infrastructure",
icon: "scatter_plot",
exact: true,
hasGroup: true,
},
redirect: "/linux",
component: Layout,
children: [
{
path: "/linux",
name: "Linux",
meta: {
title: "linux",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
// {
// path: "/infrastructure/vm",
// name: "VirtualMachine",
// meta: {
// title: "virtualMachine",
// },
// component: () => import("@/views/infrastructure/Infrastructure.vue"),
// },
// {
// path: "/infrastructure/k8s",
// name: "Kubernetes",
// meta: {
// title: "kubernetes",
// },
// component: () => import("@/views/infrastructure/Infrastructure.vue"),
// },
],
},
];

View File

@@ -14,32 +14,22 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { RouteRecordRaw } from "vue-router"; import LayerJson from "./data";
import Layout from "@/layout/Index.vue"; import Layout from "@/layout/Index.vue";
export const routesGen: Array<RouteRecordRaw> = [ function layerDashboards() {
{ const routes = LayerJson.map((item: any) => {
path: "", item.component = Layout;
name: "General", if (item.children) {
meta: { item.children = item.children.map((d: any) => {
title: "general", d.component = () =>
icon: "chart", import(/* webpackChunkName: "layer" */ "@/views/Layer.vue");
hasGroup: false, return d;
exact: true, });
}, }
component: Layout, return item;
children: [ });
{ return routes;
path: "/general", }
name: "GeneralServices",
meta: { export default layerDashboards();
title: "services",
headPath: "/general/service",
exact: true,
},
component: () =>
import(/* webpackChunkName: "layers" */ "@/views/Layer.vue"),
},
],
},
];

View File

@@ -25,7 +25,6 @@ export const routesSetting: Array<RouteRecordRaw> = [
title: "settings", title: "settings",
icon: "settings", icon: "settings",
hasGroup: false, hasGroup: false,
exact: false,
}, },
component: Layout, component: Layout,
children: [ children: [
@@ -36,7 +35,6 @@ export const routesSetting: Array<RouteRecordRaw> = [
title: "settings", title: "settings",
icon: "settings", icon: "settings",
hasGroup: false, hasGroup: false,
exact: false,
}, },
component: () => component: () =>
import(/* webpackChunkName: "settings" */ "@/views/Settings.vue"), import(/* webpackChunkName: "settings" */ "@/views/Settings.vue"),

View File

@@ -29,3 +29,11 @@ export const TextConfig = {
fontSize: 14, fontSize: 14,
textAlign: "left", textAlign: "left",
}; };
export const TimeRangeConfig = {
fontColor: "black",
backgroundColor: "white",
fontSize: 14,
textAlign: "center",
text: "text",
};

View File

@@ -34,6 +34,7 @@ interface AppState {
pageTitle: string; pageTitle: string;
version: string; version: string;
isMobile: boolean; isMobile: boolean;
reloadTimer: Nullable<any>;
} }
export const appStore = defineStore({ export const appStore = defineStore({
@@ -53,6 +54,7 @@ export const appStore = defineStore({
pageTitle: "", pageTitle: "",
version: "", version: "",
isMobile: false, isMobile: false,
reloadTimer: null,
}), }),
getters: { getters: {
duration(): Duration { duration(): Duration {
@@ -69,7 +71,7 @@ export const appStore = defineStore({
step: this.duration.step, step: this.duration.step,
}; };
}, },
intervalTime(): string[] { intervalUnix(): number[] {
let interval = 946080000000; let interval = 946080000000;
switch (this.duration.step) { switch (this.duration.step) {
case "MINUTE": case "MINUTE":
@@ -95,12 +97,17 @@ export const appStore = defineStore({
this.utcMin * 60000; this.utcMin * 60000;
const startUnix: number = this.duration.start.getTime(); const startUnix: number = this.duration.start.getTime();
const endUnix: number = this.duration.end.getTime(); const endUnix: number = this.duration.end.getTime();
const timeIntervals: string[] = []; const timeIntervals: number[] = [];
for (let i = 0; i <= endUnix - startUnix; i += interval) { for (let i = 0; i <= endUnix - startUnix; i += interval) {
const temp: string = dateFormatTime( timeIntervals.push(startUnix + i - utcSpace);
new Date(startUnix + i - utcSpace), }
this.duration.step return timeIntervals;
); },
intervalTime(): string[] {
const arr = this.intervalUnix;
const timeIntervals: string[] = [];
for (const item of arr) {
const temp: string = dateFormatTime(new Date(item), this.duration.step);
timeIntervals.push(temp); timeIntervals.push(temp);
} }
return timeIntervals; return timeIntervals;
@@ -117,12 +124,18 @@ export const appStore = defineStore({
} }
this.runEventStack(); this.runEventStack();
}, },
updateDurationRow(data: Duration) {
this.durationRow = data;
},
setUTC(utcHour: number, utcMin: number): void { setUTC(utcHour: number, utcMin: number): void {
this.runEventStack(); this.runEventStack();
this.utcMin = utcMin; this.utcMin = utcMin;
this.utcHour = utcHour; this.utcHour = utcHour;
this.utc = `${utcHour}:${utcMin}`; this.utc = `${utcHour}:${utcMin}`;
}, },
updateUTC(data: string) {
this.utc = data;
},
setIsMobile(mode: boolean) { setIsMobile(mode: boolean) {
this.isMobile = mode; this.isMobile = mode;
}, },
@@ -153,10 +166,9 @@ export const appStore = defineStore({
.params({}); .params({});
if (res.data.errors) { if (res.data.errors) {
this.utc = -(new Date().getTimezoneOffset() / 60) + ":0"; this.utc = -(new Date().getTimezoneOffset() / 60) + ":0";
return res.data; } else {
this.utc = res.data.data.getTimeInfo.timezone / 100 + ":0";
} }
this.utc = res.data.data.getTimeInfo.timezone / 100 + ":0";
const utcArr = this.utc.split(":"); const utcArr = this.utc.split(":");
this.utcHour = isNaN(Number(utcArr[0])) ? 0 : Number(utcArr[0]); this.utcHour = isNaN(Number(utcArr[0])) ? 0 : Number(utcArr[0]);
this.utcMin = isNaN(Number(utcArr[1])) ? 0 : Number(utcArr[1]); this.utcMin = isNaN(Number(utcArr[1])) ? 0 : Number(utcArr[1]);
@@ -173,6 +185,9 @@ export const appStore = defineStore({
this.version = res.data.data.version; this.version = res.data.data.version;
return res.data; return res.data;
}, },
setReloadTimer(timer: any): void {
this.reloadTimer = timer;
},
}, },
}); });
export function useAppStoreWithOut(): any { export function useAppStoreWithOut(): any {

View File

@@ -21,7 +21,7 @@ import graphql from "@/graphql";
import query from "@/graphql/fetch"; import query from "@/graphql/fetch";
import { DashboardItem } from "@/types/dashboard"; import { DashboardItem } from "@/types/dashboard";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { NewControl, TextConfig } from "../data"; import { NewControl, TextConfig, TimeRangeConfig } from "../data";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
@@ -39,6 +39,7 @@ interface DashboardState {
dashboards: DashboardItem[]; dashboards: DashboardItem[];
currentDashboard: Nullable<DashboardItem>; currentDashboard: Nullable<DashboardItem>;
editMode: boolean; editMode: boolean;
currentTabIndex: number;
} }
export const dashboardStore = defineStore({ export const dashboardStore = defineStore({
@@ -56,6 +57,7 @@ export const dashboardStore = defineStore({
dashboards: [], dashboards: [],
currentDashboard: null, currentDashboard: null,
editMode: false, editMode: false,
currentTabIndex: 0,
}), }),
actions: { actions: {
setLayout(data: LayoutConfig[]) { setLayout(data: LayoutConfig[]) {
@@ -80,6 +82,7 @@ export const dashboardStore = defineStore({
const newItem: LayoutConfig = { const newItem: LayoutConfig = {
...NewControl, ...NewControl,
i: index, i: index,
id: index,
type, type,
metricTypes: [""], metricTypes: [""],
metrics: [""], metrics: [""],
@@ -111,11 +114,14 @@ export const dashboardStore = defineStore({
}; };
} }
if ( if (
type === "Trace" || [
type === "Profile" || "Trace",
type === "Log" || "Profile",
type === "Ebpf" || "Log",
type === "DemandLog" "DemandLog",
"Ebpf",
"NetworkProfiling",
].includes(type)
) { ) {
newItem.h = 36; newItem.h = 36;
} }
@@ -123,6 +129,11 @@ export const dashboardStore = defineStore({
newItem.h = 6; newItem.h = 6;
newItem.graph = TextConfig; newItem.graph = TextConfig;
} }
if (type === "TimeRange") {
newItem.w = 8;
newItem.h = 6;
newItem.graph = TimeRangeConfig;
}
this.activedGridItem = newItem.i; this.activedGridItem = newItem.i;
this.selectedGrid = newItem; this.selectedGrid = newItem;
this.layout = this.layout.map((d: LayoutConfig) => { this.layout = this.layout.map((d: LayoutConfig) => {
@@ -158,9 +169,11 @@ export const dashboardStore = defineStore({
if (!children.length) { if (!children.length) {
index = "0"; index = "0";
} }
const id = `${activedGridItem}-${tabIndex}-${index}`;
const newItem: LayoutConfig = { const newItem: LayoutConfig = {
...NewControl, ...NewControl,
i: index, i: index,
id,
type, type,
metricTypes: [""], metricTypes: [""],
metrics: [""], metrics: [""],
@@ -172,11 +185,14 @@ export const dashboardStore = defineStore({
}; };
} }
if ( if (
type === "Trace" || [
type === "Profile" || "Trace",
type === "Log" || "Profile",
type === "DemandLog" || "Log",
type === "Ebpf" "DemandLog",
"Ebpf",
"NetworkProfiling",
].includes(type)
) { ) {
newItem.h = 32; newItem.h = 32;
} }
@@ -184,6 +200,11 @@ export const dashboardStore = defineStore({
newItem.h = 6; newItem.h = 6;
newItem.graph = TextConfig; newItem.graph = TextConfig;
} }
if (type === "TimeRange") {
newItem.w = 8;
newItem.h = 6;
newItem.graph = TextConfig;
}
if (this.layout[idx].children) { if (this.layout[idx].children) {
const items = children.map((d: LayoutConfig) => { const items = children.map((d: LayoutConfig) => {
d.y = d.y + newItem.h; d.y = d.y + newItem.h;
@@ -198,6 +219,7 @@ export const dashboardStore = defineStore({
this.activedGridItem = index; this.activedGridItem = index;
}, },
setActiveTabIndex(index: number, target?: number) { setActiveTabIndex(index: number, target?: number) {
this.currentTabIndex = index;
const m = target || this.activedGridItem; const m = target || this.activedGridItem;
const idx = this.layout.findIndex((d: LayoutConfig) => d.i === m); const idx = this.layout.findIndex((d: LayoutConfig) => d.i === m);
if (idx < 0) { if (idx < 0) {
@@ -287,6 +309,28 @@ export const dashboardStore = defineStore({
}; };
this.selectedGrid = this.layout[index]; this.selectedGrid = this.layout[index];
}, },
setWidget(param: LayoutConfig) {
for (let i = 0; i < this.layout.length; i++) {
if (this.layout[i].type === "Tab") {
if (this.layout[i].children && this.layout[i].children.length) {
for (const child of this.layout[i].children) {
if (child.children && child.children.length) {
for (let c = 0; c < child.children.length; c++) {
if (child.children[c].id === param.id) {
child.children.splice(c, 1, param);
return;
}
}
}
}
}
} else {
if (this.layout[i].id === param.id) {
this.layout.splice(i, 1, param);
}
}
}
},
async fetchMetricType(item: string) { async fetchMetricType(item: string) {
const res: AxiosResponse = await graphql const res: AxiosResponse = await graphql
.query("queryTypeOfMetrics") .query("queryTypeOfMetrics")

View File

@@ -25,8 +25,7 @@ import {
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
interface EbpfState {
interface EbpfStore {
taskList: EBPFTaskList[]; taskList: EBPFTaskList[];
eBPFSchedules: EBPFProfilingSchedule[]; eBPFSchedules: EBPFProfilingSchedule[];
currentSchedule: EBPFProfilingSchedule | Record<string, never>; currentSchedule: EBPFProfilingSchedule | Record<string, never>;
@@ -40,7 +39,7 @@ interface EbpfStore {
export const ebpfStore = defineStore({ export const ebpfStore = defineStore({
id: "eBPF", id: "eBPF",
state: (): EbpfStore => ({ state: (): EbpfState => ({
taskList: [], taskList: [],
eBPFSchedules: [], eBPFSchedules: [],
currentSchedule: {}, currentSchedule: {},
@@ -53,7 +52,7 @@ export const ebpfStore = defineStore({
}), }),
actions: { actions: {
setSelectedTask(task: EBPFTaskList) { setSelectedTask(task: EBPFTaskList) {
this.selectedTask = task; this.selectedTask = task || {};
}, },
setCurrentSchedule(s: EBPFProfilingSchedule) { setCurrentSchedule(s: EBPFProfilingSchedule) {
this.currentSchedule = s; this.currentSchedule = s;
@@ -84,22 +83,31 @@ export const ebpfStore = defineStore({
if (res.data.errors) { if (res.data.errors) {
return res.data; return res.data;
} }
this.getTaskList(param.serviceId); this.getTaskList({
serviceId: param.serviceId,
targets: ["ON_CPU", "OFF_CPU"],
});
return res.data; return res.data;
}, },
async getTaskList(serviceId: string) { async getTaskList(params: {
if (!serviceId) { serviceId: string;
serviceInstanceId: string;
targets: string[];
}) {
if (!params.serviceId) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql const res: AxiosResponse = await graphql
.query("getEBPFTasks") .query("getEBPFTasks")
.params({ serviceId }); .params(params);
this.tip = ""; this.tip = "";
if (res.data.errors) { if (res.data.errors) {
return res.data; return res.data;
} }
this.taskList = res.data.data.queryEBPFTasks || []; this.taskList = res.data.data.queryEBPFTasks || [];
this.selectedTask = this.taskList[0] || {};
this.setSelectedTask(this.selectedTask);
if (!this.taskList.length) { if (!this.taskList.length) {
return res.data; return res.data;
} }

View File

@@ -19,16 +19,16 @@ import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import { Event, QueryEventCondition } from "@/types/events"; import { Event, QueryEventCondition } from "@/types/events";
import { Instance, Endpoint, Service } from "@/types/selector"; import { Instance, Endpoint } from "@/types/selector";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
interface eventState { interface eventState {
loading: boolean; loading: boolean;
events: Event[]; events: Event[];
services: Service[];
instances: Instance[]; instances: Instance[];
endpoints: Endpoint[]; endpoints: Endpoint[];
condition: QueryEventCondition | any; condition: Nullable<QueryEventCondition>;
} }
export const eventStore = defineStore({ export const eventStore = defineStore({
@@ -36,32 +36,18 @@ export const eventStore = defineStore({
state: (): eventState => ({ state: (): eventState => ({
loading: false, loading: false,
events: [], events: [],
services: [{ value: "", label: "All" }],
instances: [{ value: "", label: "All" }], instances: [{ value: "", label: "All" }],
endpoints: [{ value: "", label: "All" }], endpoints: [{ value: "", label: "All" }],
condition: { condition: null,
paging: { pageNum: 1, pageSize: 15 },
},
}), }),
actions: { actions: {
setEventCondition(data: any) { setEventCondition(data: QueryEventCondition) {
this.condition = { ...this.condition, ...data }; this.condition = data;
}, },
async getServices(layer: string) { async getInstances() {
if (!layer) { const serviceId = useSelectorStore().currentService
this.services = [{ value: "", label: "All" }]; ? useSelectorStore().currentService.id
return new Promise((resolve) => resolve([])); : "";
}
const res: AxiosResponse = await graphql.query("queryServices").params({
layer,
});
if (res.data.errors) {
return res.data;
}
this.services = res.data.data.services;
return res.data;
},
async getInstances(serviceId: string) {
const res: AxiosResponse = await graphql.query("queryInstances").params({ const res: AxiosResponse = await graphql.query("queryInstances").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
@@ -75,7 +61,13 @@ export const eventStore = defineStore({
]; ];
return res.data; return res.data;
}, },
async getEndpoints(serviceId: string) { async getEndpoints() {
const serviceId = useSelectorStore().currentService
? useSelectorStore().currentService.id
: "";
if (!serviceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryEndpoints").params({ const res: AxiosResponse = await graphql.query("queryEndpoints").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
@@ -112,6 +104,9 @@ export const eventStore = defineStore({
scope = "Endpoint"; scope = "Endpoint";
} }
item.scope = scope; item.scope = scope;
if (!item.endTime || item.endTime === item.startTime) {
item.endTime = Number(item.startTime) + 60000;
}
return item; return item;
} }
); );

View File

@@ -53,6 +53,13 @@ export const logStore = defineStore({
setLogCondition(data: any) { setLogCondition(data: any) {
this.conditions = { ...this.conditions, ...data }; this.conditions = { ...this.conditions, ...data };
}, },
resetState() {
this.logs = [];
this.conditions = {
queryDuration: useAppStoreWithOut().durationTime,
paging: { pageNum: 1, pageSize: 15 },
};
},
async getServices(layer: string) { async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({ const res: AxiosResponse = await graphql.query("queryServices").params({
layer, layer,

View File

@@ -0,0 +1,203 @@
/**
* 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 { defineStore } from "pinia";
import { EBPFTaskList, ProcessNode } from "@/types/ebpf";
import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
import { Call } from "@/types/topology";
import { LayoutConfig } from "@/types/dashboard";
import { ElMessage } from "element-plus";
interface NetworkProfilingState {
networkTasks: EBPFTaskList[];
networkTip: string;
selectedNetworkTask: Recordable<EBPFTaskList>;
nodes: ProcessNode[];
calls: Call[];
node: Nullable<ProcessNode>;
call: Nullable<Call>;
metricsLayout: LayoutConfig[];
selectedMetric: Nullable<LayoutConfig>;
activeMetricIndex: string;
aliveNetwork: boolean;
loadNodes: boolean;
}
export const networkProfilingStore = defineStore({
id: "networkProfiling",
state: (): NetworkProfilingState => ({
networkTasks: [],
networkTip: "",
selectedNetworkTask: {},
nodes: [],
calls: [],
node: null,
call: null,
metricsLayout: [],
selectedMetric: null,
activeMetricIndex: "",
aliveNetwork: false,
loadNodes: false,
}),
actions: {
setSelectedNetworkTask(task: EBPFTaskList) {
this.selectedNetworkTask = task || {};
},
setNode(node: Node) {
this.node = node;
},
setLink(link: Call) {
this.call = link;
},
setMetricsLayout(layout: LayoutConfig[]) {
this.metricsLayout = layout;
},
setSelectedMetric(item: LayoutConfig) {
this.selectedMetric = item;
},
setActiveItem(index: string) {
this.activeMetricIndex = index;
},
setTopology(data: { nodes: ProcessNode[]; calls: Call[] }) {
const obj = {} as any;
let calls = (data.calls || []).reduce((prev: Call[], next: Call) => {
if (!obj[next.id]) {
obj[next.id] = true;
next.value = next.value || 1;
for (const node of data.nodes) {
if (next.source === node.id) {
next.sourceObj = node;
}
if (next.target === node.id) {
next.targetObj = node;
}
}
next.value = next.value || 1;
prev.push(next);
}
return prev;
}, []);
const param = {} as any;
calls = data.calls.reduce((prev: (Call | any)[], next: Call | any) => {
if (param[next.targetId + next.sourceId]) {
next.lowerArc = true;
}
param[next.sourceId + next.targetId] = true;
next.sourceId = next.source;
next.targetId = next.target;
next.source = next.sourceObj;
next.target = next.targetObj;
delete next.sourceObj;
delete next.targetObj;
prev.push(next);
return prev;
}, []);
this.calls = calls;
this.nodes = data.nodes;
},
async createNetworkTask(
instanceId: string,
params: {
uriRegex: string;
when4xx: string;
when5xx: string;
minDuration: number;
}[]
) {
const res: AxiosResponse = await graphql
.query("newNetworkProfiling")
.params({
request: {
instanceId,
samplings: params,
},
});
if (res.data.errors) {
return res.data;
}
return res.data;
},
async getTaskList(params: {
serviceId: string;
serviceInstanceId: string;
targets: string[];
}) {
if (!params.serviceId) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql
.query("getEBPFTasks")
.params(params);
this.networkTip = "";
if (res.data.errors) {
return res.data;
}
this.networkTasks = res.data.data.queryEBPFTasks || [];
this.selectedNetworkTask = this.networkTasks[0] || {};
this.setSelectedNetworkTask(this.selectedNetworkTask);
if (!this.networkTasks.length) {
this.nodes = [];
this.calls = [];
}
return res.data;
},
async keepNetworkProfiling(taskId: string) {
if (!taskId) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql
.query("aliveNetworkProfiling")
.params({ taskId });
this.aliveMessage = "";
if (res.data.errors) {
return res.data;
}
this.aliveNetwork = res.data.data.keepEBPFNetworkProfiling.status;
if (!this.aliveNetwork) {
ElMessage.warning(res.data.data.keepEBPFNetworkProfiling.errorReason);
}
return res.data;
},
async getProcessTopology(params: {
duration: any;
serviceInstanceId: string;
}) {
this.loadNodes = true;
const res: AxiosResponse = await graphql
.query("getProcessTopology")
.params(params);
this.loadNodes = false;
if (res.data.errors) {
this.nodes = [];
this.calls = [];
return res.data;
}
const { topology } = res.data.data;
this.setTopology(topology);
return res.data;
},
},
});
export function useNetworkProfilingStore(): any {
return networkProfilingStore(store);
}

View File

@@ -83,7 +83,7 @@ export const profileStore = defineStore({
if (res.data.errors) { if (res.data.errors) {
return res.data; return res.data;
} }
this.endpoints = [{ value: "", label: "All" }, ...res.data.data.pods]; this.endpoints = res.data.data.pods || [];
return res.data; return res.data;
}, },
async getTaskEndpoints(serviceId: string, keyword?: string) { async getTaskEndpoints(serviceId: string, keyword?: string) {

View File

@@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { Service, Instance, Endpoint } from "@/types/selector"; import { Service, Instance, Endpoint, Process } from "@/types/selector";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
@@ -24,11 +24,15 @@ interface SelectorState {
services: Service[]; services: Service[];
destServices: Service[]; destServices: Service[];
pods: Array<Instance | Endpoint>; pods: Array<Instance | Endpoint>;
processes: Process[];
destProcesses: Process[];
currentService: Nullable<Service>; currentService: Nullable<Service>;
currentPod: Nullable<Instance | Endpoint>; currentPod: Nullable<Instance | Endpoint>;
currentProcess: Nullable<Process>;
currentDestService: Nullable<Service>; currentDestService: Nullable<Service>;
currentDestPod: Nullable<Instance | Endpoint>; currentDestPod: Nullable<Instance | Endpoint>;
destPods: Array<Instance | Endpoint>; destPods: Array<Instance | Endpoint>;
currentDestProcess: Nullable<Process>;
} }
export const selectorStore = defineStore({ export const selectorStore = defineStore({
@@ -38,10 +42,14 @@ export const selectorStore = defineStore({
destServices: [], destServices: [],
pods: [], pods: [],
destPods: [], destPods: [],
processes: [],
destProcesses: [],
currentService: null, currentService: null,
currentPod: null, currentPod: null,
currentProcess: null,
currentDestService: null, currentDestService: null,
currentDestPod: null, currentDestPod: null,
currentDestProcess: null,
}), }),
actions: { actions: {
setCurrentService(service: Nullable<Service>) { setCurrentService(service: Nullable<Service>) {
@@ -56,6 +64,18 @@ export const selectorStore = defineStore({
setCurrentDestPod(pod: Nullable<Instance | Endpoint>) { setCurrentDestPod(pod: Nullable<Instance | Endpoint>) {
this.currentDestPod = pod; this.currentDestPod = pod;
}, },
setCurrentProcess(process: Nullable<Process>) {
this.currentProcess = process;
},
setCurrentDestProcess(process: Nullable<Process>) {
this.currentDestProcess = process;
},
setDestPods(pods: Array<Instance | Endpoint>) {
this.destPods = pods;
},
setDestProcesses(processes: Array<Process>) {
this.destProcesses = processes;
},
async fetchLayers(): Promise<AxiosResponse> { async fetchLayers(): Promise<AxiosResponse> {
const res: AxiosResponse = await graphql.query("queryLayers").params({}); const res: AxiosResponse = await graphql.query("queryLayers").params({});
@@ -93,6 +113,27 @@ export const selectorStore = defineStore({
} }
return res.data; return res.data;
}, },
async getProcesses(param?: {
instanceId: string;
isRelation: boolean;
}): Promise<Nullable<AxiosResponse>> {
const instanceId = param ? param.instanceId : this.currentPod?.id;
if (!instanceId) {
return null;
}
const res: AxiosResponse = await graphql.query("queryProcesses").params({
instanceId,
duration: useAppStoreWithOut().durationTime,
});
if (!res.data.errors) {
if (param && param.isRelation) {
this.destProcesses = res.data.data.processes || [];
return res.data;
}
this.processes = res.data.data.processes || [];
}
return res.data;
},
async getEndpoints(params: { async getEndpoints(params: {
keyword?: string; keyword?: string;
serviceId?: string; serviceId?: string;
@@ -176,6 +217,25 @@ export const selectorStore = defineStore({
this.pods = [res.data.data.endpoint]; this.pods = [res.data.data.endpoint];
} }
return res.data;
},
async getProcess(instanceId: string, isRelation?: boolean) {
if (!instanceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryProcess").params({
instanceId,
});
if (!res.data.errors) {
if (isRelation) {
this.currentDestProcess = res.data.data.process || null;
this.destProcesses = [res.data.data.process];
return;
}
this.currentProcess = res.data.data.process || null;
this.processes = [res.data.data.process];
}
return res.data; return res.data;
}, },
}, },

View File

@@ -23,7 +23,7 @@ import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import query from "@/graphql/fetch"; import query from "@/graphql/fetch";
import { useQueryTopologyMetrics } from "@/hooks/useProcessor"; import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
interface MetricVal { interface MetricVal {

View File

@@ -22,6 +22,7 @@ import graphql from "@/graphql";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { QueryOrders } from "@/views/dashboard/data";
interface TraceState { interface TraceState {
services: Service[]; services: Service[];
@@ -47,7 +48,7 @@ export const traceStore = defineStore({
conditions: { conditions: {
queryDuration: useAppStoreWithOut().durationTime, queryDuration: useAppStoreWithOut().durationTime,
traceState: "ALL", traceState: "ALL",
queryOrder: "BY_START_TIME", queryOrder: QueryOrders[0].value,
paging: { pageNum: 1, pageSize: 20 }, paging: { pageNum: 1, pageSize: 20 },
}, },
traceSpanLogs: [], traceSpanLogs: [],
@@ -63,6 +64,17 @@ export const traceStore = defineStore({
setTraceSpans(spans: Span) { setTraceSpans(spans: Span) {
this.traceSpans = spans; this.traceSpans = spans;
}, },
resetState() {
this.traceSpans = [];
this.traceList = [];
this.currentTrace = {};
this.conditions = {
queryDuration: useAppStoreWithOut().durationTime,
paging: { pageNum: 1, pageSize: 20 },
traceState: "ALL",
queryOrder: QueryOrders[0].value,
};
},
async getServices(layer: string) { async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({ const res: AxiosResponse = await graphql.query("queryServices").params({
layer, layer,
@@ -73,6 +85,36 @@ export const traceStore = defineStore({
this.services = res.data.data.services; this.services = res.data.data.services;
return res.data; return res.data;
}, },
async getService(serviceId: string) {
if (!serviceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryService").params({
serviceId,
});
return res.data;
},
async getInstance(instanceId: string) {
if (!instanceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryInstance").params({
instanceId,
});
return res.data;
},
async getEndpoint(endpointId: string) {
if (!endpointId) {
return;
}
const res: AxiosResponse = await graphql.query("queryEndpoint").params({
endpointId,
});
return res.data;
},
async getInstances(id: string) { async getInstances(id: string) {
const serviceId = this.selectorStore.currentService const serviceId = this.selectorStore.currentService
? this.selectorStore.currentService.id ? this.selectorStore.currentService.id
@@ -133,7 +175,8 @@ export const traceStore = defineStore({
if (res.data.errors) { if (res.data.errors) {
return res.data; return res.data;
} }
this.setTraceSpans(res.data.data.trace.spans || []); const data = res.data.data.trace.spans;
this.setTraceSpans(data || []);
return res.data; return res.data;
}, },
async getSpanLogs(params: any) { async getSpanLogs(params: any) {

View File

@@ -17,6 +17,7 @@
import "element-plus/es/components/message/style/css"; import "element-plus/es/components/message/style/css";
import "element-plus/es/components/message-box/style/css"; import "element-plus/es/components/message-box/style/css";
import "element-plus/es/components/notification/style/css"; import "element-plus/es/components/notification/style/css";
import "element-plus/es/components/drawer/style/css";
import "./grid.scss"; import "./grid.scss";
import "./lib.scss"; import "./lib.scss";
import "./reset.scss"; import "./reset.scss";

View File

@@ -173,7 +173,7 @@
} }
.scroll_bar_style::-webkit-scrollbar { .scroll_bar_style::-webkit-scrollbar {
width: 9px; width: 4px;
height: 4px; height: 4px;
background-color: #eee; background-color: #eee;
} }

View File

@@ -139,8 +139,10 @@ pre {
padding-left: 56px !important; padding-left: 56px !important;
} }
.el-icon.menu-icons { .el-sub-menu__title {
margin-top: -3px !important; .el-icon.menu-icons {
margin-top: -5px !important;
}
} }
.el-switch__label--left { .el-switch__label--left {
@@ -151,6 +153,55 @@ pre {
margin-left: 5px; margin-left: 5px;
} }
.el-switch__label * {
font-size: 12px;
}
.el-drawer__header {
margin-bottom: 0;
padding-left: 10px;
font-size: 16px;
}
.el-drawer__body {
padding: 0;
}
.switch { .switch {
margin: 0 5px; margin: 0 5px;
} }
div.vis-tooltip {
max-width: 600px;
overflow: hidden;
background-color: #fff !important;
white-space: normal !important;
font-size: 12px !important;
}
.vis-item {
cursor: pointer;
height: 20px;
}
.vis-item.Error {
background-color: #e66;
opacity: 0.8;
border-color: #e66;
color: #fff !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;
}

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

@@ -32,3 +32,18 @@ export type Paging = {
pageNum: number; pageNum: number;
pageSize: number; pageSize: number;
}; };
export type EventParams = {
componentType: string;
seriesType: string;
seriesIndex: number;
seriesName: string;
name: string;
dataIndex: number;
data: unknown;
dataType: string;
value: number | Array;
color: string;
event: any;
dataIndex: number;
};

View File

@@ -9,6 +9,7 @@ declare module '@vue/runtime-core' {
ElCollapse: typeof import('element-plus/es')['ElCollapse'] ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElDialog: typeof import('element-plus/es')['ElDialog'] ElDialog: typeof import('element-plus/es')['ElDialog']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElDropdown: typeof import('element-plus/es')['ElDropdown'] ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
@@ -24,6 +25,7 @@ declare module '@vue/runtime-core' {
ElPopover: typeof import('element-plus/es')['ElPopover'] ElPopover: typeof import('element-plus/es')['ElPopover']
ElProgress: typeof import('element-plus/es')['ElProgress'] ElProgress: typeof import('element-plus/es')['ElProgress']
ElRadio: typeof import('element-plus/es')['ElRadio'] ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElSelect: typeof import('element-plus/es')['ElSelect'] ElSelect: typeof import('element-plus/es')['ElSelect']
ElSlider: typeof import('element-plus/es')['ElSlider'] ElSlider: typeof import('element-plus/es')['ElSlider']

View File

@@ -1,3 +1,4 @@
import { DurationTime } from "@/types/app";
/** /**
* Licensed to the Apache Software Foundation (ASF) under one or more * Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with * contributor license agreements. See the NOTICE file distributed with
@@ -36,7 +37,35 @@ export interface LayoutConfig {
children?: { name: string; children: LayoutConfig[] }[]; children?: { name: string; children: LayoutConfig[] }[];
activedTabIndex?: number; activedTabIndex?: number;
metricConfig?: MetricConfigOpt[]; metricConfig?: MetricConfigOpt[];
id?: string;
associate?: { widgetId: string }[];
eventAssociate?: boolean;
filters?: Filters;
relatedTrace?: RelatedTrace;
} }
export type RelatedTrace = {
duration: DurationTime;
status: string;
queryOrder: string;
latency: boolean;
enableRelate: boolean;
};
export type Filters = {
dataIndex: number;
sourceId: string;
isRange?: boolean;
duration?: {
startTime: string;
endTime: string;
};
traceId?: string;
spanId?: string;
segmentId?: string;
id?: string;
queryOrder?: string;
status?: string;
};
export type MetricConfigOpt = { export type MetricConfigOpt = {
unit?: string; unit?: string;
@@ -45,9 +74,11 @@ export type MetricConfigOpt = {
labelsIndex: string; labelsIndex: string;
sortOrder: string; sortOrder: string;
topN?: number; topN?: number;
index?: number;
}; };
export interface WidgetConfig { export interface WidgetConfig {
name?: string;
title?: string; title?: string;
tips?: string; tips?: string;
} }
@@ -64,6 +95,7 @@ export type GraphConfig =
export interface BarConfig { export interface BarConfig {
type?: string; type?: string;
showBackground?: boolean; showBackground?: boolean;
legend?: LegendOptions;
} }
export interface LineConfig extends AreaConfig { export interface LineConfig extends AreaConfig {
type?: string; type?: string;
@@ -79,6 +111,7 @@ export interface LineConfig extends AreaConfig {
export interface AreaConfig { export interface AreaConfig {
type?: string; type?: string;
opacity?: number; opacity?: number;
legend?: LegendOptions;
} }
export interface CardConfig { export interface CardConfig {
@@ -137,3 +170,25 @@ export interface TopologyConfig {
depth?: number; depth?: number;
showDepth?: boolean; showDepth?: boolean;
} }
export type EventParams = {
componentType: string;
seriesType: string;
seriesIndex: number;
seriesName: string;
name: string;
dataIndex: number;
data: Record<string, unknown>;
dataType: string;
value: number | number[];
color: string;
};
export type LegendOptions = {
show: boolean;
total: boolean;
min: boolean;
max: boolean;
mean: boolean;
asTable: boolean;
toTheRight: boolean;
width: number;
};

32
src/types/ebpf.d.ts vendored
View File

@@ -15,6 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { Process } from "./selector";
export interface EBPFTaskCreationRequest { export interface EBPFTaskCreationRequest {
serviceId: string; serviceId: string;
processLabels: string[]; processLabels: string[];
@@ -43,18 +44,7 @@ export interface EBPFProfilingSchedule {
startTime: number; startTime: number;
} }
export type Process = { export type Process = Process;
id: string;
name: string;
serviceId: string;
serviceName: string;
instanceId: string;
instanceName: string;
agentId: string;
detectType: string;
attributes: { name: string; value: string }[];
labels: string[];
};
export type StackElement = { export type StackElement = {
id: string; id: string;
originId: string; originId: string;
@@ -75,3 +65,21 @@ export type AnalyzationTrees = {
dumpCount: number; dumpCount: number;
stackType: string; stackType: string;
}; };
export type ProcessNode = {
id: string;
name: string;
serviceId: string;
serviceName: string;
serviceInstanceId: string;
serviceInstanceName: string;
name: string;
isReal: boolean;
x?: number;
y?: number;
};
export interface NetworkProfilingRequest {
uriRegex: string;
when4xx: string;
when5xx: string;
minDuration: number;
}

View File

@@ -46,3 +46,16 @@ export type Service = {
layers: string[]; layers: string[];
shortName: string; shortName: string;
}; };
export type Process = {
id: string;
name: string;
serviceId: string;
serviceName: string;
instanceId: string;
instanceName: string;
agentId: string;
detectType: string;
attributes: { name: string; value: string }[];
labels: string[];
};

View File

@@ -23,6 +23,9 @@ export interface Call {
sourceObj?: any; sourceObj?: any;
targetObj?: any; targetObj?: any;
value?: number; value?: number;
lowerArc?: boolean;
sourceComponents: string[];
targetComponents: string[];
} }
export interface Node { export interface Node {
id: string; id: string;

17
src/types/trace.d.ts vendored
View File

@@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
export interface Trace { export interface Trace {
duration: number; duration: number;
isError: boolean; isError: boolean;
@@ -85,3 +84,19 @@ export class TraceTreeRef {
segmentMap: Map<string, Span>; segmentMap: Map<string, Span>;
segmentIdGroup: string[]; segmentIdGroup: string[];
} }
type Instant = {
seconds: number;
nanos: number;
};
type KeyValue = {
key: string;
value: string | number;
};
export interface SpanAttachedEvent {
startTime: Instant;
endTime: Instant;
event: string;
tags: KeyValue[];
summary: KeyValue[];
}

View File

@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import dayjs from "dayjs";
export default function dateFormatStep( export default function dateFormatStep(
date: Date, date: Date,
step: string, step: string,
@@ -99,3 +100,6 @@ export const dateFormatTime = (date: Date, step: string): string => {
} }
return ""; return "";
}; };
export const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(new Date(date)).format(pattern);

View File

@@ -16,13 +16,7 @@
*/ */
import * as echarts from "echarts/core"; import * as echarts from "echarts/core";
import { import { BarChart, LineChart, HeatmapChart, SankeyChart } from "echarts/charts";
BarChart,
LineChart,
PieChart,
HeatmapChart,
SankeyChart,
} from "echarts/charts";
import { import {
TitleComponent, TitleComponent,
@@ -32,6 +26,7 @@ import {
DataZoomComponent, DataZoomComponent,
VisualMapComponent, VisualMapComponent,
TimelineComponent, TimelineComponent,
MarkAreaComponent,
} from "echarts/components"; } from "echarts/components";
import { SVGRenderer } from "echarts/renderers"; import { SVGRenderer } from "echarts/renderers";
@@ -43,13 +38,13 @@ echarts.use([
GridComponent, GridComponent,
BarChart, BarChart,
LineChart, LineChart,
PieChart,
HeatmapChart, HeatmapChart,
SankeyChart, SankeyChart,
SVGRenderer, SVGRenderer,
DataZoomComponent, DataZoomComponent,
VisualMapComponent, VisualMapComponent,
TimelineComponent, TimelineComponent,
MarkAreaComponent,
]); ]);
export default echarts; export default echarts;

View File

@@ -24,7 +24,7 @@ import Header from "./alarm/Header.vue";
import Content from "./alarm/Content.vue"; import Content from "./alarm/Content.vue";
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
appStore.setPageTitle("Alarm"); appStore.setPageTitle("Alerting");
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.alarm { .alarm {

View File

@@ -27,7 +27,6 @@ import { useDashboardStore } from "@/store/modules/dashboard";
import Dashboard from "./dashboard/Edit.vue"; import Dashboard from "./dashboard/Edit.vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { RoutesMap } from "@/constants/data";
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
@@ -38,7 +37,7 @@ const layer = ref<string>("GENERAL");
getDashboard(); getDashboard();
async function getDashboard() { async function getDashboard() {
layer.value = RoutesMap[String(route.name)]; layer.value = String(route.meta.layer);
dashboardStore.setLayer(layer.value); dashboardStore.setLayer(layer.value);
dashboardStore.setMode(false); dashboardStore.setMode(false);
await dashboardStore.setDashboards(); await dashboardStore.setDashboards();

View File

@@ -69,18 +69,16 @@ limitations under the License. -->
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, reactive } from "vue"; import { ref } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import timeFormat from "@/utils/timeFormat"; import timeFormat from "@/utils/timeFormat";
import { Languages } from "@/constants/data"; import { Languages } from "@/constants/data";
import Selector from "@/components/Selector.vue"; import Selector from "@/components/Selector.vue";
import getLocalTime from "@/utils/localtime";
const { t, locale } = useI18n(); const { t, locale } = useI18n();
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const state = reactive<{ timer: ReturnType<typeof setInterval> | null }>({
timer: null,
});
const lang = ref<string>(locale.value || "en"); const lang = ref<string>(locale.value || "en");
const autoTime = ref<number>(6); const autoTime = ref<number>(6);
const auto = ref<boolean>(appStore.autoRefresh || false); const auto = ref<boolean>(appStore.autoRefresh || false);
@@ -91,8 +89,11 @@ appStore.setPageTitle("Setting");
const handleReload = () => { const handleReload = () => {
const gap = const gap =
appStore.duration.end.getTime() - appStore.duration.start.getTime(); appStore.duration.end.getTime() - appStore.duration.start.getTime();
const time: Date[] = [new Date(new Date().getTime() - gap), new Date()]; const dates: Date[] = [
appStore.setDuration(timeFormat(time)); getLocalTime(appStore.utc, new Date(new Date().getTime() - gap)),
getLocalTime(appStore.utc, new Date()),
];
appStore.setDuration(timeFormat(dates));
}; };
const handleAuto = () => { const handleAuto = () => {
if (autoTime.value < 1) { if (autoTime.value < 1) {
@@ -101,10 +102,10 @@ const handleAuto = () => {
appStore.setAutoRefresh(auto.value); appStore.setAutoRefresh(auto.value);
if (auto.value) { if (auto.value) {
handleReload(); handleReload();
state.timer = setInterval(handleReload, autoTime.value * 1000); appStore.setReloadTimer(setInterval(handleReload, autoTime.value * 1000));
} else { } else {
if (state.timer) { if (appStore.reloadTimer) {
clearInterval(state.timer); clearInterval(appStore.reloadTimer);
} }
} }
}; };
@@ -112,12 +113,12 @@ const changeAutoTime = () => {
if (autoTime.value < 1) { if (autoTime.value < 1) {
return; return;
} }
if (state.timer) { if (appStore.reloadTimer) {
clearInterval(state.timer); clearInterval(appStore.reloadTimer);
} }
if (auto.value) { if (auto.value) {
handleReload(); handleReload();
state.timer = setInterval(handleReload, autoTime.value * 1000); appStore.setReloadTimer(setInterval(handleReload, autoTime.value * 1000));
} }
}; };
const setLang = (): void => { const setLang = (): void => {

View File

@@ -145,16 +145,14 @@ limitations under the License. -->
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue"; import { ref } from "vue";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { Alarm, Event } from "@/types/alarm"; import { Alarm, Event } from "@/types/alarm";
import { useAlarmStore } from "@/store/modules/alarm"; import { useAlarmStore } from "@/store/modules/alarm";
import { EventsDetailHeaders, AlarmDetailCol, EventsDetailKeys } from "./data"; import { EventsDetailHeaders, AlarmDetailCol, EventsDetailKeys } from "./data";
import { dateFormat } from "@/utils/dateFormat";
const { t } = useI18n(); const { t } = useI18n();
const alarmStore = useAlarmStore(); const alarmStore = useAlarmStore();
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern);
const isShowDetails = ref<boolean>(false); const isShowDetails = ref<boolean>(false);
const showEventDetails = ref<boolean>(false); const showEventDetails = ref<boolean>(false);
const currentDetail = ref<Alarm | any>({}); const currentDetail = ref<Alarm | any>({});

View File

@@ -15,11 +15,7 @@ limitations under the License. -->
<template> <template>
<div> <div>
<span class="grey">{{ t("tags") }}: </span> <span class="grey">{{ t("tags") }}: </span>
<span <span v-if="tagsList.length" class="trace-tags">
v-if="tagsList.length"
class="trace-tags"
:style="type === 'LOG' ? `min-width: 122px;` : ''"
>
<span class="selected" v-for="(item, index) in tagsList" :key="index"> <span class="selected" v-for="(item, index) in tagsList" :key="index">
<span>{{ item }}</span> <span>{{ item }}</span>
<span class="remove-icon" @click="removeTags(index)">×</span> <span class="remove-icon" @click="removeTags(index)">×</span>
@@ -33,31 +29,29 @@ limitations under the License. -->
@change="addLabels" @change="addLabels"
:placeholder="t('addTags')" :placeholder="t('addTags')"
/> />
<span v-else> <el-popover v-else trigger="click" :visible="visible" width="300px">
<el-input <template #reference>
size="small" <el-input
v-model="tags" size="small"
class="trace-new-tag" v-model="tags"
@click="showClick" class="trace-new-tag"
/> @input="inputTags"
<el-dropdown @blur="visible = false"
ref="dropdownTag" @focus="visible = true"
trigger="contextmenu" @change="addTags"
:hide-on-click="false" />
style="margin: 20px 0 0 -130px" </template>
v-if="tagArr.length" <div class="content">
> <span
<template #dropdown> v-for="(item, index) in tagList"
<el-dropdown-menu> :key="index"
<el-dropdown-item v-for="(item, index) in tagArr" :key="index"> @click="selectTag(item)"
<span @click="selectTag(item)" class="tag-item"> class="tag-item"
{{ item }} >
</span> {{ item }}
</el-dropdown-item> </span>
</el-dropdown-menu> </div>
</template> </el-popover>
</el-dropdown>
</span>
<span <span
class="tags-tip" class="tags-tip"
:class="type !== 'ALARM' && tagArr.length ? 'link-tips' : ''" :class="type !== 'ALARM' && tagArr.length ? 'link-tips' : ''"
@@ -68,12 +62,12 @@ limitations under the License. -->
> >
{{ t("tagsLink") }} {{ t("tagsLink") }}
</a> </a>
<el-tooltip :content="t(tipsMap[type])"> <el-tooltip :content="t((tipsMap as any)[type])">
<span> <span>
<Icon class="icon-help mr-5" iconName="help" size="middle" /> <Icon class="icon-help mr-5" iconName="help" size="middle" />
</span> </span>
</el-tooltip> </el-tooltip>
<b v-if="type === 'AL'">{{ t("noticeTag") }}</b> <b v-if="type === 'ALARM'">{{ t("noticeTag") }}</b>
</span> </span>
</div> </div>
</template> </template>
@@ -85,7 +79,7 @@ import { useLogStore } from "@/store/modules/log";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
/*global Nullable, defineEmits, defineProps */ /*global defineEmits, defineProps */
const emit = defineEmits(["update"]); const emit = defineEmits(["update"]);
const props = defineProps({ const props = defineProps({
type: { type: String, default: "TRACE" }, type: { type: String, default: "TRACE" },
@@ -97,13 +91,15 @@ const { t } = useI18n();
const tags = ref<string>(""); const tags = ref<string>("");
const tagsList = ref<string[]>([]); const tagsList = ref<string[]>([]);
const tagArr = ref<string[]>([]); const tagArr = ref<string[]>([]);
const tagList = ref<string[]>([]);
const tagKeys = ref<string[]>([]); const tagKeys = ref<string[]>([]);
const keysList = ref<string[]>([]);
const visible = ref<boolean>(false);
const tipsMap = { const tipsMap = {
LOG: "logTagsTip", LOG: "logTagsTip",
TRACE: "traceTagsTip", TRACE: "traceTagsTip",
ALARM: "alarmTagsTip", ALARM: "alarmTagsTip",
}; };
const dropdownTag = ref<Nullable<any>>(null);
fetchTagKeys(); fetchTagKeys();
@@ -143,6 +139,8 @@ async function fetchTagKeys() {
} }
tagArr.value = resp.data.tagKeys; tagArr.value = resp.data.tagKeys;
tagKeys.value = resp.data.tagKeys; tagKeys.value = resp.data.tagKeys;
keysList.value = resp.data.tagKeys;
searchTags();
} }
async function fetchTagValues() { async function fetchTagValues() {
@@ -159,25 +157,56 @@ async function fetchTagValues() {
return; return;
} }
tagArr.value = resp.data.tagValues; tagArr.value = resp.data.tagValues;
searchTags();
}
function inputTags() {
if (!tags.value) {
tagArr.value = keysList.value;
tagKeys.value = keysList.value;
tagList.value = tagArr.value;
return;
}
let search = "";
if (tags.value.includes("=")) {
search = tags.value.split("=")[1];
fetchTagValues();
} else {
search = tags.value;
}
tagList.value = tagArr.value.filter((d: string) => d.includes(search));
}
function addTags() {
if (!tags.value.includes("=")) {
return;
}
addLabels();
tagArr.value = tagKeys.value;
searchTags();
} }
function selectTag(item: string) { function selectTag(item: string) {
if (tags.value.includes("=")) { if (tags.value.includes("=")) {
tags.value += item; const key = tags.value.split("=")[0];
addLabels(); tags.value = key + "=" + item;
tagArr.value = tagKeys.value; addTags();
dropdownTag.value.handleClose();
return; return;
} }
tags.value = item + "="; tags.value = item + "=";
fetchTagValues(); fetchTagValues();
} }
function showClick() { function searchTags() {
if (dropdownTag.value) { let search = "";
dropdownTag.value.handleOpen(); if (tags.value.includes("=")) {
search = tags.value.split("=")[1];
} else {
search = tags.value;
} }
tagList.value = tagArr.value.filter((d: string) => d.includes(search));
} }
watch( watch(
() => appStore.durationTime, () => appStore.durationTime,
() => { () => {
@@ -221,6 +250,12 @@ watch(
.tag-item { .tag-item {
display: inline-block; display: inline-block;
min-width: 210px; min-width: 210px;
cursor: pointer;
margin-top: 10px;
&:hover {
color: #409eff;
}
} }
.tags-tip { .tags-tip {
@@ -229,7 +264,6 @@ watch(
.link-tips { .link-tips {
display: inline-block; display: inline-block;
margin-left: 130px;
} }
.light { .light {
@@ -247,4 +281,10 @@ watch(
.icon-help { .icon-help {
cursor: pointer; cursor: pointer;
} }
.content {
width: 300px;
max-height: 400px;
overflow: auto;
}
</style> </style>

View File

@@ -41,6 +41,7 @@ import Tool from "./panel/Tool.vue";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import Configuration from "./configuration"; import Configuration from "./configuration";
import { LayoutConfig } from "@/types/dashboard";
export default defineComponent({ export default defineComponent({
name: "Dashboard", name: "Dashboard",
@@ -66,7 +67,8 @@ export default defineComponent({
sessionStorage.getItem(layoutKey.value) || "{}" sessionStorage.getItem(layoutKey.value) || "{}"
); );
const layout: any = c.configuration || {}; const layout: any = c.configuration || {};
dashboardStore.setLayout(layout.children || []);
dashboardStore.setLayout(setWidgetsID(layout.children || []));
appStore.setPageTitle(layout.name); appStore.setPageTitle(layout.name);
if (p.entity) { if (p.entity) {
dashboardStore.setCurrentDashboard({ dashboardStore.setCurrentDashboard({
@@ -78,6 +80,24 @@ export default defineComponent({
}); });
} }
} }
function setWidgetsID(widgets: LayoutConfig[]) {
for (const item of widgets) {
item.id = item.i;
if (item.type === "Tab") {
if (item.children && item.children.length) {
for (const [index, child] of item.children.entries()) {
if (child.children && child.children.length) {
child.children.map((d: { i: string; index: string } | any) => {
d.id = `${item.i}-${index}-${d.i}`;
return d;
});
}
}
}
}
}
return widgets;
}
function handleClick(e: any) { function handleClick(e: any) {
e.stopPropagation(); e.stopPropagation();
if (e.target.className === "ds-main") { if (e.target.className === "ds-main") {

View File

@@ -20,7 +20,7 @@ limitations under the License. -->
placeholder="Please input name" placeholder="Please input name"
class="input-with-search ml-10" class="input-with-search ml-10"
size="small" size="small"
@change="searchDashboards" @change="searchDashboards(1)"
> >
<template #append> <template #append>
<el-button size="small"> <el-button size="small">
@@ -46,7 +46,7 @@ limitations under the License. -->
ref="multipleTableRef" ref="multipleTableRef"
:default-sort="{ prop: 'name', order: 'ascending' }" :default-sort="{ prop: 'name', order: 'ascending' }"
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
height="637px" height="calc(100% - 60px)"
size="small" size="small"
> >
<el-table-column type="selection" width="55" /> <el-table-column type="selection" width="55" />
@@ -129,7 +129,8 @@ limitations under the License. -->
small small
layout="prev, pager, next" layout="prev, pager, next"
:page-size="pageSize" :page-size="pageSize"
:total="dashboardStore.dashboards.length" :total="total"
v-model="currentPage"
@current-change="changePage" @current-change="changePage"
@prev-click="changePage" @prev-click="changePage"
@next-click="changePage" @next-click="changePage"
@@ -155,10 +156,12 @@ import { isEmptyObject } from "@/utils/is";
const { t } = useI18n(); const { t } = useI18n();
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const pageSize = 18; const pageSize = 20;
const dashboards = ref<DashboardItem[]>([]); const dashboards = ref<DashboardItem[]>([]);
const searchText = ref<string>(""); const searchText = ref<string>("");
const loading = ref<boolean>(false); const loading = ref<boolean>(false);
const currentPage = ref<number>(1);
const total = ref<number>(0);
const multipleTableRef = ref<InstanceType<typeof ElTable>>(); const multipleTableRef = ref<InstanceType<typeof ElTable>>();
const multipleSelection = ref<DashboardItem[]>([]); const multipleSelection = ref<DashboardItem[]>([]);
const dashboardFile = ref<Nullable<HTMLDivElement>>(null); const dashboardFile = ref<Nullable<HTMLDivElement>>(null);
@@ -170,7 +173,7 @@ const handleSelectionChange = (val: DashboardItem[]) => {
setList(); setList();
async function setList() { async function setList() {
await dashboardStore.setDashboards(); await dashboardStore.setDashboards();
searchDashboards(); searchDashboards(1);
} }
async function importTemplates(event: any) { async function importTemplates(event: any) {
const arr: any = await readFile(event); const arr: any = await readFile(event);
@@ -232,12 +235,20 @@ function exportTemplates() {
}, 2000); }, 2000);
} }
function optimizeTemplate( function optimizeTemplate(
children: (LayoutConfig & { moved?: boolean; standard?: unknown })[] children: (LayoutConfig & {
moved?: boolean;
standard?: unknown;
label?: string;
value?: string;
})[]
) { ) {
for (const child of children || []) { for (const child of children || []) {
delete child.moved; delete child.moved;
delete child.activedTabIndex; delete child.activedTabIndex;
delete child.standard; delete child.standard;
delete child.id;
delete child.label;
delete child.value;
if (isEmptyObject(child.graph)) { if (isEmptyObject(child.graph)) {
delete child.graph; delete child.graph;
} }
@@ -370,7 +381,7 @@ async function setRoot(row: DashboardItem) {
items.push(d); items.push(d);
} }
dashboardStore.resetDashboards(items); dashboardStore.resetDashboards(items);
searchDashboards(); searchDashboards(1);
loading.value = false; loading.value = false;
} }
function handleRename(row: DashboardItem) { function handleRename(row: DashboardItem) {
@@ -402,6 +413,7 @@ async function updateName(d: DashboardItem, value: string) {
name: value, name: value,
}; };
delete c.id; delete c.id;
delete c.filters;
const setting = { const setting = {
id: d.id, id: d.id,
configuration: JSON.stringify(c), configuration: JSON.stringify(c),
@@ -453,10 +465,13 @@ function searchDashboards(pageIndex?: any) {
const arr = list.filter((d: { name: string }) => const arr = list.filter((d: { name: string }) =>
d.name.includes(searchText.value) d.name.includes(searchText.value)
); );
dashboards.value = arr.splice(
(pageIndex - 1 || 0) * pageSize, total.value = arr.length;
pageSize * (pageIndex || 1) dashboards.value = arr.filter(
(d: { name: string }, index: number) =>
index < pageIndex * pageSize && index >= (pageIndex - 1) * pageSize
); );
currentPage.value = pageIndex;
} }
async function reloadTemplates() { async function reloadTemplates() {
@@ -465,6 +480,7 @@ async function reloadTemplates() {
loading.value = false; loading.value = false;
} }
function changePage(pageIndex: number) { function changePage(pageIndex: number) {
currentPage.value = pageIndex;
searchDashboards(pageIndex); searchDashboards(pageIndex);
} }
</script> </script>
@@ -484,12 +500,13 @@ function changePage(pageIndex: number) {
} }
.table { .table {
padding: 20px; padding: 20px 10px;
background-color: #fff; background-color: #fff;
box-shadow: 0px 1px 4px 0px #00000029; box-shadow: 0px 1px 4px 0px #00000029;
border-radius: 5px; border-radius: 5px;
width: 100%; width: 100%;
overflow: hidden; height: 100%;
overflow: auto;
} }
.toggle-selection { .toggle-selection {

View File

@@ -81,6 +81,10 @@ const onCreate = () => {
states.entity === d.entity && states.entity === d.entity &&
states.selectedLayer === d.layer states.selectedLayer === d.layer
); );
if (!states.name) {
ElMessage.error(t("nameEmptyError"));
return;
}
if (index > -1) { if (index > -1) {
ElMessage.error(t("nameError")); ElMessage.error(t("nameError"));
return; return;

View File

@@ -0,0 +1,82 @@
<!-- 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>
<span class="label">{{ t("enableAssociate") }}</span>
<el-switch
v-model="eventAssociate"
active-text="Yes"
inactive-text="No"
@change="updateConfig"
/>
</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 { ref } from "vue";
import { useDashboardStore } from "@/store/modules/dashboard";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const originConfig = dashboardStore.selectedGrid;
const eventAssociate = ref(dashboardStore.selectedGrid.eventAssociate || false);
function updateConfig() {
dashboardStore.selectedGrid = {
...dashboardStore.selectedGrid,
eventAssociate,
};
dashboardStore.selectWidget(dashboardStore.selectedGrid);
}
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: 10px 0;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid #eee;
padding: 10px;
text-align: right;
width: 100%;
background-color: #fff;
}
</style>

View File

@@ -0,0 +1,161 @@
<!-- 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("text") }}</span>
<el-input
class="input"
v-model="text"
size="small"
@change="changeConfig({ text })"
/>
</div>
<div class="item">
<span class="label">{{ t("textAlign") }}</span>
<Selector
:value="textAlign"
:options="AlignStyle"
size="small"
placeholder="Select a color"
class="input"
@change="changeConfig({ textAlign: $event[0].value })"
/>
</div>
<div class="item">
<span class="label">{{ t("backgroundColors") }}</span>
<Selector
:value="backgroundColor"
:options="Colors"
size="small"
placeholder="Select a color"
class="input"
@change="changeConfig({ backgroundColor: $event[0].value })"
/>
</div>
<div class="item">
<span class="label">{{ t("fontSize") }}</span>
<el-slider
class="slider"
v-model="fontSize"
show-input
input-size="small"
:min="12"
:max="30"
:step="1"
@change="changeConfig({ fontSize })"
/>
</div>
<div class="item">
<span class="label">{{ t("fontColors") }}</span>
<Selector
:value="fontColor"
:options="Colors"
size="small"
placeholder="Select a color"
class="input"
@change="changeConfig({ fontColor: $event[0].value })"
/>
</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 { ref } from "vue";
import { useDashboardStore } from "@/store/modules/dashboard";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const originConfig = dashboardStore.selectedGrid;
const graph = originConfig.graph || {};
const backgroundColor = ref(graph.backgroundColor || "green");
const fontColor = ref(graph.fontColor || "white");
const fontSize = ref<number>(graph.fontSize || 12);
const textAlign = ref(graph.textAlign || "left");
const text = ref<string>(graph.text || "");
const Colors = [
{
label: "Green",
value: "green",
},
{ label: "Blue", value: "blue" },
{ label: "Red", value: "red" },
{ label: "Grey", value: "grey" },
{ label: "White", value: "white" },
{ label: "Black", value: "black" },
{ label: "Orange", value: "orange" },
];
const AlignStyle = [
{
label: "Left",
value: "left",
},
{ label: "Center", value: "center" },
{ label: "Right", value: "right" },
];
function changeConfig(param: { [key: string]: unknown }) {
const { selectedGrid } = dashboardStore;
const graph = {
...selectedGrid.graph,
...param,
};
dashboardStore.selectWidget({ ...selectedGrid, graph });
}
function applyConfig() {
dashboardStore.setConfigPanel(false);
dashboardStore.setConfigs(dashboardStore.selectedGrid);
}
function cancelConfig() {
dashboardStore.selectWidget(originConfig);
dashboardStore.setConfigPanel(false);
}
</script>
<style lang="scss" scoped>
.slider {
width: 500px;
margin-top: -3px;
}
.label {
font-size: 13px;
font-weight: 500;
display: block;
margin-bottom: 5px;
}
.input {
width: 500px;
}
.item {
margin-bottom: 10px;
}
.footer {
position: fixed;
bottom: 0;
right: 0;
border-top: 1px solid #eee;
padding: 10px;
text-align: right;
width: 100%;
background-color: #fff;
}
</style>

View File

@@ -32,10 +32,12 @@ limitations under the License. -->
:data="states.source" :data="states.source"
:config="{ :config="{
...graph, ...graph,
legend: (dashboardStore.selectedGrid.graph || {}).legend,
i: dashboardStore.selectedGrid.i, i: dashboardStore.selectedGrid.i,
metrics: dashboardStore.selectedGrid.metrics, metrics: dashboardStore.selectedGrid.metrics,
metricTypes: dashboardStore.selectedGrid.metricTypes, metricTypes: dashboardStore.selectedGrid.metricTypes,
metricConfig: dashboardStore.selectedGrid.metricConfig, metricConfig: dashboardStore.selectedGrid.metricConfig,
relatedTrace: dashboardStore.selectedGrid.relatedTrace,
}" }"
:needQuery="true" :needQuery="true"
/> />
@@ -58,6 +60,20 @@ limitations under the License. -->
<el-collapse-item :title="t('widgetOptions')" name="3"> <el-collapse-item :title="t('widgetOptions')" name="3">
<WidgetOptions /> <WidgetOptions />
</el-collapse-item> </el-collapse-item>
<el-collapse-item
:title="t('associateOptions')"
name="4"
v-if="hasAssociate"
>
<AssociateOptions />
</el-collapse-item>
<el-collapse-item
:title="t('relatedTraceOptions')"
name="5"
v-if="hasAssociate"
>
<RelatedTraceOptions />
</el-collapse-item>
</el-collapse> </el-collapse>
</div> </div>
<div class="footer"> <div class="footer">
@@ -77,20 +93,16 @@ import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { Option } from "@/types/app"; import { Option } from "@/types/app";
import graphs from "../graphs"; import graphs from "../graphs";
import configs from "./widget/graph-styles"; import CustomOptions from "./widget/index";
import WidgetOptions from "./widget/WidgetOptions.vue";
import MetricOptions from "./widget/metric/Index.vue";
export default defineComponent({ export default defineComponent({
name: "WidgetEdit", name: "WidgetEdit",
components: { components: {
...graphs, ...graphs,
...configs, ...CustomOptions,
WidgetOptions,
MetricOptions,
}, },
setup() { setup() {
const configHeight = document.documentElement.clientHeight - 520; const configHeight = document.documentElement.clientHeight - 540;
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const appStoreWithOut = useAppStoreWithOut(); const appStoreWithOut = useAppStoreWithOut();
@@ -111,6 +123,12 @@ export default defineComponent({
const graph = computed(() => dashboardStore.selectedGrid.graph || {}); const graph = computed(() => dashboardStore.selectedGrid.graph || {});
const title = computed(() => encodeURIComponent(widget.value.title || "")); const title = computed(() => encodeURIComponent(widget.value.title || ""));
const tips = computed(() => encodeURIComponent(widget.value.tips || "")); const tips = computed(() => encodeURIComponent(widget.value.tips || ""));
const hasAssociate = computed(() =>
["Bar", "Line", "Area", "TopList"].includes(
dashboardStore.selectedGrid.graph &&
dashboardStore.selectedGrid.graph.type
)
);
function getSource(source: unknown) { function getSource(source: unknown) {
states.source = source; states.source = source;
@@ -145,6 +163,7 @@ export default defineComponent({
graph, graph,
title, title,
tips, tips,
hasAssociate,
}; };
}, },
}); });

View File

@@ -18,9 +18,13 @@
import Text from "./Text.vue"; import Text from "./Text.vue";
import Widget from "./Widget.vue"; import Widget from "./Widget.vue";
import Topology from "./Topology.vue"; import Topology from "./Topology.vue";
import Event from "./Event.vue";
import TimeRange from "./TimeRange.vue";
export default { export default {
Text, Text,
Widget, Widget,
Topology, Topology,
Event,
TimeRange,
}; };

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