Compare commits

...

50 Commits

Author SHA1 Message Date
dependabot[bot]
0c2cfa5630
build(deps-dev): bump vite from 6.2.6 to 6.3.4 (#468) 2025-05-01 16:42:57 +08:00
Fine0830
5e6e5aa737
Fix: correct the same labels for metrics (#467) 2025-04-24 11:58:29 +08:00
Fine0830
a4cd265d45
refactor: use the Fetch API to instead of Axios (#466) 2025-04-22 11:41:29 +08:00
peachisai
0ef6b57cae
Add the Flink icon and descriptions (#465) 2025-04-17 19:27:58 +08:00
Fine0830
687ae07bb0
feat: enhance the trace table graph for multiple refs (#464) 2025-04-14 21:12:48 +08:00
dependabot[bot]
0775bf0034
build(deps-dev): bump vite from 6.2.5 to 6.2.6 (#463) 2025-04-12 10:39:21 +08:00
Fine0830
5c322d960f
feat: enhance the trace tree to support displaying multiple refs of spans and distinguishing different parents. (#462) 2025-04-08 20:44:36 +08:00
dependabot[bot]
df2d07f508
build(deps-dev): bump vite from 6.2.4 to 6.2.5 (#461) 2025-04-07 09:44:10 +08:00
dependabot[bot]
105450071e
build(deps-dev): bump vite from 6.2.3 to 6.2.4 (#460)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.3 to 6.2.4.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.4/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.4/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 07:32:25 +08:00
Fine0830
39b4626317
feat: enhance trace list graph (#459) 2025-03-28 10:34:01 +08:00
dependabot[bot]
0ea8335fee
build(deps-dev): bump vite from 6.1.1 to 6.2.3 (#458) 2025-03-26 09:39:39 +08:00
dependabot[bot]
0d2bedf529
build(deps): bump axios from 1.7.5 to 1.8.2 (#456) 2025-03-09 19:11:40 +08:00
dependabot[bot]
b525f84fa0
build(deps): bump vue-i18n from 9.2.2 to 9.14.3 (#455) 2025-03-09 19:04:12 +08:00
Fine0830
1fe58f5f6c
build: remove lodash library (#454) 2025-03-06 17:10:54 +08:00
周小杰
012ae1db6c
fix: time range when generate link (#453) 2025-03-06 15:47:55 +08:00
Zixin Zhou
79ec865ee7
Add skywalking go agent self observability menu (#452) 2025-03-02 18:48:14 +08:00
Fine0830
9ab8ac44bc
add pagination for endpoint list (#451) 2025-02-28 17:15:55 +08:00
Fine0830
7a690e6704
feat: add topN selector for endpoint list (#450) 2025-02-28 16:19:27 +08:00
Fine0830
65607a5540
fix echarts tooltips (#449) 2025-02-24 16:38:17 +08:00
Fine0830
5bb4218bfe
feat: Implement the legend selector in metrics charts (#447) 2025-02-20 16:19:22 +08:00
Fine0830
2b6f3ecaa8
build: bump up dependencies (#448) 2025-02-20 13:49:58 +08:00
Fine0830
2246a9a045
fix name in routes (#446) 2025-02-13 18:06:56 +08:00
dependabot[bot]
9318d32b0b
build(deps-dev): bump vite from 4.5.3 to 4.5.9 (#445) 2025-01-22 14:19:08 +08:00
Fine0830
8ea50c8680
feat: visualize Snapshot on Alerting page (#444) 2025-01-13 17:24:32 +08:00
Fine0830
55b3867bea
fix: set up async profiling tasks (#443) 2024-12-30 15:51:26 +08:00
Super-Lu
c33d6c4180
Fix the issue where when initiating a query on the endpoint topology page, if the query parameters contain the ID of the virtual endpoint User, all link information will be retrieved. (#442) 2024-12-21 20:25:44 +08:00
dependabot[bot]
70ea9fd06f
build(deps): bump nanoid from 3.3.7 to 3.3.8 (#441) 2024-12-15 14:55:06 +08:00
Fine0830
2fca7a79a2
fix unit (#440) 2024-12-09 17:12:57 +08:00
Fine0830
f5cfb030a3
fix style (#439) 2024-12-04 10:29:30 +08:00
Fine0830
fbeeca8d9a
Fix Async Profiling widget (#438) 2024-12-03 19:16:51 +08:00
Fine0830
ea0f5e5f62
Feat: Polish Async profiling widget (#437) 2024-12-03 12:41:51 +08:00
Fine0830
8771ce4a19
refactor: polish Trace Profiling widget (#436) 2024-12-02 15:10:24 +08:00
Fine0830
99a2461734
update style (#435) 2024-11-28 21:35:44 +08:00
Fine0830
fb0817eed8
feat: implement Async Profiling widget (#434) 2024-11-28 17:50:43 +08:00
Fine0830
e164d87209
Fix: releases an existing object URL (#433) 2024-11-25 11:26:42 +08:00
Fine0830
5c92a46569
Refactor copy util with Web API (#432) 2024-11-24 17:25:13 +08:00
Zixin Zhou
aff69c057f
feat: Add support collapse span (#431) 2024-11-23 19:55:54 +08:00
Fine0830
7338cec6b4
fix metrics query (#430) 2024-11-22 15:53:37 +08:00
Fine0830
536df8c052
feat: Split topology metric query to avoid exceeding the maximum query complexity (#429) 2024-11-14 11:38:13 +08:00
Fine0830
64d4a2b59b
hide entrances to unrelated dashboards in topn list (#428) 2024-11-08 10:03:49 +08:00
Fine0830
6e1a6cf19b
fix (#427) 2024-11-07 12:41:19 +08:00
Fine0830
aeddb39637
feat: implement owner option for TopList widgets in related trace options (#426) 2024-11-07 10:31:06 +08:00
Fine0830
14fa5d65b6
feat: View data value related dashboards in TopList widgets (#425) 2024-11-05 11:21:08 +08:00
Fine0830
224e761d70
Fix initialization dashboards (#424) 2024-10-24 15:49:02 +08:00
Fine0830
4c60f69aef
Fix Value Mappings (#423) 2024-10-18 14:48:55 +08:00
Zixin Zhou
0007e3e3ae
Update mappingTip (#422) 2024-10-18 14:36:25 +08:00
Fine0830
b6522f4555
feat: support ranges for Value Mappings (#421) 2024-10-18 10:31:17 +08:00
Fine0830
bddbe40974
add duration (#420) 2024-10-16 10:26:45 +08:00
Fine0830
61449f4b17
feat: add content decorations to Table and Card widgets (#419) 2024-10-15 16:25:21 +08:00
Zixin Zhou
a92365efcf
feat: add support for case-insensitive search in the dashboard list (#418) 2024-10-07 23:14:22 +08:00
142 changed files with 6722 additions and 5798 deletions

View File

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

7514
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.4.0", "version": "10.2.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@ -18,19 +18,17 @@
"check-components-types": "if (! git diff --quiet -U0 ./src/types); then echo 'type files are not updated correctly'; git diff -U0 ./src/types; exit 1; fi" "check-components-types": "if (! git diff --quiet -U0 ./src/types); then echo 'type files are not updated correctly'; git diff -U0 ./src/types; exit 1; fi"
}, },
"dependencies": { "dependencies": {
"axios": "^1.7.5",
"d3": "^7.3.0", "d3": "^7.3.0",
"d3-flame-graph": "^4.1.3", "d3-flame-graph": "^4.1.3",
"d3-tip": "^0.9.1", "d3-tip": "^0.9.1",
"echarts": "^5.2.2", "echarts": "^5.2.2",
"element-plus": "^2.2.5", "element-plus": "^2.9.4",
"lodash": "^4.17.21",
"monaco-editor": "^0.34.1", "monaco-editor": "^0.34.1",
"pinia": "^2.0.28", "pinia": "^2.0.28",
"vis-timeline": "^7.5.1", "vis-timeline": "^7.5.1",
"vue": "^3.2.45", "vue": "^3.2.45",
"vue-grid-layout": "^3.0.0-beta1", "vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.1.9", "vue-i18n": "^9.14.3",
"vue-router": "^4.1.6", "vue-router": "^4.1.6",
"vue-types": "^4.1.1" "vue-types": "^4.1.1"
}, },
@ -42,11 +40,10 @@
"@types/d3-tip": "^3.5.5", "@types/d3-tip": "^3.5.5",
"@types/echarts": "^4.9.12", "@types/echarts": "^4.9.12",
"@types/jsdom": "^20.0.1", "@types/jsdom": "^20.0.1",
"@types/lodash": "^4.14.179",
"@types/node": "^18.11.12", "@types/node": "^18.11.12",
"@types/three": "^0.131.0", "@types/three": "^0.131.0",
"@vitejs/plugin-vue": "^4.0.0", "@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^3.0.0", "@vitejs/plugin-vue-jsx": "^4.1.1",
"@vue/eslint-config-prettier": "^7.0.0", "@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^11.0.0", "@vue/eslint-config-typescript": "^11.0.0",
"@vue/test-utils": "^2.2.6", "@vue/test-utils": "^2.2.6",
@ -64,21 +61,21 @@
"postcss-html": "^1.3.0", "postcss-html": "^1.3.0",
"postcss-scss": "^4.0.2", "postcss-scss": "^4.0.2",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"sass": "^1.56.1", "sass": "^1.85.0",
"start-server-and-test": "^2.0.5", "start-server-and-test": "^2.0.5",
"stylelint": "15.9.0", "stylelint": "15.9.0",
"stylelint-config-html": "^1.0.0", "stylelint-config-html": "^1.0.0",
"stylelint-config-prettier": "9.0.4", "stylelint-config-prettier": "9.0.4",
"stylelint-config-standard": "^33.0.0", "stylelint-config-standard": "^33.0.0",
"stylelint-order": "^6.0.3", "stylelint-order": "^6.0.3",
"typescript": "~4.7.4", "typescript": "^5.7.3",
"unplugin-auto-import": "^0.18.2", "unplugin-auto-import": "^0.18.2",
"unplugin-vue-components": "^0.27.3", "unplugin-vue-components": "^0.27.3",
"vite": "^4.5.3", "vite": "^6.3.4",
"vite-plugin-monaco-editor": "^1.1.0", "vite-plugin-monaco-editor": "^1.1.0",
"vite-plugin-svg-icons": "^2.0.1", "vite-plugin-svg-icons": "^2.0.1",
"vitest": "^0.25.6", "vitest": "^3.0.5",
"vue-tsc": "^1.8.27" "vue-tsc": "^2.2.2"
}, },
"browserslist": [ "browserslist": [
"> 1%", "> 1%",

View File

@ -0,0 +1,18 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path d="M512 992c-83.2 0-166.4-19.2-243.2-64-89.6-51.2-160-134.4-204.8-230.4C25.6 601.6 19.2 486.4 51.2 390.4c25.6-102.4 89.6-192 172.8-256C307.2 70.4 409.6 38.4 518.4 38.4c19.2 0 32 12.8 32 32s-19.2 25.6-38.4 25.6c-89.6 0-179.2 32-256 83.2S128 313.6 108.8 403.2s-12.8 185.6 19.2 268.8c32 83.2 96 153.6 179.2 198.4 76.8 44.8 172.8 64 262.4 51.2 89.6-12.8 172.8-51.2 236.8-115.2s108.8-147.2 115.2-236.8c12.8-89.6-6.4-185.6-51.2-262.4-6.4-12.8-6.4-32 12.8-44.8 12.8-12.8 38.4-6.4 44.8 12.8 51.2 89.6 76.8 198.4 57.6 300.8-12.8 102.4-64 204.8-134.4 275.2-76.8 76.8-172.8 121.6-275.2 134.4-19.2 6.4-44.8 6.4-64 6.4z" p-id="8538"></path><path d="M512 480c-19.2 0-32-12.8-32-32V64c0-19.2 12.8-32 32-32s32 12.8 32 32v384c0 19.2-12.8 32-32 32z" p-id="8539"></path><path d="M512 608c-12.8 0-25.6 0-38.4-6.4-12.8-6.4-19.2-12.8-32-19.2-6.4-12.8-12.8-19.2-19.2-32-6.4-12.8-6.4-25.6-6.4-38.4 0-25.6 12.8-51.2 25.6-70.4 38.4-38.4 102.4-38.4 134.4 0 19.2 19.2 32 44.8 32 70.4 0 25.6-12.8 51.2-25.6 70.4-19.2 12.8-44.8 25.6-70.4 25.6z m0-128c-6.4 0-19.2 6.4-25.6 6.4 0 6.4-6.4 19.2-6.4 25.6v12.8c0 6.4 6.4 6.4 6.4 12.8 0 0 6.4 6.4 12.8 6.4 12.8 6.4 25.6 0 32-6.4 6.4-6.4 12.8-19.2 12.8-25.6 0-6.4-6.4-19.2-6.4-25.6-6.4 0-19.2-6.4-25.6-6.4z" p-id="8540"></path><path d="M512 800c-51.2 0-102.4-12.8-147.2-38.4-57.6-32-96-83.2-121.6-140.8-19.2-57.6-25.6-121.6-6.4-185.6 19.2-64 51.2-115.2 102.4-153.6C384 243.2 448 224 512 224c19.2 0 32 12.8 32 32s-12.8 32-32 32c-51.2 0-96 19.2-134.4 44.8-38.4 32-70.4 76.8-83.2 121.6s-6.4 96 12.8 140.8c19.2 44.8 51.2 83.2 96 108.8 44.8 25.6 89.6 32 140.8 25.6 51.2-6.4 96-32 128-64s57.6-83.2 64-128c6.4-51.2-6.4-96-25.6-140.8-12.8-12.8-6.4-32 6.4-38.4 12.8-6.4 32-6.4 44.8 12.8 32 57.6 44.8 121.6 38.4 179.2-6.4 64-38.4 121.6-83.2 166.4-44.8 44.8-102.4 76.8-166.4 83.2H512z"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,38 @@
<!-- 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 width="2400" height="2400" viewBox="0 0 200 100" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="arrowhead" markerWidth="8" markerHeight="8" refX="2" refY="2.5" orient="auto">
<polygon points="0 0, 3 2.5, 0 5" fill="white" />
</marker>
</defs>
<line x1="0" y1="20" x2="42" y2="20" stroke="white" stroke-width="7" marker-end="url(#arrowhead)" />
<line x1="0" y1="50" x2="42" y2="50" stroke="white" stroke-width="7" marker-end="url(#arrowhead)" />
<line x1="0" y1="80" x2="42" y2="80" stroke="white" stroke-width="7" marker-end="url(#arrowhead)" />
<line x1="49" y1="10" x2="139" y2="10" stroke="white" stroke-width="7" />
<line x1="49" y1="90" x2="139" y2="90" stroke="white" stroke-width="7" />
<line x1="49" y1="10" x2="50" y2="90" stroke="white" stroke-width="7" />
<ellipse cx="140" cy="50" rx="10" ry="40" fill="none" stroke="white" stroke-width="7" />
<line x1="147" y1="20" x2="190" y2="20" stroke="white" stroke-width="7" marker-end="url(#arrowhead)" />
<line x1="149" y1="50" x2="190" y2="50" stroke="white" stroke-width="7" marker-end="url(#arrowhead)" />
<line x1="147" y1="80" x2="190" y2="80" stroke="white" stroke-width="7" marker-end="url(#arrowhead)" />
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -13,6 +13,13 @@ 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>
<SelectorLegend
:data="option.legend.data"
:show="legendSelector.isSelector"
:isConfigPage="legendSelector.isConfigPage"
:colors="option.color"
@change="changeLegend"
/>
<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 <div
@ -54,6 +61,7 @@ limitations under the License. -->
import Trace from "@/views/dashboard/related/trace/Index.vue"; import Trace from "@/views/dashboard/related/trace/Index.vue";
import associateProcessor from "@/hooks/useAssociateProcessor"; import associateProcessor from "@/hooks/useAssociateProcessor";
import { WidgetType } from "@/views/dashboard/data"; import { WidgetType } from "@/views/dashboard/data";
import SelectorLegend from "./Legend.vue";
/*global Nullable, defineProps, defineEmits, Indexable*/ /*global Nullable, defineProps, defineEmits, Indexable*/
const emits = defineEmits(["select"]); const emits = defineEmits(["select"]);
@ -84,6 +92,10 @@ limitations under the License. -->
type: Array as PropType<{ widgetId: string }[]>, type: Array as PropType<{ widgetId: string }[]>,
default: () => [], default: () => [],
}, },
legendSelector: {
type: Object as PropType<Indexable>,
default: () => ({ isConfigPage: false, isSelector: false }),
},
}); });
const available = computed( const available = computed(
() => () =>
@ -103,6 +115,7 @@ limitations under the License. -->
if (!instance) { if (!instance) {
return; return;
} }
instance.on("click", (params: EventParams) => { instance.on("click", (params: EventParams) => {
currentParams.value = params; currentParams.value = params;
if (props.option.series.type === "sankey") { if (props.option.series.type === "sankey") {
@ -203,6 +216,23 @@ limitations under the License. -->
}); });
} }
function changeLegend(names: string[]) {
const instance = getInstance();
for (const item of props.option.legend.data) {
if (names.includes(item.name)) {
instance.dispatchAction({
type: "legendSelect",
name: item.name,
});
} else {
instance.dispatchAction({
type: "legendUnSelect",
name: item.name,
});
}
}
}
watch( watch(
() => props.option, () => props.option,
(newVal, oldVal) => { (newVal, oldVal) => {

View File

@ -0,0 +1,74 @@
<!-- 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>
<Selector
class="mb-10"
multiple
:value="legend"
size="small"
:options="Options"
@change="changeLegend"
filterable
collapseTags
collapseTagsTooltip
v-if="show"
/>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from "vue";
import type { PropType } from "vue";
import type { Option } from "@/types/app";
import Selector from "./Selector.vue";
const props = defineProps({
data: {
type: Array as PropType<{ name: string }[]>,
default: () => [],
},
colors: {
type: Array as PropType<string[]>,
default: () => [],
},
show: {
type: Boolean,
default: false,
},
isConfigPage: {
type: Boolean,
default: false,
},
});
const emits = defineEmits(["change"]);
const legend = ref<string[]>([]);
const Options = computed(() =>
props.data.map((d: { name: string }, index: number) => ({
label: d.name,
value: d.name,
color: props.colors[index % props.colors.length],
})),
);
function changeLegend(opt: Option[]) {
legend.value = opt.map((d: Option) => d.value);
emits("change", legend.value);
}
watch(
() => props.data,
() => {
legend.value = props.data.map((d) => d.name);
},
);
</script>

View File

@ -0,0 +1,107 @@
<!-- 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>
<el-select
:size="size"
v-model="selected"
:placeholder="placeholder"
@change="changeSelected"
:multiple="multiple"
:disabled="disabled"
:style="{ borderRadius }"
:clearable="clearable"
:remote="isRemote"
:reserve-keyword="isRemote"
:remote-method="remoteMethod"
:filterable="filterable"
:collapse-tags="collapseTags"
:collapse-tags-tooltip="collapseTagsTooltip"
>
<el-option
v-for="(item, index) in options"
:key="`${item.value}${index}`"
:label="item.label || ''"
:value="item.value || ''"
:disabled="item.disabled || false"
>
<div class="flex items-center">
<el-tag :color="item.color" class="mr-5" size="small" />
<span :style="{ color: item.color }">{{ item.label }}</span>
</div>
</el-option>
</el-select>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import type { PropType } from "vue";
/*global defineProps, defineEmits, Indexable*/
const emit = defineEmits(["change", "query"]);
const props = defineProps({
options: {
type: Array as PropType<
({
label: string | number;
value: string | number;
color: string;
} & { disabled?: boolean })[]
>,
default: () => [],
},
value: {
type: [Array, String, Number, undefined] as PropType<any>,
default: () => [],
},
size: { type: null, default: "default" },
placeholder: {
type: [String, undefined] as PropType<string>,
default: "Select a option",
},
borderRadius: { type: Number, default: 3 },
multiple: { type: Boolean, default: false },
disabled: { type: Boolean, default: false },
clearable: { type: Boolean, default: false },
isRemote: { type: Boolean, default: false },
filterable: { type: Boolean, default: true },
collapseTags: { type: Boolean, default: false },
collapseTagsTooltip: { type: Boolean, default: false },
});
const selected = ref<string[] | string>(props.value);
function changeSelected() {
const options = props.options.filter((d: Indexable) =>
props.multiple ? selected.value.includes(d.value) : selected.value === d.value,
);
emit("change", options);
}
function remoteMethod(query: string) {
if (props.isRemote) {
emit("query", query);
}
}
watch(
() => props.value,
(data) => {
selected.value = data;
},
);
</script>
<style lang="scss" scoped>
.el-input__inner {
border-radius: unset !important;
}
</style>

View File

@ -26,6 +26,8 @@ limitations under the License. -->
:reserve-keyword="isRemote" :reserve-keyword="isRemote"
:remote-method="remoteMethod" :remote-method="remoteMethod"
:filterable="filterable" :filterable="filterable"
:collapse-tags="collapseTags"
:collapse-tags-tooltip="collapseTagsTooltip"
> >
<el-option <el-option
v-for="(item, index) in options" v-for="(item, index) in options"
@ -41,11 +43,6 @@ limitations under the License. -->
import { ref, watch } from "vue"; import { ref, watch } from "vue";
import type { PropType } from "vue"; import type { PropType } from "vue";
// interface Option {
// label: string | number;
// value: string | number;
// }
/*global defineProps, defineEmits, Indexable*/ /*global defineProps, defineEmits, Indexable*/
const emit = defineEmits(["change", "query"]); const emit = defineEmits(["change", "query"]);
const props = defineProps({ const props = defineProps({
@ -73,6 +70,8 @@ limitations under the License. -->
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 }, filterable: { type: Boolean, default: true },
collapseTags: { type: Boolean, default: false },
collapseTagsTooltip: { type: Boolean, default: false },
}); });
const selected = ref<string[] | string>(props.value); const selected = ref<string[] | string>(props.value);

View File

@ -18,7 +18,7 @@ import type { App } from "vue";
import Icon from "./Icon.vue"; import Icon from "./Icon.vue";
import TimePicker from "./TimePicker.vue"; import TimePicker from "./TimePicker.vue";
import Selector from "./Selector.vue"; import Selector from "./Selector.vue";
import Graph from "./Graph.vue"; import Graph from "./Graph/Graph.vue";
import Radio from "./Radio.vue"; import Radio from "./Radio.vue";
import SelectSingle from "./SelectSingle.vue"; import SelectSingle from "./SelectSingle.vue";
import Tags from "./Tags.vue"; import Tags from "./Tags.vue";

75
src/graphql/base.ts Normal file
View File

@ -0,0 +1,75 @@
/*
* Licensed to 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. Apache Software Foundation (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.
*/
const Timeout = 2 * 60 * 1000;
export let globalAbortController = new AbortController();
export function abortRequestsAndUpdate() {
globalAbortController.abort(`Request timeout ${Timeout}ms`);
globalAbortController = new AbortController();
}
class HTTPError extends Error {
response;
constructor(response: Response, detailText = "") {
super(detailText || response.statusText);
this.name = "HTTPError";
this.response = response;
}
}
const BasePath = `/graphql`;
export async function httpQuery({
path = "",
method = "GET",
json,
headers = {},
}: {
path?: string;
method: string;
json: unknown;
headers: Recordable;
}) {
const timeoutId = setTimeout(() => {
abortRequestsAndUpdate();
}, Timeout);
const url = `${BasePath}${path}`;
const response: Response = await fetch(url, {
method,
headers: {
"Content-Type": "application/json",
accept: "application/json",
...headers,
},
body: JSON.stringify(json),
signal: globalAbortController.signal,
})
.catch((error) => {
throw new HTTPError(error);
})
.finally(() => {
clearTimeout(timeoutId);
});
if (response.ok) {
return response.json();
} else {
console.error(new HTTPError(response));
}
}

View File

@ -14,20 +14,18 @@
* 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 type { AxiosResponse } from "axios"; import { httpQuery } from "./base";
import axios from "axios";
import { cancelToken } from "@/utils/cancelToken";
async function query(param: { queryStr: string; conditions: { [key: string]: unknown } }) { async function fetchQuery(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const res: AxiosResponse = await axios.post( const response = await httpQuery({
"/graphql", method: "post",
{ query: param.queryStr, variables: { ...param.conditions } }, json: { query: param.queryStr, variables: { ...param.conditions } },
{ cancelToken: cancelToken() }, headers: {},
); });
if (res.data.errors) { if (response.errors) {
res.data.errors = res.data.errors.map((e: { message: string }) => e.message).join(" "); response.errors = response.errors.map((e: { message: string }) => e.message).join(" ");
} }
return res; return response;
} }
export default query; export default fetchQuery;

View File

@ -24,6 +24,7 @@ export const Alarm = {
message message
startTime startTime
scope scope
name
tags { tags {
key key
value value
@ -43,6 +44,35 @@ export const Alarm = {
startTime startTime
endTime endTime
} }
snapshot {
expression
metrics {
name
results {
metric {
labels {
key
value
}
}
values {
id
owner {
scope
serviceID
serviceName
normal
serviceInstanceID
serviceInstanceName
endpointID
endpointName
}
value
traceID
}
}
}
}
} }
}`, }`,
}; };

View File

@ -0,0 +1,80 @@
/**
* 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 const GetAsyncTaskList = {
variable: "$request: AsyncProfilerTaskListRequest!",
query: `
asyncTaskList: queryAsyncProfilerTaskList(request: $request) {
errorReason
tasks {
id
serviceId
serviceInstanceIds
createTime
events
duration
execArgs
}
}
`,
};
export const GetAsyncProfileTaskProcess = {
variable: "$taskId: String!",
query: `
taskProgress: queryAsyncProfilerTaskProgress(taskId: $taskId) {
logs {
id
instanceId
instanceName
operationType
operationTime
}
errorInstanceIds
successInstanceIds
}
`,
};
export const CreateAsyncProfileTask = {
variable: "$asyncProfilerTaskCreationRequest: AsyncProfilerTaskCreationRequest!",
query: `
task: createAsyncProfilerTask(asyncProfilerTaskCreationRequest: $asyncProfilerTaskCreationRequest) {
id
errorReason
code
}
`,
};
export const GetAsyncProfileAnalyze = {
variable: "$request: AsyncProfilerAnalyzationRequest!",
query: `
analysisResult: queryAsyncProfilerAnalyze(request: $request) {
tree {
type
elements {
id
parentId
symbol: codeSignature
dumpCount: total
self
}
}
}
`,
};

View File

@ -14,22 +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 const TypeOfMetrics = {
variable: "$name: String!",
query: `typeOfMetrics(name: $name)`,
};
export const listMetrics = {
variable: "$regex: String",
query: `
metrics: listMetrics(regex: $regex) {
value: name
label: name
type
catalog
}
`,
};
export const getAllTemplates = { export const getAllTemplates = {
query: ` query: `

View File

@ -73,9 +73,9 @@ export const Processes = {
}; };
export const Endpoints = { export const Endpoints = {
variable: "$serviceId: ID!, $keyword: String!", variable: "$serviceId: ID!, $keyword: String!, $duration: Duration, $limit: Int!",
query: ` query: `
pods: findEndpoint(serviceId: $serviceId, keyword: $keyword, limit: 20) { pods: findEndpoint(serviceId: $serviceId, keyword: $keyword, limit: $limit, duration: $duration) {
id id
value: name value: name
label: name label: name

View File

@ -14,9 +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 type { AxiosPromise, AxiosResponse } from "axios"; import { httpQuery } from "./base";
import axios from "axios";
import { cancelToken } from "@/utils/cancelToken";
import * as app from "./query/app"; import * as app from "./query/app";
import * as selector from "./query/selector"; import * as selector from "./query/selector";
import * as dashboard from "./query/dashboard"; import * as dashboard from "./query/dashboard";
@ -28,6 +26,7 @@ import * as alarm from "./query/alarm";
import * as event from "./query/event"; import * as event from "./query/event";
import * as ebpf from "./query/ebpf"; import * as ebpf from "./query/ebpf";
import * as demandLog from "./query/demand-log"; import * as demandLog from "./query/demand-log";
import * as asyncProfile from "./query/async-profile";
const query: { [key: string]: string } = { const query: { [key: string]: string } = {
...app, ...app,
@ -41,32 +40,27 @@ const query: { [key: string]: string } = {
...event, ...event,
...ebpf, ...ebpf,
...demandLog, ...demandLog,
...asyncProfile,
}; };
class Graphql { class Graphql {
private queryData = ""; queryData = "";
public query(queryData: string) { query(data: string) {
this.queryData = queryData; this.queryData = data;
return this; return this;
} }
public params(variablesData: unknown): AxiosPromise<void> { async params(variables: unknown) {
return axios const response = await httpQuery({
.post( method: "post",
"/graphql", headers: {},
{ json: {
query: query[this.queryData], query: query[this.queryData],
variables: variablesData, variables,
}, },
{ cancelToken: cancelToken() }, });
) if (response.errors) {
.then((res: AxiosResponse) => { response.errors = response.errors.map((e: { message: string }) => e.message).join(" ");
if (res.data.errors) { }
res.data.errors = res.data.errors.map((e: { message: string }) => e.message).join(" "); return response;
}
return res;
})
.catch((err: Error) => {
throw err;
});
} }
} }

View File

@ -0,0 +1,31 @@
/**
* 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 {
GetAsyncTaskList,
GetAsyncProfileTaskProcess,
CreateAsyncProfileTask,
GetAsyncProfileAnalyze,
} from "../fragments/async-profile";
export const getAsyncTaskList = `query getAsyncTaskList(${GetAsyncTaskList.variable}) {${GetAsyncTaskList.query}}`;
export const getAsyncProfileTaskProcess = `query getAsyncProfileTaskProcess(${GetAsyncProfileTaskProcess.variable}) {${GetAsyncProfileTaskProcess.query}}`;
export const saveAsyncProfileTask = `mutation createAsyncProfileTask(${CreateAsyncProfileTask.variable}) {${CreateAsyncProfileTask.query}}`;
export const getAsyncProfileAnalyze = `query getAsyncProfileAnalyze(${GetAsyncProfileAnalyze.variable}) {${GetAsyncProfileAnalyze.query}}`;

View File

@ -14,18 +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 { import { getAllTemplates, addTemplate, changeTemplate, deleteTemplate } from "../fragments/dashboard";
TypeOfMetrics,
listMetrics,
getAllTemplates,
addTemplate,
changeTemplate,
deleteTemplate,
} from "../fragments/dashboard";
export const queryTypeOfMetrics = `query typeOfMetrics(${TypeOfMetrics.variable}) {${TypeOfMetrics.query}}`;
export const queryMetrics = `query queryData(${listMetrics.variable}) {${listMetrics.query}}`;
export const addNewTemplate = `mutation template(${addTemplate.variable}) {${addTemplate.query}}`; export const addNewTemplate = `mutation template(${addTemplate.variable}) {${addTemplate.query}}`;

View File

@ -57,6 +57,16 @@ export const RespFields: Indexable = {
name: id name: id
value value
refId: traceID refId: traceID
owner {
scope
serviceID
serviceName
normal
serviceInstanceID
serviceInstanceName
endpointID
endpointName
}
} }
} }
error error
@ -102,3 +112,5 @@ export const LightChartColors = [
"#546570", "#546570",
"#c4ccd3", "#c4ccd3",
]; ];
export const MaxQueryLength = 120;

View File

@ -47,11 +47,11 @@ export function createBreakpointListen(fn?: (opt: CreateCallbackParams) => void)
function getWindowWidth() { function getWindowWidth() {
const width = document.body.clientWidth; const width = document.body.clientWidth;
const xs = screenMap.get(sizeEnum.XS) || ""; const xs = screenMap.get(sizeEnum.XS) || 0;
const sm = screenMap.get(sizeEnum.SM) || ""; const sm = screenMap.get(sizeEnum.SM) || 0;
const md = screenMap.get(sizeEnum.MD) || ""; const md = screenMap.get(sizeEnum.MD) || 0;
const lg = screenMap.get(sizeEnum.LG) || ""; const lg = screenMap.get(sizeEnum.LG) || 0;
const xl = screenMap.get(sizeEnum.XL) || ""; const xl = screenMap.get(sizeEnum.XL) || 0;
if (width < xs) { if (width < xs) {
screenRef.value = sizeEnum.XS; screenRef.value = sizeEnum.XS;
} else if (width < sm) { } else if (width < sm) {

View File

@ -14,9 +14,10 @@
* 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 { RespFields, MaximumEntities } from "./data"; import { RespFields, MaximumEntities, MaxQueryLength } from "./data";
import { EntityType, ExpressionResultType } from "@/views/dashboard/data"; import { EntityType, ExpressionResultType } from "@/views/dashboard/data";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { useTopologyStore } from "@/store/modules/topology";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
@ -24,6 +25,14 @@ import type { MetricConfigOpt } from "@/types/dashboard";
import type { Instance, Endpoint, Service } from "@/types/selector"; import type { Instance, Endpoint, Service } from "@/types/selector";
import type { Node, Call } from "@/types/topology"; import type { Node, Call } from "@/types/topology";
function chunkArray(array: any[], chunkSize: number) {
const result = [];
for (let i = 0; i < array.length; i += chunkSize) {
result.push(array.slice(i, i + chunkSize));
}
return result;
}
export async function useDashboardQueryProcessor(configList: Indexable[]) { export async function useDashboardQueryProcessor(configList: Indexable[]) {
function expressionsGraphql(config: Indexable, idx: number) { function expressionsGraphql(config: Indexable, idx: number) {
if (!(config.metrics && config.metrics[0])) { if (!(config.metrics && config.metrics[0])) {
@ -100,12 +109,12 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
return { source: {}, tips: [], typesOfMQE: [] }; return { source: {}, tips: [], typesOfMQE: [] };
} }
const tips: string[] = []; const tips: string[] = [];
const source: { [key: string]: unknown } = {}; const source: Indexable<unknown> = {};
const keys = Object.keys(resp.data); const keys = Object.keys(resp.data);
const typesOfMQE: string[] = []; const typesOfMQE: string[] = [];
for (let i = 0; i < config.metrics.length; i++) { for (let i = 0; i < config.metrics.length; i++) {
const c: MetricConfigOpt = (config.metricConfig && config.metricConfig[i]) || {}; const metricConfig: MetricConfigOpt = (config.metricConfig && config.metricConfig[i]) || {};
const obj = resp.data[keys[i]] || {}; const obj = resp.data[keys[i]] || {};
const results = obj.results || []; const results = obj.results || [];
const name = config.metrics[i]; const name = config.metrics[i];
@ -116,15 +125,15 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
if (!obj.error) { if (!obj.error) {
if ([ExpressionResultType.SINGLE_VALUE, ExpressionResultType.TIME_SERIES_VALUES].includes(type)) { if ([ExpressionResultType.SINGLE_VALUE, ExpressionResultType.TIME_SERIES_VALUES].includes(type)) {
for (const item of results) { for (const item of results) {
const label = let label =
item.metric && item.metric &&
item.metric.labels.map((d: { key: string; value: string }) => `${d.key}=${d.value}`).join(","); item.metric.labels.map((d: { key: string; value: string }) => `${d.key}=${d.value}`).join(",");
const values = item.values.map((d: { value: unknown }) => d.value) || []; const values = item.values.map((d: { value: unknown }) => d.value) || [];
if (results.length === 1) { if (results.length === 1) {
source[label || c.label || name] = values; // If the metrics label does not exist, use the configuration label or expression
} else { label = label ? `${metricConfig.label || name}, ${label}` : metricConfig.label || name;
source[label] = values;
} }
source[label] = values;
} }
} }
if (([ExpressionResultType.RECORD_LIST, ExpressionResultType.SORTED_LIST] as string[]).includes(type)) { if (([ExpressionResultType.RECORD_LIST, ExpressionResultType.SORTED_LIST] as string[]).includes(type)) {
@ -139,7 +148,7 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const variables: string[] = [`$duration: Duration!`]; const variables: string[] = [`$duration: Duration!`];
let fragments = ""; let fragments = "";
let conditions: Recordable = { let conditions: Recordable<unknown> = {
duration: appStore.durationTime, duration: appStore.durationTime,
}; };
for (let i = 0; i < configArr.length; i++) { for (let i = 0; i < configArr.length; i++) {
@ -181,13 +190,6 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
return { 0: { source: {}, tips: [], typesOfMQE: [] } }; return { 0: { source: {}, tips: [], typesOfMQE: [] } };
} }
} }
function chunkArray(array: any[], chunkSize: number) {
const result = [];
for (let i = 0; i < array.length; i += chunkSize) {
result.push(array.slice(i, i + chunkSize));
}
return result;
}
const partArr = chunkArray(configList, 6); const partArr = chunkArray(configList, 6);
const promiseArr = partArr.map((d: Array<Indexable>) => fetchMetrics(d)); const promiseArr = partArr.map((d: Array<Indexable>) => fetchMetrics(d));
@ -394,7 +396,7 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
function getExpressionQuery() { function getExpressionQuery(partMetrics?: string[]) {
const conditions: { [key: string]: unknown } = { const conditions: { [key: string]: unknown } = {
duration: appStore.durationTime, duration: appStore.durationTime,
}; };
@ -448,7 +450,7 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
}; };
variables.push(`$entity${index}: Entity!`); variables.push(`$entity${index}: Entity!`);
conditions[`entity${index}`] = entity; conditions[`entity${index}`] = entity;
const f = metrics.map((name: string, idx: number) => { const f = (partMetrics || metrics).map((name: string, idx: number) => {
if (index === 0) { if (index === 0) {
variables.push(`$expression${idx}: String!`); variables.push(`$expression${idx}: String!`);
conditions[`expression${idx}`] = name; conditions[`expression${idx}`] = name;
@ -462,19 +464,19 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
return { queryStr, conditions }; return { queryStr, conditions };
} }
function handleExpressionValues(resp: { [key: string]: any }) { function handleExpressionValues(partMetrics: string[], resp: { [key: string]: any }) {
const obj: any = {}; const obj: Indexable = {};
for (let idx = 0; idx < instances.length; idx++) { for (let idx = 0; idx < instances.length; idx++) {
for (let index = 0; index < metrics.length; index++) { for (let index = 0; index < partMetrics.length; index++) {
const k = "expression" + idx + index; const k = "expression" + idx + index;
if (metrics[index]) { if (partMetrics[index]) {
if (!obj[metrics[index]]) { if (!obj[partMetrics[index]]) {
obj[metrics[index]] = { obj[partMetrics[index]] = {
values: [], values: [],
}; };
} }
obj[metrics[index]].values.push({ obj[partMetrics[index]].values.push({
value: resp[k].results[0] && resp[k].results[0].values[0].value, value: resp[k] && resp[k].results[0] && resp[k].results[0].values[0].value,
id: instances[idx].id, id: instances[idx].id,
}); });
} }
@ -482,6 +484,31 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
} }
return obj; return obj;
} }
async function fetchMetrics(partMetrics: string[]) {
const topologyStore = useTopologyStore();
const param = getExpressionQuery(partMetrics);
const res = await topologyStore.getTopologyExpressionValue(param);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
return handleExpressionValues(partMetrics, res.data);
}
return { getExpressionQuery, handleExpressionValues }; async function getMetrics() {
const count = Math.floor(MaxQueryLength / instances.length);
const metricsArr = chunkArray(metrics, count);
const promiseArr = metricsArr.map((d: string[]) => fetchMetrics(d));
const responseList = await Promise.all(promiseArr);
let resp = {};
for (const item of responseList) {
resp = {
...resp,
...item,
};
}
return resp;
}
return { getMetrics, getExpressionQuery };
} }

View File

@ -35,7 +35,7 @@ export default function useLegendProcess(legend?: LegendOptions) {
if (keys.length === 1) { if (keys.length === 1) {
return false; return false;
} }
if (legend && legend.asTable) { if (legend && (legend.asTable || legend.asSelector)) {
return false; return false;
} }
return true; return true;

47
src/hooks/useSnapshot.ts Normal file
View File

@ -0,0 +1,47 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { MetricsResults } from "@/types/dashboard";
export function useSnapshot(metrics: { name: string; results: MetricsResults[] }[]) {
function processResults() {
const sources = metrics.map((metric: { name: string; results: MetricsResults[] }) => {
const values = metric.results.map(
(r: { values: { value: string }[]; metric: { labels: { key: string; value: string }[] } }) => {
const arr = r.values.map((v: { value: string }) => Number(v.value));
if (!r.metric.labels.length) {
return { values: arr };
}
const name = r.metric.labels
.map(
(label: { key: string; value: string }) =>
`${metric.name}${label ? "{" : ""}${label.key}=${label.value}${label ? "}" : ""}`,
)
.join(",");
return { name, values: arr };
},
);
return { name: metric.name, values };
});
return sources;
}
return {
processResults,
};
}

View File

@ -61,7 +61,7 @@ limitations under the License. -->
<Icon iconName="retry" :loading="appStore.autoRefresh" class="middle" /> <Icon iconName="retry" :loading="appStore.autoRefresh" class="middle" />
</span> </span>
<span class="version ml-5 cp"> <span class="version ml-5 cp">
<el-popover trigger="hover" width="250" placement="bottom" :content="appStore.version"> <el-popover trigger="hover" :width="250" placement="bottom" :content="appStore.version">
<template #reference> <template #reference>
<span> <span>
<Icon iconName="info_outline" size="middle" /> <Icon iconName="info_outline" size="middle" />

View File

@ -50,7 +50,7 @@ limitations under the License. -->
</el-menu-item> </el-menu-item>
</el-menu-item-group> </el-menu-item-group>
</el-sub-menu> </el-sub-menu>
<el-menu-item :index="String(menu.name)" @click="changePage(menu)" v-else> <el-menu-item :index="String(menu.name)" v-else>
<el-icon class="menu-icons" :style="{ marginRight: '12px' }" @mouseover="setCollapse"> <el-icon class="menu-icons" :style="{ marginRight: '12px' }" @mouseover="setCollapse">
<router-link class="items menu-title" :to="menu.children[0].path"> <router-link class="items menu-title" :to="menu.children[0].path">
<Icon size="lg" :iconName="menu.meta.icon" /> <Icon size="lg" :iconName="menu.meta.icon" />
@ -83,7 +83,6 @@ limitations under the License. -->
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const router = useRouter(); const router = useRouter();
const name = ref<string>(String(router.currentRoute.value.name)); const name = ref<string>(String(router.currentRoute.value.name));
const theme = ["VirtualMachine", "Kubernetes"].includes(name.value || "") ? ref("light") : ref("black");
const routes = ref<RouteRecordRaw[] | any>( const routes = ref<RouteRecordRaw[] | any>(
(router.options.routes || []).filter((d: any) => d.meta && d.meta.activate), (router.options.routes || []).filter((d: any) => d.meta && d.meta.activate),
); );
@ -100,9 +99,7 @@ limitations under the License. -->
if (route.name === "ViewWidget") { if (route.name === "ViewWidget") {
showMenu.value = false; showMenu.value = false;
} }
const changePage = (menu: RouteRecordRaw) => {
theme.value = ["VirtualMachine", "Kubernetes"].includes(String(menu.name)) ? "light" : "black";
};
const filterMenus = (menus: Recordable[]) => { const filterMenus = (menus: Recordable[]) => {
return menus.filter((d) => d.meta && !d.meta.notShow && d.meta.activate); return menus.filter((d) => d.meta && !d.meta.notShow && d.meta.activate);
}; };

View File

@ -139,7 +139,6 @@ const msg = {
enableAssociate: "Enable association", enableAssociate: "Enable association",
text: "Text", text: "Text",
query: "Query", query: "Query",
endpointTips: "The table shows up to 20 pieces of endpoints.",
viewTrace: "View Related Traces", viewTrace: "View Related Traces",
relatedTraceOptions: "Related Trace Options", relatedTraceOptions: "Related Trace Options",
setLatencyDuration: "Latency Related Metrics", setLatencyDuration: "Latency Related Metrics",
@ -154,6 +153,7 @@ const msg = {
legendOptions: "Legend Options", legendOptions: "Legend Options",
showLegend: "Show Legend", showLegend: "Show Legend",
asTable: "As Table", asTable: "As Table",
asSelector: "As Selector",
toTheRight: "To The Right", toTheRight: "To The Right",
legendValues: "Legend Values", legendValues: "Legend Values",
minDuration: "Minimal Request Duration", minDuration: "Minimal Request Duration",
@ -386,5 +386,16 @@ const msg = {
tabExpressions: "Tab Expressions", tabExpressions: "Tab Expressions",
hierarchyNodeMetrics: "Metrics for Hierarchy Graph Node", hierarchyNodeMetrics: "Metrics for Hierarchy Graph Node",
hierarchyNodeDashboard: "As dashboard for Hierarchy Graph Node", hierarchyNodeDashboard: "As dashboard for Hierarchy Graph Node",
valueMappings: "Value Mappings",
mappingTip: "Notice: The mapping key is a Regex string, e.g. ^([0-9])$",
valueDashboard: "Data Value Related Dashboard",
viewValueDashboard: "View Dashboard",
errorInstances: "Error Instances",
successInstances: "Success Instances",
profilingEvents: "Async Profiling Events",
execArgs: "Exec Args",
instances: "Instances",
snapshot: "Snapshot",
expression: "Expression",
}; };
export default msg; export default msg;

View File

@ -138,7 +138,6 @@ const msg = {
"El nombre sólo admite chino e inglés, líneas horizontales y subrayado, y la longitud del nombre no excederá de 300 caracteres", "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", enableAssociate: "Activar asociación",
query: "Consulta", query: "Consulta",
endpointTips: "Aquí, la tabla muestra hasta 20 punto final.",
queryOrder: "Consulta por duración", queryOrder: "Consulta por duración",
setOrder: "Orden de consulta", setOrder: "Orden de consulta",
latency: "Retraso", latency: "Retraso",
@ -386,5 +385,17 @@ const msg = {
tabExpressions: "Tab Expressions", tabExpressions: "Tab Expressions",
hierarchyNodeMetrics: "Metrics for Hierarchy Graph Node", hierarchyNodeMetrics: "Metrics for Hierarchy Graph Node",
hierarchyNodeDashboard: "As dashboard for Hierarchy Graph Node", hierarchyNodeDashboard: "As dashboard for Hierarchy Graph Node",
valueMappings: "Value Mappings",
mappingTip: "Aviso: La clave de mapeo es una cadena Regex, p. ej. ^([0-9])$",
valueDashboard: "Data Value Related Dashboard",
viewValueDashboard: "View Dashboard",
errorInstances: "Error Instances",
successInstances: "Success Instances",
profilingEvents: "Async Profiling Events",
execArgs: "Exec Args",
instances: "Instances",
snapshot: "Snapshot",
expression: "Expression",
asSelector: "As Selector",
}; };
export default msg; export default msg;

View File

@ -128,11 +128,20 @@ const titles = {
self_observability_java_agent: "SkyWalking Java Agent", self_observability_java_agent: "SkyWalking Java Agent",
self_observability_java_agent_desc: self_observability_java_agent_desc:
"The self observability of SkyWalking Java Agent, which provides the abilities to measure the tracing performance and error statistics of plugins.", "The self observability of SkyWalking Java Agent, which provides the abilities to measure the tracing performance and error statistics of plugins.",
self_observability_go_agent: "SkyWalking Go Agent",
self_observability_go_agent_desc:
"The self observability of SkyWalking Go Agent, which provides the abilities to measure the tracing performance and error statistics of plugins.",
cilium: "Cilium", cilium: "Cilium",
cilium_desc: cilium_desc:
"Cilium is a CNI plugin for Kubernetes that provides eBPF-based networking, security, and load balancing.", "Cilium is a CNI plugin for Kubernetes that provides eBPF-based networking, security, and load balancing.",
cilium_service: "Cilium Service", cilium_service: "Cilium Service",
cilium_service_desc: "Observe Service status and resources from Cilium Hubble.", cilium_service_desc: "Observe Service status and resources from Cilium Hubble.",
data_processing_engine: "Data Processing Engine",
data_processing_engine_desc:
"A data processing engine is a system designed to efficiently process, transform, and analyze large-scale data in real time or batch mode.",
data_processing_engine_flink: "Flink",
data_processing_engine_flink_desc:
"Apache Flink is a framework and distributed processing engine for stateful computations over unbounded and bounded data streams. Flink has been designed to run in all common cluster environments, perform computations at in-memory speed and at any scale.",
}; };
export default titles; export default titles;

View File

@ -128,12 +128,21 @@ const titles = {
"Satellite: an open-source agent designed for the cloud-native infrastructures, which provides a low-cost, high-efficient, and more secure way to collect telemetry data. It is the recommended load balancer for telemetry collecting.", "Satellite: an open-source agent designed for the cloud-native infrastructures, which provides a low-cost, high-efficient, and more secure way to collect telemetry data. It is the recommended load balancer for telemetry collecting.",
self_observability_java_agent: "SkyWalking Java Agent", self_observability_java_agent: "SkyWalking Java Agent",
self_observability_java_agent_desc: self_observability_java_agent_desc:
"The self observability of SkyWalking Java Agent, which provides the abilities to measure the tracing performance and error statistics of plugins.", "La auto-observabilidad de SkyWalking Java Agent, que proporciona la capacidad de medir el rendimiento del trazado y las estadísticas de errores de los plugins.",
self_observability_go_agent: "SkyWalking Go Agent",
self_observability_go_agent_desc:
"La auto-observabilidad de SkyWalking Go Agent, que proporciona la capacidad de medir el rendimiento del trazado y las estadísticas de errores de los plugins.",
cilium: "Cilium", cilium: "Cilium",
cilium_desc: cilium_desc:
"Cilium es un complemento CNI para Kubernetes que proporciona redes, seguridad y equilibrio de carga basados en eBPF.", "Cilium es un complemento CNI para Kubernetes que proporciona redes, seguridad y equilibrio de carga basados en eBPF.",
cilium_service: "Cilium Service", cilium_service: "Cilium Service",
cilium_service_desc: "Observe el estado del servicio y los recursos de Cilium Hubble.", cilium_service_desc: "Observe el estado del servicio y los recursos de Cilium Hubble.",
data_processing_engine: "Data Processing Engine",
data_processing_engine_desc:
"A data processing engine is a system designed to efficiently process, transform, and analyze large-scale data in real time or batch mode.",
data_processing_engine_flink: "Flink",
data_processing_engine_flink_desc:
"Apache Flink is a framework and distributed processing engine for stateful computations over unbounded and bounded data streams. Flink has been designed to run in all common cluster environments, perform computations at in-memory speed and at any scale.",
}; };
export default titles; export default titles;

View File

@ -113,10 +113,17 @@ const titles = {
"Satellite为云原生基础设施设计的开源代理提供了一种低成本、高效、更安全的遥测数据收集方式。它是遥测采集的推荐负载均衡器。", "Satellite为云原生基础设施设计的开源代理提供了一种低成本、高效、更安全的遥测数据收集方式。它是遥测采集的推荐负载均衡器。",
self_observability_java_agent: "SkyWalking Java Agent", self_observability_java_agent: "SkyWalking Java Agent",
self_observability_java_agent_desc: "SkyWalking Java Agent 自监控提供了对 agent 插件的性能追踪和错误统计。", self_observability_java_agent_desc: "SkyWalking Java Agent 自监控提供了对 agent 插件的性能追踪和错误统计。",
self_observability_go_agent: "SkyWalking Go Agent",
self_observability_go_agent_desc: "SkyWalking Go Agent 自监控提供了对 agent 插件的性能追踪和错误统计。",
cilium: "Cilium", cilium: "Cilium",
cilium_desc: "Cilium是Kubernetes上的CNI插件提供基于eBPF的网络、安全和负载均衡。", cilium_desc: "Cilium是Kubernetes上的CNI插件提供基于eBPF的网络、安全和负载均衡。",
cilium_service: "Cilium服务", cilium_service: "Cilium服务",
cilium_service_desc: "通过Cilium Hubble收集的遥测数据观察服务。", cilium_service_desc: "通过Cilium Hubble收集的遥测数据观察服务。",
data_processing_engine: "数据处理引擎",
data_processing_engine_desc: "数据处理引擎是一个用于高效地在实时或批处理模式下处理、转换和分析大规模数据的系统。",
data_processing_engine_flink: "Flink",
data_processing_engine_flink_desc:
"Apache Flink 是一个框架和分布式处理引擎用于在无边界和有边界数据流上进行有状态的计算。Flink 能在所有常见集群环境中运行,并能以内存速度和任意规模进行计算。",
}; };
export default titles; export default titles;

View File

@ -137,7 +137,6 @@ const msg = {
duplicateName: "重复的名称", duplicateName: "重复的名称",
text: "文本", text: "文本",
query: "查询", query: "查询",
endpointTips: "这里最多展示20条endpoints。",
viewTrace: "查看相关Trace", viewTrace: "查看相关Trace",
relatedTraceOptions: "相关的Trace选项", relatedTraceOptions: "相关的Trace选项",
setLatencyDuration: "延迟相关指标", setLatencyDuration: "延迟相关指标",
@ -152,6 +151,7 @@ const msg = {
legendOptions: "图例选项", legendOptions: "图例选项",
showLegend: "显示图例", showLegend: "显示图例",
asTable: "作为表格", asTable: "作为表格",
asSelector: "作为选择器",
toTheRight: "在右边", toTheRight: "在右边",
legendValues: "图例值", legendValues: "图例值",
minDuration: "最小请求持续时间", minDuration: "最小请求持续时间",
@ -384,5 +384,16 @@ const msg = {
tabExpressions: "Tab表达式", tabExpressions: "Tab表达式",
hierarchyNodeMetrics: "层次图节点的指标", hierarchyNodeMetrics: "层次图节点的指标",
hierarchyNodeDashboard: "作为层次图节点的dashboard", hierarchyNodeDashboard: "作为层次图节点的dashboard",
valueMappings: "值映射",
mappingTip: "注意: 映射键是一个正则表达式字符串,比如 ^([0-9])$",
valueDashboard: "数据值相关的仪表板",
viewValueDashboard: "查看仪表板",
errorInstances: "错误的实例",
successInstances: "成功的实例",
profilingEvents: "异步分析事件",
execArgs: "String任务扩展",
instances: "实例",
snapshot: "快照",
expression: "表达式",
}; };
export default msg; export default msg;

View File

@ -33,7 +33,7 @@ export const routesAlarm: Array<RouteRecordRaw> = [
children: [ children: [
{ {
path: "/alerting", path: "/alerting",
name: "Alarm", name: "ViewAlarm",
component: Alarm, component: Alarm,
}, },
], ],

View File

@ -100,7 +100,7 @@ export const routesDashboard: Array<RouteRecordRaw> = [
path: "", path: "",
redirect: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name", redirect: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name",
component: Edit, component: Edit,
name: "ViewServiceRelation", name: "ServiceRelations",
meta: { meta: {
notShow: true, notShow: true,
}, },
@ -121,7 +121,7 @@ export const routesDashboard: Array<RouteRecordRaw> = [
path: "", path: "",
redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:name", redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:name",
component: Edit, component: Edit,
name: "ViewPod", name: "Pods",
meta: { meta: {
notShow: true, notShow: true,
}, },
@ -142,7 +142,7 @@ export const routesDashboard: Array<RouteRecordRaw> = [
path: "", path: "",
redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:name", redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:name",
component: Edit, component: Edit,
name: "ViewProcess", name: "Processes",
meta: { meta: {
notShow: true, notShow: true,
}, },
@ -163,7 +163,7 @@ export const routesDashboard: Array<RouteRecordRaw> = [
path: "", path: "",
redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name", redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name",
component: Edit, component: Edit,
name: "PodRelation", name: "PodRelations",
meta: { meta: {
notShow: true, notShow: true,
}, },
@ -185,7 +185,7 @@ export const routesDashboard: Array<RouteRecordRaw> = [
redirect: redirect:
"/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name", "/dashboard/:layerId/:entity/:serviceId/:podId/:processId/:destServiceId/:destPodId/:destProcessId/:name",
component: Edit, component: Edit,
name: "ProcessRelation", name: "ProcessRelations",
meta: { meta: {
notShow: true, notShow: true,
}, },

View File

@ -37,16 +37,8 @@ const router = createRouter({
routes, routes,
}); });
(window as any).axiosCancel = []; router.beforeEach((to, _, next) => {
router.beforeEach((to, from, next) => {
// const token = window.localStorage.getItem("skywalking-authority"); // const token = window.localStorage.getItem("skywalking-authority");
if ((window as any).axiosCancel.length !== 0) {
for (const func of (window as any).axiosCancel) {
setTimeout(func(), 0);
}
(window as any).axiosCancel = [];
}
if (to.path === "/") { if (to.path === "/") {
let defaultPath = ""; let defaultPath = "";

View File

@ -33,7 +33,7 @@ export const routesSettings: Array<RouteRecordRaw> = [
children: [ children: [
{ {
path: "/settings", path: "/settings",
name: "Settings", name: "ViewSettings",
component: Settings, component: Settings,
}, },
], ],

View File

@ -47,6 +47,7 @@ export const ControlsTypes = [
WidgetType.DemandLog, WidgetType.DemandLog,
WidgetType.Ebpf, WidgetType.Ebpf,
WidgetType.NetworkProfiling, WidgetType.NetworkProfiling,
WidgetType.AsyncProfiling,
WidgetType.ThirdPartyApp, WidgetType.ThirdPartyApp,
WidgetType.ContinuousProfiling, WidgetType.ContinuousProfiling,
WidgetType.TaskTimeline, WidgetType.TaskTimeline,
@ -55,3 +56,5 @@ export enum EBPFProfilingTriggerType {
FIXED_TIME = "FIXED_TIME", FIXED_TIME = "FIXED_TIME",
CONTINUOUS_PROFILING = "CONTINUOUS_PROFILING", CONTINUOUS_PROFILING = "CONTINUOUS_PROFILING",
} }
export const EndpointsTopNDefault = 20;

View File

@ -17,7 +17,6 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import type { Alarm } from "@/types/alarm"; import type { Alarm } from "@/types/alarm";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
@ -37,30 +36,22 @@ export const alarmStore = defineStore({
actions: { actions: {
async getAlarms(params: Recordable) { async getAlarms(params: Recordable) {
this.loading = true; this.loading = true;
const res: AxiosResponse = await graphql.query("queryAlarms").params(params); const res = await graphql.query("queryAlarms").params(params);
this.loading = false; this.loading = false;
if (res.data.errors) { if (res.errors) {
return res.data; return res;
} }
if (res.data.data.getAlarm.items) { if (res.data.getAlarm.items) {
this.alarms = res.data.data.getAlarm.items; this.alarms = res.data.getAlarm.items;
this.total = res.data.data.getAlarm.total; this.total = res.data.getAlarm.total;
} }
return res.data; return res.data;
}, },
async getAlarmTagKeys() { async getAlarmTagKeys() {
const res: AxiosResponse = await graphql return await graphql.query("queryAlarmTagKeys").params({ duration: useAppStoreWithOut().durationTime });
.query("queryAlarmTagKeys")
.params({ duration: useAppStoreWithOut().durationTime });
return res.data;
}, },
async getAlarmTagValues(tagKey: string) { async getAlarmTagValues(tagKey: string) {
const res: AxiosResponse = await graphql return await graphql.query("queryAlarmTagValues").params({ tagKey, duration: useAppStoreWithOut().durationTime });
.query("queryAlarmTagValues")
.params({ tagKey, duration: useAppStoreWithOut().durationTime });
return res.data;
}, },
}, },
}); });

View File

@ -19,7 +19,6 @@ import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { Duration, DurationTime } from "@/types/app"; import type { Duration, DurationTime } from "@/types/app";
import getLocalTime from "@/utils/localtime"; import getLocalTime from "@/utils/localtime";
import type { AxiosResponse } from "axios";
import dateFormatStep, { dateFormatTime } from "@/utils/dateFormat"; import dateFormatStep, { dateFormatTime } from "@/utils/dateFormat";
import { TimeType } from "@/constants/data"; import { TimeType } from "@/constants/data";
import type { MenuOptions, SubItem } from "@/types/app"; import type { MenuOptions, SubItem } from "@/types/app";
@ -118,12 +117,6 @@ export const appStore = defineStore({
actions: { actions: {
setDuration(data: Duration): void { setDuration(data: Duration): void {
this.durationRow = data; this.durationRow = data;
if ((window as any).axiosCancel.length !== 0) {
for (const event of (window as any).axiosCancel) {
setTimeout(event(), 0);
}
(window as any).axiosCancel = [];
}
this.runEventStack(); this.runEventStack();
}, },
updateDurationRow(data: Duration) { updateDurationRow(data: Duration) {
@ -185,11 +178,11 @@ export const appStore = defineStore({
}); });
}, },
async queryOAPTimeInfo() { async queryOAPTimeInfo() {
const res: AxiosResponse = await graphql.query("queryOAPTimeInfo").params({}); const res = await graphql.query("queryOAPTimeInfo").params({});
if (res.data.errors) { if (res.errors) {
this.utc = -(new Date().getTimezoneOffset() / 60) + ":0"; this.utc = -(new Date().getTimezoneOffset() / 60) + ":0";
} else { } else {
this.utc = res.data.data.getTimeInfo.timezone / 100 + ":0"; this.utc = res.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]);
@ -197,21 +190,21 @@ export const appStore = defineStore({
return res.data; return res.data;
}, },
async fetchVersion(): Promise<void> { async fetchVersion() {
const res: AxiosResponse = await graphql.query("queryOAPVersion").params({}); const res = await graphql.query("queryOAPVersion").params({});
if (res.data.errors) { if (res.errors) {
return res.data; return res;
} }
this.version = res.data.data.version; this.version = res.data.version;
return res.data; return res.data;
}, },
async queryMenuItems() { async queryMenuItems() {
const res: AxiosResponse = await graphql.query("queryMenuItems").params({}); const res = await graphql.query("queryMenuItems").params({});
if (res.data.errors) { if (res.errors) {
return res.data; return res;
} }
return res.data.data; return res.data;
}, },
setReloadTimer(timer: IntervalHandle) { setReloadTimer(timer: IntervalHandle) {
this.reloadTimer = timer; this.reloadTimer = timer;

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 { defineStore } from "pinia";
import type {
AsyncProfilingTask,
AsyncProfileTaskCreationRequest,
AsyncProfilerStackElement,
AsyncProfilerTaskProgress,
} from "@/types/async-profiling";
import { store } from "@/store";
import graphql from "@/graphql";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import type { Instance } from "@/types/selector";
interface AsyncProfilingState {
taskList: Array<Recordable<AsyncProfilingTask>>;
selectedTask: Recordable<AsyncProfilingTask>;
taskProgress: Recordable<AsyncProfilerTaskProgress>;
instances: Instance[];
analyzeTrees: AsyncProfilerStackElement[];
loadingTree: boolean;
loadingTasks: boolean;
}
export const asyncProfilingStore = defineStore({
id: "asyncProfiling",
state: (): AsyncProfilingState => ({
taskList: [],
selectedTask: {},
taskProgress: {},
instances: [],
analyzeTrees: [],
loadingTree: false,
loadingTasks: false,
}),
actions: {
setSelectedTask(task: Recordable<AsyncProfilingTask>) {
this.selectedTask = task || {};
},
setAnalyzeTrees(tree: AsyncProfilerStackElement[]) {
this.analyzeTrees = tree;
},
async getTaskList() {
const selectorStore = useSelectorStore();
this.loadingTasks = true;
const response = await graphql.query("getAsyncTaskList").params({
request: {
serviceId: selectorStore.currentService.id,
limit: 10000,
},
});
this.loadingTasks = false;
if (response.errors) {
return response;
}
this.taskList = response.data.asyncTaskList.tasks || [];
this.selectedTask = this.taskList[0] || {};
this.setAnalyzeTrees([]);
this.setSelectedTask(this.selectedTask);
if (!this.taskList.length) {
return response;
}
return response;
},
async getTaskLogs(param: { taskID: string }) {
const response = await graphql.query("getAsyncProfileTaskProcess").params(param);
if (response.errors) {
return response;
}
this.taskProgress = response.data.taskProgress;
return response;
},
async getServiceInstances(param: { serviceId: string; isRelation: boolean }) {
if (!param.serviceId) {
return null;
}
const response = await graphql.query("queryInstances").params({
serviceId: param.serviceId,
duration: useAppStoreWithOut().durationTime,
});
if (!response.errors) {
this.instances = (response.data.pods || []).map((d: Instance) => {
d.value = d.id || "";
return d;
});
}
return response;
},
async createTask(param: AsyncProfileTaskCreationRequest) {
const response = await graphql.query("saveAsyncProfileTask").params({ asyncProfilerTaskCreationRequest: param });
if (response.errors) {
return response;
}
this.getTaskList();
return response;
},
async getAsyncProfilingAnalyze(params: { taskId: string; instanceIds: Array<string>; eventType: string }) {
if (!params.instanceIds.length) {
return new Promise((resolve) => resolve({}));
}
this.loadingTree = true;
const response = await graphql.query("getAsyncProfileAnalyze").params({ request: params });
this.loadingTree = false;
if (response.errors) {
this.analyzeTrees = [];
return response;
}
const { analysisResult } = response.data;
if (!analysisResult) {
this.analyzeTrees = [];
return response;
}
this.analyzeTrees = [analysisResult.tree];
return response;
},
},
});
export function useAsyncProfilingStore(): Recordable {
return asyncProfilingStore(store);
}

View File

@ -21,7 +21,6 @@ import type { Instance } from "@/types/selector";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { MonitorInstance, MonitorProcess } from "@/types/continous-profiling"; import type { MonitorInstance, MonitorProcess } from "@/types/continous-profiling";
import type { AxiosResponse } from "axios";
import { dateFormat } from "@/utils/dateFormat"; import { dateFormat } from "@/utils/dateFormat";
interface ContinousProfilingState { interface ContinousProfilingState {
@ -84,37 +83,37 @@ export const continousProfilingStore = defineStore({
checkItems: CheckItems[]; checkItems: CheckItems[];
}[], }[],
) { ) {
const res: AxiosResponse = await graphql.query("editStrategy").params({ const response = await graphql.query("editStrategy").params({
request: { request: {
serviceId, serviceId,
targets, targets,
}, },
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
return res.data; return response;
}, },
async getStrategyList(params: { serviceId: string }) { async getStrategyList(params: { serviceId: string }) {
if (!params.serviceId) { if (!params.serviceId) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
this.policyLoading = true; this.policyLoading = true;
const res: AxiosResponse = await graphql.query("getStrategyList").params(params); const response = await graphql.query("getStrategyList").params(params);
this.policyLoading = false; this.policyLoading = false;
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
const list = res.data.data.strategyList || []; const list = response.data.strategyList || [];
if (!list.length) { if (!list.length) {
this.taskList = []; this.taskList = [];
this.instances = []; this.instances = [];
this.instance = null; this.instance = null;
} }
const arr = list.length ? res.data.data.strategyList : [{ type: "", checkItems: [{ type: "" }] }]; const arr = list.length ? response.data.strategyList : [{ type: "", checkItems: [{ type: "" }] }];
this.strategyList = arr.map((d: StrategyItem, index: number) => { this.strategyList = arr.map((d: StrategyItem, index: number) => {
return { return {
...d, ...d,
@ -123,25 +122,25 @@ export const continousProfilingStore = defineStore({
}); });
this.setSelectedStrategy(this.strategyList[0]); this.setSelectedStrategy(this.strategyList[0]);
if (!this.selectedStrategy.type) { if (!this.selectedStrategy.type) {
return res.data; return response;
} }
this.getMonitoringInstances(params.serviceId); this.getMonitoringInstances(params.serviceId);
return res.data; return response;
}, },
async getMonitoringInstances(serviceId: string): Promise<Nullable<AxiosResponse>> { async getMonitoringInstances(serviceId: string) {
this.instancesLoading = true; this.instancesLoading = true;
if (!serviceId) { if (!serviceId) {
return null; return null;
} }
const res: AxiosResponse = await graphql.query("getMonitoringInstances").params({ const response = await graphql.query("getMonitoringInstances").params({
serviceId, serviceId,
target: this.selectedStrategy.type, target: this.selectedStrategy.type,
}); });
this.instancesLoading = false; this.instancesLoading = false;
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.instances = (res.data.data.instances || []) this.instances = (response.data.instances || [])
.map((d: MonitorInstance) => { .map((d: MonitorInstance) => {
const processes = (d.processes || []) const processes = (d.processes || [])
.sort((c: MonitorProcess, d: MonitorProcess) => d.lastTriggerTimestamp - c.lastTriggerTimestamp) .sort((c: MonitorProcess, d: MonitorProcess) => d.lastTriggerTimestamp - c.lastTriggerTimestamp)
@ -161,7 +160,7 @@ export const continousProfilingStore = defineStore({
}) })
.sort((a: MonitorInstance, b: MonitorInstance) => b.lastTriggerTimestamp - a.lastTriggerTimestamp); .sort((a: MonitorInstance, b: MonitorInstance) => b.lastTriggerTimestamp - a.lastTriggerTimestamp);
this.instance = this.instances[0] || null; this.instance = this.instances[0] || null;
return res.data; return response;
}, },
}, },
}); });

View File

@ -18,11 +18,10 @@ import { defineStore } from "pinia";
import { store } from "@/store"; import { store } from "@/store";
import type { LayoutConfig } from "@/types/dashboard"; import type { LayoutConfig } from "@/types/dashboard";
import graphql from "@/graphql"; import graphql from "@/graphql";
import query from "@/graphql/fetch"; import fetchQuery from "@/graphql/fetch";
import type { DashboardItem } from "@/types/dashboard"; import type { DashboardItem } from "@/types/dashboard";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { NewControl, TextConfig, TimeRangeConfig, ControlsTypes } from "../data"; import { NewControl, TextConfig, TimeRangeConfig, ControlsTypes } from "../data";
import type { AxiosResponse } from "axios";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { EntityType, WidgetType } from "@/views/dashboard/data"; import { EntityType, WidgetType } from "@/views/dashboard/data";
interface DashboardState { interface DashboardState {
@ -299,37 +298,20 @@ export const dashboardStore = defineStore({
} }
} }
}, },
async fetchMetricType(item: string) {
const res: AxiosResponse = await graphql.query("queryTypeOfMetrics").params({ name: item });
return res.data;
},
async getTypeOfMQE(expression: string) {
const res: AxiosResponse = await graphql.query("getTypeOfMQE").params({ expression });
return res.data;
},
async fetchMetricList(regex: string) {
const res: AxiosResponse = await graphql.query("queryMetrics").params({ regex });
return res.data;
},
async fetchMetricValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) { async fetchMetricValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const res: AxiosResponse = await query(param); return await fetchQuery(param);
return res.data;
}, },
async fetchTemplates() { async fetchTemplates() {
const res: AxiosResponse = await graphql.query("getTemplates").params({}); const res = await graphql.query("getTemplates").params({});
if (res.data.errors) { if (res.errors) {
return res.data; return res;
} }
const data = res.data.data.getAllTemplates; const data = res.data.getAllTemplates;
let list = []; let list = [];
for (const t of data) { for (const t of data) {
const c = JSON.parse(t.configuration); const c = JSON.parse(t.configuration);
const key = [c.layer, c.entity, c.name].join("_"); const key = [c.layer, c.entity, c.name].join("_");
list.push({ list.push({
...c, ...c,
id: t.id, id: t.id,
@ -372,20 +354,20 @@ export const dashboardStore = defineStore({
this.dashboards = JSON.parse(sessionStorage.getItem("dashboards") || "[]"); this.dashboards = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
}, },
async updateDashboard(setting: { id: string; configuration: string }) { async updateDashboard(setting: { id: string; configuration: string }) {
const res: AxiosResponse = await graphql.query("updateTemplate").params({ const resp = await graphql.query("updateTemplate").params({
setting, setting,
}); });
if (res.data.errors) { if (resp.errors) {
ElMessage.error(res.data.errors); ElMessage.error(resp.errors);
return res.data; return resp;
} }
const json = res.data.data.changeTemplate; const json = resp.data.changeTemplate;
if (!json.status) { if (!json.status) {
ElMessage.error(json.message); ElMessage.error(json.message);
return res.data; return resp;
} }
ElMessage.success("Saved successfully"); ElMessage.success("Saved successfully");
return res.data; return resp;
}, },
async saveDashboard() { async saveDashboard() {
if (!this.currentDashboard?.name) { if (!this.currentDashboard?.name) {
@ -419,14 +401,14 @@ export const dashboardStore = defineStore({
} }
res = await graphql.query("addNewTemplate").params({ setting: { configuration: JSON.stringify(c) } }); res = await graphql.query("addNewTemplate").params({ setting: { configuration: JSON.stringify(c) } });
json = res.data.data.addTemplate; json = res.data.addTemplate;
if (!json.status) { if (!json.status) {
ElMessage.error(json.message); ElMessage.error(json.message);
} }
} }
if (res.data.errors || res.errors) { if (res.errors) {
ElMessage.error(res.data.errors); ElMessage.error(res.errors);
return res.data; return res;
} }
if (!json.status) { if (!json.status) {
return json; return json;
@ -448,16 +430,16 @@ export const dashboardStore = defineStore({
return json; return json;
}, },
async deleteDashboard() { async deleteDashboard() {
const res: AxiosResponse = await graphql.query("removeTemplate").params({ id: this.currentDashboard?.id }); const res = await graphql.query("removeTemplate").params({ id: this.currentDashboard?.id });
if (res.data.errors) { if (res.errors) {
ElMessage.error(res.data.errors); ElMessage.error(res.errors);
return res.data; return res;
} }
const json = res.data.data.disableTemplate; const json = res.data.disableTemplate;
if (!json.status) { if (!json.status) {
ElMessage.error(json.message); ElMessage.error(json.message);
return res.data; return res;
} }
this.dashboards = this.dashboards.filter((d: Recordable) => d.id !== this.currentDashboard?.id); this.dashboards = this.dashboards.filter((d: Recordable) => d.id !== this.currentDashboard?.id);
const key = [this.currentDashboard?.layer, this.currentDashboard?.entity, this.currentDashboard?.name].join("_"); const key = [this.currentDashboard?.layer, this.currentDashboard?.entity, this.currentDashboard?.name].join("_");

View File

@ -18,7 +18,6 @@ import { defineStore } from "pinia";
import type { Instance } from "@/types/selector"; import type { Instance } from "@/types/selector";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import type { Conditions, Log } from "@/types/demand-log"; import type { Conditions, Log } from "@/types/demand-log";
@ -60,16 +59,16 @@ export const demandLogStore = defineStore({
}, },
async getInstances(id: string) { async getInstances(id: string) {
const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id; const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id;
const res: AxiosResponse = await graphql.query("queryInstances").params({ const response = await graphql.query("queryInstances").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.instances = res.data.data.pods || []; this.instances = response.data.pods || [];
return res.data; return response;
}, },
async getContainers(serviceInstanceId: string) { async getContainers(serviceInstanceId: string) {
if (!serviceInstanceId) { if (!serviceInstanceId) {
@ -78,35 +77,35 @@ export const demandLogStore = defineStore({
const condition = { const condition = {
serviceInstanceId, serviceInstanceId,
}; };
const res: AxiosResponse = await graphql.query("fetchContainers").params({ condition }); const response = await graphql.query("fetchContainers").params({ condition });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
if (res.data.data.containers.errorReason) { if (response.data.containers.errorReason) {
this.containers = [{ label: "", value: "" }]; this.containers = [{ label: "", value: "" }];
return res.data; return response;
} }
this.containers = res.data.data.containers.containers.map((d: string) => { this.containers = response.data.containers.containers.map((d: string) => {
return { label: d, value: d }; return { label: d, value: d };
}); });
return res.data; return response;
}, },
async getDemandLogs() { async getDemandLogs() {
this.loadLogs = true; this.loadLogs = true;
const res: AxiosResponse = await graphql.query("fetchDemandPodLogs").params({ condition: this.conditions }); const response = await graphql.query("fetchDemandPodLogs").params({ condition: this.conditions });
this.loadLogs = false; this.loadLogs = false;
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
if (res.data.data.logs.errorReason) { if (response.data.logs.errorReason) {
this.setLogs([], res.data.data.logs.errorReason); this.setLogs([], response.data.logs.errorReason);
return res.data; return response;
} }
this.total = res.data.data.logs.logs.length; this.total = response.data.logs.logs.length;
const logs = res.data.data.logs.logs.map((d: Log) => d.content).join("\n"); const logs = response.data.logs.logs.map((d: Log) => d.content).join("\n");
this.setLogs(logs); this.setLogs(logs);
return res.data; return response;
}, },
}, },
}); });

View File

@ -19,7 +19,6 @@ import type { Option } from "@/types/app";
import type { EBPFTaskCreationRequest, EBPFProfilingSchedule, EBPFTaskList, AnalyzationTrees } from "@/types/ebpf"; import type { EBPFTaskCreationRequest, EBPFProfilingSchedule, EBPFTaskList, AnalyzationTrees } from "@/types/ebpf";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { EBPFProfilingTriggerType } from "../data"; import { EBPFProfilingTriggerType } from "../data";
interface EbpfState { interface EbpfState {
taskList: Array<Recordable<EBPFTaskList>>; taskList: Array<Recordable<EBPFTaskList>>;
@ -57,70 +56,70 @@ export const ebpfStore = defineStore({
this.analyzeTrees = tree; this.analyzeTrees = tree;
}, },
async getCreateTaskData(serviceId: string) { async getCreateTaskData(serviceId: string) {
const res: AxiosResponse = await graphql.query("getCreateTaskData").params({ serviceId }); const response = await graphql.query("getCreateTaskData").params({ serviceId });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
const json = res.data.data.createTaskData; const json = response.data.createTaskData;
this.couldProfiling = json.couldProfiling || false; this.couldProfiling = json.couldProfiling || false;
this.labels = json.processLabels.map((d: string) => { this.labels = json.processLabels.map((d: string) => {
return { label: d, value: d }; return { label: d, value: d };
}); });
return res.data; return response;
}, },
async createTask(param: EBPFTaskCreationRequest) { async createTask(param: EBPFTaskCreationRequest) {
const res: AxiosResponse = await graphql.query("saveEBPFTask").params({ request: param }); const response = await graphql.query("saveEBPFTask").params({ request: param });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.getTaskList({ this.getTaskList({
serviceId: param.serviceId, serviceId: param.serviceId,
targets: ["ON_CPU", "OFF_CPU"], targets: ["ON_CPU", "OFF_CPU"],
triggerType: EBPFProfilingTriggerType.FIXED_TIME, triggerType: EBPFProfilingTriggerType.FIXED_TIME,
}); });
return res.data; return response;
}, },
async getTaskList(params: { serviceId: string; targets: string[] }) { async getTaskList(params: { serviceId: string; targets: string[] }) {
if (!params.serviceId) { if (!params.serviceId) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql.query("getEBPFTasks").params(params); const response = await graphql.query("getEBPFTasks").params(params);
this.ebpfTips = ""; this.ebpfTips = "";
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.taskList = res.data.data.queryEBPFTasks || []; this.taskList = response.data.queryEBPFTasks || [];
this.selectedTask = this.taskList[0] || {}; this.selectedTask = this.taskList[0] || {};
this.setSelectedTask(this.selectedTask); this.setSelectedTask(this.selectedTask);
if (!this.taskList.length) { if (!this.taskList.length) {
return res.data; return response;
} }
this.getEBPFSchedules({ taskId: String(this.taskList[0].taskId) }); this.getEBPFSchedules({ taskId: String(this.taskList[0].taskId) });
return res.data; return response;
}, },
async getEBPFSchedules(params: { taskId: string }) { async getEBPFSchedules(params: { taskId: string }) {
if (!params.taskId) { if (!params.taskId) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql.query("getEBPFSchedules").params({ ...params }); const response = await graphql.query("getEBPFSchedules").params({ ...params });
if (res.data.errors) { if (response.errors) {
this.eBPFSchedules = []; this.eBPFSchedules = [];
return res.data; return response;
} }
this.ebpfTips = ""; this.ebpfTips = "";
const { eBPFSchedules } = res.data.data; const { eBPFSchedules } = response.data;
this.eBPFSchedules = eBPFSchedules; this.eBPFSchedules = eBPFSchedules;
if (!eBPFSchedules.length) { if (!eBPFSchedules.length) {
this.eBPFSchedules = []; this.eBPFSchedules = [];
this.analyzeTrees = []; this.analyzeTrees = [];
} }
return res.data; return response;
}, },
async getEBPFAnalyze(params: { async getEBPFAnalyze(params: {
scheduleIdList: string[]; scheduleIdList: string[];
@ -134,24 +133,24 @@ export const ebpfStore = defineStore({
if (!params.timeRanges.length) { if (!params.timeRanges.length) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql.query("getEBPFResult").params(params); const response = await graphql.query("getEBPFResult").params(params);
if (res.data.errors) { if (response.errors) {
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
const { analysisEBPFResult } = res.data.data; const { analysisEBPFResult } = response.data;
this.ebpfTips = analysisEBPFResult.tip; this.ebpfTips = analysisEBPFResult.tip;
if (!analysisEBPFResult) { if (!analysisEBPFResult) {
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
if (analysisEBPFResult.tip) { if (analysisEBPFResult.tip) {
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
this.analyzeTrees = analysisEBPFResult.trees; this.analyzeTrees = analysisEBPFResult.trees;
return res.data; return response;
}, },
}, },
}); });

View File

@ -17,11 +17,11 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import type { Event, QueryEventCondition } from "@/types/events"; import type { Event, QueryEventCondition } from "@/types/events";
import type { Instance, Endpoint } from "@/types/selector"; import type { Instance, Endpoint } from "@/types/selector";
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 { EndpointsTopNDefault } from "../data";
interface eventState { interface eventState {
loading: boolean; loading: boolean;
@ -46,47 +46,48 @@ export const eventStore = defineStore({
}, },
async getInstances() { async getInstances() {
const serviceId = useSelectorStore().currentService ? useSelectorStore().currentService.id : ""; const serviceId = useSelectorStore().currentService ? useSelectorStore().currentService.id : "";
const res: AxiosResponse = await graphql.query("queryInstances").params({ const response = await graphql.query("queryInstances").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.instances = [{ value: "", label: "All" }, ...res.data.data.pods] || [{ value: "", label: "All" }]; this.instances = [{ value: "", label: "All" }, ...response.data.pods];
return res.data; return response;
}, },
async getEndpoints(keyword: string) { async getEndpoints(keyword: string) {
const serviceId = useSelectorStore().currentService ? useSelectorStore().currentService.id : ""; const serviceId = useSelectorStore().currentService ? useSelectorStore().currentService.id : "";
if (!serviceId) { if (!serviceId) {
return; return;
} }
const res: AxiosResponse = await graphql.query("queryEndpoints").params({ const response = await graphql.query("queryEndpoints").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
keyword: keyword || "", keyword: keyword || "",
limit: EndpointsTopNDefault,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.endpoints = [{ value: "", label: "All" }, ...res.data.data.pods] || [{ value: "", label: "All" }]; this.endpoints = [{ value: "", label: "All" }, ...response.data.pods];
return res.data; return response;
}, },
async getEvents() { async getEvents() {
this.loading = true; this.loading = true;
const res: AxiosResponse = await graphql.query("queryEvents").params({ const response = await graphql.query("queryEvents").params({
condition: { condition: {
...this.condition, ...this.condition,
time: useAppStoreWithOut().durationTime, time: useAppStoreWithOut().durationTime,
}, },
}); });
this.loading = false; this.loading = false;
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
if (res.data.data.fetchEvents) { if (response.data.fetchEvents) {
this.events = (res.data.data.fetchEvents.events || []).map((item: Event) => { this.events = (response.data.fetchEvents.events || []).map((item: Event) => {
let scope = "Service"; let scope = "Service";
if (item.source.serviceInstance) { if (item.source.serviceInstance) {
scope = "ServiceInstance"; scope = "ServiceInstance";
@ -101,7 +102,7 @@ export const eventStore = defineStore({
return item; return item;
}); });
} }
return res.data; return response;
}, },
}, },
}); });

View File

@ -18,10 +18,10 @@ import { defineStore } from "pinia";
import type { Instance, Endpoint, Service } from "@/types/selector"; import type { Instance, Endpoint, Service } from "@/types/selector";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { EndpointsTopNDefault } from "../data";
interface LogState { interface LogState {
services: Service[]; services: Service[];
@ -61,50 +61,51 @@ export const logStore = defineStore({
}; };
}, },
async getServices(layer: string) { async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({ const response = await graphql.query("queryServices").params({
layer, layer,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.services = res.data.data.services; this.services = response.data.services;
return res.data; return response;
}, },
async getInstances(id: string) { async getInstances(id: string) {
const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id; const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id;
const res: AxiosResponse = await graphql.query("queryInstances").params({ const response = await graphql.query("queryInstances").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.instances = [{ value: "0", label: "All" }, ...res.data.data.pods] || [{ value: " 0", label: "All" }]; this.instances = [{ value: "0", label: "All" }, ...response.data.pods];
return res.data; return response;
}, },
async getEndpoints(id: string, keyword?: string) { async getEndpoints(id: string, keyword?: string) {
const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id; const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id;
const res: AxiosResponse = await graphql.query("queryEndpoints").params({ const response = await graphql.query("queryEndpoints").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
keyword: keyword || "", keyword: keyword || "",
limit: EndpointsTopNDefault,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.endpoints = [{ value: "0", label: "All" }, ...res.data.data.pods] || [{ value: "0", label: "All" }]; this.endpoints = [{ value: "0", label: "All" }, ...response.data.pods];
return res.data; return response;
}, },
async getLogsByKeywords() { async getLogsByKeywords() {
const res: AxiosResponse = await graphql.query("queryLogsByKeywords").params({}); const response = await graphql.query("queryLogsByKeywords").params({});
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.supportQueryLogsByKeywords = res.data.data.support; this.supportQueryLogsByKeywords = response.data.support;
return res.data; return response;
}, },
async getLogs() { async getLogs() {
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
@ -115,39 +116,31 @@ export const logStore = defineStore({
}, },
async getServiceLogs() { async getServiceLogs() {
this.loadLogs = true; this.loadLogs = true;
const res: AxiosResponse = await graphql.query("queryServiceLogs").params({ condition: this.conditions }); const response = await graphql.query("queryServiceLogs").params({ condition: this.conditions });
this.loadLogs = false; this.loadLogs = false;
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.logs = res.data.data.queryLogs.logs; this.logs = response.data.queryLogs.logs;
return res.data; return response;
}, },
async getBrowserLogs() { async getBrowserLogs() {
this.loadLogs = true; this.loadLogs = true;
const res: AxiosResponse = await graphql.query("queryBrowserErrorLogs").params({ condition: this.conditions }); const response = await graphql.query("queryBrowserErrorLogs").params({ condition: this.conditions });
this.loadLogs = false; this.loadLogs = false;
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.logs = res.data.data.queryBrowserErrorLogs.logs; this.logs = response.data.queryBrowserErrorLogs.logs;
return res.data; return response;
}, },
async getLogTagKeys() { async getLogTagKeys() {
const res: AxiosResponse = await graphql return await graphql.query("queryLogTagKeys").params({ duration: useAppStoreWithOut().durationTime });
.query("queryLogTagKeys")
.params({ duration: useAppStoreWithOut().durationTime });
return res.data;
}, },
async getLogTagValues(tagKey: string) { async getLogTagValues(tagKey: string) {
const res: AxiosResponse = await graphql return await graphql.query("queryLogTagValues").params({ tagKey, duration: useAppStoreWithOut().durationTime });
.query("queryLogTagValues")
.params({ tagKey, duration: useAppStoreWithOut().durationTime });
return res.data;
}, },
}, },
}); });

View File

@ -18,7 +18,6 @@ import { defineStore } from "pinia";
import type { EBPFTaskList, ProcessNode } from "@/types/ebpf"; import type { EBPFTaskList, ProcessNode } from "@/types/ebpf";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import type { Call } from "@/types/topology"; import type { Call } from "@/types/topology";
import type { LayoutConfig } from "@/types/dashboard"; import type { LayoutConfig } from "@/types/dashboard";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
@ -126,65 +125,65 @@ export const networkProfilingStore = defineStore({
minDuration: number; minDuration: number;
}[], }[],
) { ) {
const res: AxiosResponse = await graphql.query("newNetworkProfiling").params({ const response = await graphql.query("newNetworkProfiling").params({
request: { request: {
instanceId, instanceId,
samplings: params, samplings: params,
}, },
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
return res.data; return response;
}, },
async getTaskList(params: { serviceId: string; serviceInstanceId: string; targets: string[] }) { async getTaskList(params: { serviceId: string; serviceInstanceId: string; targets: string[] }) {
if (!params.serviceId) { if (!params.serviceId) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql.query("getEBPFTasks").params(params); const response = await graphql.query("getEBPFTasks").params(params);
this.networkTip = ""; this.networkTip = "";
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.networkTasks = res.data.data.queryEBPFTasks || []; this.networkTasks = response.data.queryEBPFTasks || [];
this.selectedNetworkTask = this.networkTasks[0] || {}; this.selectedNetworkTask = this.networkTasks[0] || {};
this.setSelectedNetworkTask(this.selectedNetworkTask); this.setSelectedNetworkTask(this.selectedNetworkTask);
if (!this.networkTasks.length) { if (!this.networkTasks.length) {
this.nodes = []; this.nodes = [];
this.calls = []; this.calls = [];
} }
return res.data; return response;
}, },
async keepNetworkProfiling(taskId: string) { async keepNetworkProfiling(taskId: string) {
if (!taskId) { if (!taskId) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql.query("aliveNetworkProfiling").params({ taskId }); const response = await graphql.query("aliveNetworkProfiling").params({ taskId });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.aliveNetwork = res.data.data.keepEBPFNetworkProfiling.status; this.aliveNetwork = response.data.keepEBPFNetworkProfiling.status;
if (!this.aliveNetwork) { if (!this.aliveNetwork) {
ElMessage.warning(res.data.data.keepEBPFNetworkProfiling.errorReason); ElMessage.warning(response.data.keepEBPFNetworkProfiling.errorReason);
} }
return res.data; return response;
}, },
async getProcessTopology(params: { duration: DurationTime; serviceInstanceId: string }) { async getProcessTopology(params: { duration: DurationTime; serviceInstanceId: string }) {
this.loadNodes = true; this.loadNodes = true;
const res: AxiosResponse = await graphql.query("getProcessTopology").params(params); const response = await graphql.query("getProcessTopology").params(params);
this.loadNodes = false; this.loadNodes = false;
if (res.data.errors) { if (response.errors) {
this.nodes = []; this.nodes = [];
this.calls = []; this.calls = [];
return res.data; return response;
} }
const { topology } = res.data.data; const { topology } = response.data;
this.setTopology(topology); this.setTopology(topology);
return res.data; return response;
}, },
}, },
}); });

View File

@ -26,8 +26,8 @@ import type {
import type { Trace } from "@/types/trace"; import type { Trace } from "@/types/trace";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { EndpointsTopNDefault } from "../data";
interface ProfileState { interface ProfileState {
endpoints: Endpoint[]; endpoints: Endpoint[];
@ -80,7 +80,7 @@ export const profileStore = defineStore({
this.analyzeTrees = []; this.analyzeTrees = [];
}, },
setCurrentSegment(segment: Trace) { setCurrentSegment(segment: Trace) {
this.currentSegment = segment; this.currentSegment = segment || {};
this.segmentSpans = segment.spans || []; this.segmentSpans = segment.spans || [];
if (segment.spans) { if (segment.spans) {
this.currentSpan = segment.spans[0] || {}; this.currentSpan = segment.spans[0] || {};
@ -93,36 +93,38 @@ export const profileStore = defineStore({
this.highlightTop = !this.highlightTop; this.highlightTop = !this.highlightTop;
}, },
async getEndpoints(serviceId: string, keyword?: string) { async getEndpoints(serviceId: string, keyword?: string) {
const res: AxiosResponse = await graphql.query("queryEndpoints").params({ const response = await graphql.query("queryEndpoints").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
keyword: keyword || "", keyword: keyword || "",
limit: EndpointsTopNDefault,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.endpoints = res.data.data.pods || []; this.endpoints = response.data.pods || [];
return res.data; return response.data;
}, },
async getTaskEndpoints(serviceId: string, keyword?: string) { async getTaskEndpoints(serviceId: string, keyword?: string) {
const res: AxiosResponse = await graphql.query("queryEndpoints").params({ const response = await graphql.query("queryEndpoints").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
keyword: keyword || "", keyword: keyword || "",
limit: EndpointsTopNDefault,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.taskEndpoints = [{ value: "", label: "All" }, ...res.data.data.pods]; this.taskEndpoints = [{ value: "", label: "All" }, ...response.data.pods];
return res.data; return response;
}, },
async getTaskList() { async getTaskList() {
const res: AxiosResponse = await graphql.query("getProfileTaskList").params(this.condition); const response = await graphql.query("getProfileTaskList").params(this.condition);
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
const list = res.data.data.taskList || []; const list = response.data.taskList || [];
this.taskList = list; this.taskList = list;
this.currentTask = list[0] || {}; this.currentTask = list[0] || {};
if (!list.length) { if (!list.length) {
@ -130,52 +132,52 @@ export const profileStore = defineStore({
this.segmentSpans = []; this.segmentSpans = [];
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
this.getSegmentList({ taskID: list[0].id }); this.getSegmentList({ taskID: list[0].id });
return res.data; return response;
}, },
async getSegmentList(params: { taskID: string }) { async getSegmentList(params: { taskID: string }) {
if (!params.taskID) { if (!params.taskID) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql.query("getProfileTaskSegmentList").params(params); const response = await graphql.query("getProfileTaskSegmentList").params(params);
if (res.data.errors) { if (response.errors) {
this.segmentList = []; this.segmentList = [];
return res.data; return response;
} }
const { segmentList } = res.data.data; const { segmentList } = response.data;
this.segmentList = segmentList || []; this.segmentList = segmentList || [];
if (!segmentList.length) { if (!segmentList.length) {
this.segmentSpans = []; this.segmentSpans = [];
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
if (segmentList[0]) { if (segmentList[0]) {
this.currentSegment = segmentList[0]; this.setCurrentSegment(segmentList[0]);
this.getSegmentSpans(segmentList[0].segmentId); this.getSegmentSpans(segmentList[0].segmentId);
} else { } else {
this.currentSegment = {}; this.setCurrentSegment({});
} }
return res.data; return response;
}, },
async getSegmentSpans(params: { segmentId: string }) { async getSegmentSpans(params: { segmentId: string }) {
if (!(params && params.segmentId)) { if (!(params && params.segmentId)) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql.query("queryProfileSegment").params(params); const response = await graphql.query("queryProfileSegment").params(params);
if (res.data.errors) { if (response.errors) {
this.segmentSpans = []; this.segmentSpans = [];
return res.data; return response;
} }
const { segment } = res.data.data; const { segment } = response.data;
if (!segment) { if (!segment) {
this.segmentSpans = []; this.segmentSpans = [];
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
this.segmentSpans = segment.spans.map((d: SegmentSpan) => { this.segmentSpans = segment.spans.map((d: SegmentSpan) => {
return { return {
@ -186,52 +188,52 @@ export const profileStore = defineStore({
}); });
if (!(segment.spans && segment.spans.length)) { if (!(segment.spans && segment.spans.length)) {
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
const index = segment.spans.length - 1 || 0; const index = segment.spans.length - 1 || 0;
this.currentSpan = segment.spans[index]; this.currentSpan = segment.spans[index];
return res.data; return response;
}, },
async getProfileAnalyze(params: Array<{ segmentId: string; timeRange: { start: number; end: number } }>) { async getProfileAnalyze(params: Array<{ segmentId: string; timeRange: { start: number; end: number } }>) {
if (!params.length) { if (!params.length) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql.query("getProfileAnalyze").params({ queries: params }); const response = await graphql.query("getProfileAnalyze").params({ queries: params });
if (res.data.errors) { if (response.errors) {
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
const { analyze, tip } = res.data.data; const { analyze, tip } = response.data;
if (tip) { if (tip) {
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
if (!analyze) { if (!analyze) {
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
this.analyzeTrees = analyze.trees; this.analyzeTrees = analyze.trees;
return res.data; return response;
}, },
async createTask(param: ProfileTaskCreationRequest) { async createTask(param: ProfileTaskCreationRequest) {
const res: AxiosResponse = await graphql.query("saveProfileTask").params({ creationRequest: param }); const response = await graphql.query("saveProfileTask").params({ creationRequest: param });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.getTaskList(); this.getTaskList();
return res.data; return response;
}, },
async getTaskLogs(param: { taskID: string }) { async getTaskLogs(param: { taskID: string }) {
const res: AxiosResponse = await graphql.query("getProfileTaskLogs").params(param); const response = await graphql.query("getProfileTaskLogs").params(param);
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.taskLogs = res.data.data.taskLogs; this.taskLogs = response.data.taskLogs;
return res.data; return response;
}, },
}, },
}); });

View File

@ -18,8 +18,8 @@ import { defineStore } from "pinia";
import type { Service, Instance, Endpoint, Process } from "@/types/selector"; import type { Service, Instance, Endpoint, Process } from "@/types/selector";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { EndpointsTopNDefault } from "../data";
interface SelectorState { interface SelectorState {
services: Service[]; services: Service[];
destServices: Service[]; destServices: Service[];
@ -76,62 +76,55 @@ export const selectorStore = defineStore({
setDestProcesses(processes: Array<Process>) { setDestProcesses(processes: Array<Process>) {
this.destProcesses = processes; this.destProcesses = processes;
}, },
async fetchLayers(): Promise<AxiosResponse> { async fetchLayers() {
const res: AxiosResponse = await graphql.query("queryLayers").params({}); return await graphql.query("queryLayers").params({});
return res.data || {};
}, },
async fetchServices(layer: string): Promise<AxiosResponse> { async fetchServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({ layer }); const res = await graphql.query("queryServices").params({ layer });
if (!res.data.errors) { if (!res.errors) {
this.services = res.data.data.services || []; this.services = res.data.services || [];
this.destServices = res.data.data.services || []; this.destServices = res.data.services || [];
} }
return res.data; return res.data;
}, },
async getServiceInstances(param?: { serviceId: string; isRelation: boolean }): Promise<Nullable<AxiosResponse>> { async getServiceInstances(param?: { serviceId: string; isRelation: boolean }) {
const serviceId = param ? param.serviceId : this.currentService?.id; const serviceId = param ? param.serviceId : this.currentService?.id;
if (!serviceId) { if (!serviceId) {
return null; return null;
} }
const res: AxiosResponse = await graphql.query("queryInstances").params({ const resp = await graphql.query("queryInstances").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
}); });
if (!res.data.errors) { if (!resp.errors) {
if (param && param.isRelation) { if (param && param.isRelation) {
this.destPods = res.data.data.pods || []; this.destPods = resp.data.pods || [];
return res.data; return resp;
} }
this.pods = res.data.data.pods || []; this.pods = resp.data.pods || [];
} }
return res.data; return resp;
}, },
async getProcesses(param?: { instanceId: string; isRelation: boolean }): Promise<Nullable<AxiosResponse>> { async getProcesses(param?: { instanceId: string; isRelation: boolean }) {
const instanceId = param ? param.instanceId : this.currentPod?.id; const instanceId = param ? param.instanceId : this.currentPod?.id;
if (!instanceId) { if (!instanceId) {
return null; return null;
} }
const res: AxiosResponse = await graphql.query("queryProcesses").params({ const res = await graphql.query("queryProcesses").params({
instanceId, instanceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
}); });
if (!res.data.errors) { if (!res.errors) {
if (param && param.isRelation) { if (param && param.isRelation) {
this.destProcesses = res.data.data.processes || []; this.destProcesses = res.data.processes || [];
return res.data; return res;
} }
this.processes = res.data.data.processes || []; this.processes = res.data.processes || [];
} }
return res.data; return res;
}, },
async getEndpoints(params: { async getEndpoints(params: { keyword?: string; serviceId?: string; isRelation?: boolean; limit?: number }) {
keyword?: string;
serviceId?: string;
isRelation?: boolean;
limit?: number;
}): Promise<Nullable<AxiosResponse>> {
if (!params) { if (!params) {
params = {}; params = {};
} }
@ -139,96 +132,96 @@ export const selectorStore = defineStore({
if (!serviceId) { if (!serviceId) {
return null; return null;
} }
const res: AxiosResponse = await graphql.query("queryEndpoints").params({ const res = await graphql.query("queryEndpoints").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
keyword: params.keyword || "", keyword: params.keyword || "",
limit: params.limit, limit: params.limit || EndpointsTopNDefault,
}); });
if (!res.data.errors) { if (!res.errors) {
if (params.isRelation) { if (params.isRelation) {
this.destPods = res.data.data.pods || []; this.destPods = res.data.pods || [];
return res.data; return res;
} }
this.pods = res.data.data.pods || []; this.pods = res.data.pods || [];
} }
return res.data; return res;
}, },
async getService(serviceId: string, isRelation: boolean) { async getService(serviceId: string, isRelation: boolean) {
if (!serviceId) { if (!serviceId) {
return; return;
} }
const res: AxiosResponse = await graphql.query("queryService").params({ const res = await graphql.query("queryService").params({
serviceId, serviceId,
}); });
if (!res.data.errors) { if (!res.errors) {
if (isRelation) { if (isRelation) {
this.setCurrentDestService(res.data.data.service); this.setCurrentDestService(res.data.service);
this.destServices = [res.data.data.service]; this.destServices = [res.data.service];
return res.data; return res;
} }
this.setCurrentService(res.data.data.service); this.setCurrentService(res.data.service);
this.services = [res.data.data.service]; this.services = [res.data.service];
} }
return res.data; return res;
}, },
async getInstance(instanceId: string, isRelation?: boolean) { async getInstance(instanceId: string, isRelation?: boolean) {
if (!instanceId) { if (!instanceId) {
return; return;
} }
const res: AxiosResponse = await graphql.query("queryInstance").params({ const res = await graphql.query("queryInstance").params({
instanceId, instanceId,
}); });
if (!res.data.errors) { if (!res.errors) {
if (isRelation) { if (isRelation) {
this.currentDestPod = res.data.data.instance || null; this.currentDestPod = res.data.instance || null;
this.destPods = [res.data.data.instance]; this.destPods = [res.data.instance];
return res.data; return res;
} }
this.currentPod = res.data.data.instance || null; this.currentPod = res.data.instance || null;
this.pods = [res.data.data.instance]; this.pods = [res.data.instance];
} }
return res.data; return res;
}, },
async getEndpoint(endpointId: string, isRelation?: string) { async getEndpoint(endpointId: string, isRelation?: string) {
if (!endpointId) { if (!endpointId) {
return; return;
} }
const res: AxiosResponse = await graphql.query("queryEndpoint").params({ const res = await graphql.query("queryEndpoint").params({
endpointId, endpointId,
}); });
if (res.data.errors) { if (res.errors) {
return res.data; return res;
} }
if (isRelation) { if (isRelation) {
this.currentDestPod = res.data.data.endpoint || null; this.currentDestPod = res.data.endpoint || null;
this.destPods = [res.data.data.endpoint]; this.destPods = [res.data.endpoint];
return res.data; return res;
} }
this.currentPod = res.data.data.endpoint || null; this.currentPod = res.data.endpoint || null;
this.pods = [res.data.data.endpoint]; this.pods = [res.data.endpoint];
return res.data; return res;
}, },
async getProcess(processId: string, isRelation?: boolean) { async getProcess(processId: string, isRelation?: boolean) {
if (!processId) { if (!processId) {
return; return;
} }
const res: AxiosResponse = await graphql.query("queryProcess").params({ const res = await graphql.query("queryProcess").params({
processId, processId,
}); });
if (!res.data.errors) { if (!res.errors) {
if (isRelation) { if (isRelation) {
this.currentDestProcess = res.data.data.process || null; this.currentDestProcess = res.data.process || null;
this.destProcesses = [res.data.data.process]; this.destProcesses = [res.data.process];
return res.data; return res.data;
} }
this.currentProcess = res.data.data.process || null; this.currentProcess = res.data.process || null;
this.processes = [res.data.data.process]; this.processes = [res.data.process];
} }
return res.data; return res;
}, },
}, },
}); });

View File

@ -18,7 +18,6 @@ import { defineStore } from "pinia";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import type { EBPFTaskList } from "@/types/ebpf"; import type { EBPFTaskList } from "@/types/ebpf";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling"; import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
@ -57,20 +56,18 @@ export const taskTimelineStore = defineStore({
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
this.loading = true; this.loading = true;
const res: AxiosResponse = await graphql.query("getEBPFTasks").params(params); const response = await graphql.query("getEBPFTasks").params(params);
this.loading = false; this.loading = false;
this.errorTip = ""; this.errorTip = "";
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
this.taskList = (res.data.data.queryEBPFTasks || []).filter( this.taskList = (response.data.queryEBPFTasks || []).filter(
(d: EBPFTaskList) => selectorStore.currentProcess && d.processId === selectorStore.currentProcess.id, (d: EBPFTaskList) => selectorStore.currentProcess && d.processId === selectorStore.currentProcess.id,
); );
// this.selectedTask = this.taskList[0] || {}; return response;
// await this.getGraphData();
return res.data;
}, },
async getGraphData() { async getGraphData() {
let res: any = {}; let res: any = {};

View File

@ -21,10 +21,8 @@ import graphql from "@/graphql";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import type { AxiosResponse } from "axios"; import fetchQuery from "@/graphql/fetch";
import query from "@/graphql/fetch";
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor"; import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
import { ElMessage } from "element-plus";
interface MetricVal { interface MetricVal {
[key: string]: { values: { id: string; value: unknown }[] }; [key: string]: { values: { id: string; value: unknown }[] };
@ -306,14 +304,14 @@ export const topologyStore = defineStore({
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const duration = useAppStoreWithOut().durationTime; const duration = useAppStoreWithOut().durationTime;
const res: AxiosResponse = await graphql.query("getServicesTopology").params({ const res = await graphql.query("getServicesTopology").params({
serviceIds, serviceIds,
duration, duration,
}); });
if (res.data.errors) { if (res.errors) {
return res.data; return res;
} }
return res.data.data.topology; return res.data.topology;
}, },
async getInstanceTopology() { async getInstanceTopology() {
const { currentService, currentDestService } = useSelectorStore(); const { currentService, currentDestService } = useSelectorStore();
@ -323,15 +321,15 @@ export const topologyStore = defineStore({
if (!(serverServiceId && clientServiceId)) { if (!(serverServiceId && clientServiceId)) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql.query("getInstanceTopology").params({ const res = await graphql.query("getInstanceTopology").params({
clientServiceId, clientServiceId,
serverServiceId, serverServiceId,
duration, duration,
}); });
if (!res.data.errors) { if (!res.errors) {
this.setInstanceTopology(res.data.data.topology); this.setInstanceTopology(res.data.topology);
} }
return res.data; return res;
}, },
async updateEndpointTopology(endpointIds: string[], depth: number) { async updateEndpointTopology(endpointIds: string[], depth: number) {
if (!endpointIds.length) { if (!endpointIds.length) {
@ -339,7 +337,10 @@ export const topologyStore = defineStore({
} }
const res = await this.getEndpointTopology(endpointIds); const res = await this.getEndpointTopology(endpointIds);
if (depth > 1) { if (depth > 1) {
const ids = res.nodes.map((item: Node) => item.id).filter((d: string) => !endpointIds.includes(d)); const userNodeName = "User";
const ids = res.nodes
.filter((d: Node) => !endpointIds.includes(d.id) && d.name !== userNodeName)
.map((item: Node) => item.id);
if (!ids.length) { if (!ids.length) {
this.setTopology(res); this.setTopology(res);
return; return;
@ -347,8 +348,8 @@ export const topologyStore = defineStore({
const json = await this.getEndpointTopology(ids); const json = await this.getEndpointTopology(ids);
if (depth > 2) { if (depth > 2) {
const pods = json.nodes const pods = json.nodes
.map((item: Node) => item.id) .filter((d: Node) => ![...ids, ...endpointIds].includes(d.id) && d.name !== userNodeName)
.filter((d: string) => ![...ids, ...endpointIds].includes(d)); .map((item: Node) => item.id);
if (!pods.length) { if (!pods.length) {
const nodes = [...res.nodes, ...json.nodes]; const nodes = [...res.nodes, ...json.nodes];
const calls = [...res.calls, ...json.calls]; const calls = [...res.calls, ...json.calls];
@ -358,8 +359,8 @@ export const topologyStore = defineStore({
const topo = await this.getEndpointTopology(pods); const topo = await this.getEndpointTopology(pods);
if (depth > 3) { if (depth > 3) {
const endpoints = topo.nodes const endpoints = topo.nodes
.map((item: Node) => item.id) .filter((d: Node) => ![...ids, ...pods, ...endpointIds].includes(d.id) && d.name !== userNodeName)
.filter((d: string) => ![...ids, ...pods, ...endpointIds].includes(d)); .map((item: Node) => item.id);
if (!endpoints.length) { if (!endpoints.length) {
const nodes = [...res.nodes, ...json.nodes, ...topo.nodes]; const nodes = [...res.nodes, ...json.nodes, ...topo.nodes];
const calls = [...res.calls, ...json.calls, ...topo.calls]; const calls = [...res.calls, ...json.calls, ...topo.calls];
@ -369,8 +370,11 @@ export const topologyStore = defineStore({
const data = await this.getEndpointTopology(endpoints); const data = await this.getEndpointTopology(endpoints);
if (depth > 4) { if (depth > 4) {
const nodeIds = data.nodes const nodeIds = data.nodes
.map((item: Node) => item.id) .filter(
.filter((d: string) => ![...endpoints, ...ids, ...pods, ...endpointIds].includes(d)); (d: Node) =>
![...endpoints, ...ids, ...pods, ...endpointIds].includes(d.id) && d.name !== userNodeName,
)
.map((item: Node) => item.id);
if (!nodeIds.length) { if (!nodeIds.length) {
const nodes = [...res.nodes, ...json.nodes, ...topo.nodes, ...data.nodes]; const nodes = [...res.nodes, ...json.nodes, ...topo.nodes, ...data.nodes];
const calls = [...res.calls, ...json.calls, ...topo.calls, ...data.calls]; const calls = [...res.calls, ...json.calls, ...topo.calls, ...data.calls];
@ -427,12 +431,12 @@ export const topologyStore = defineStore({
}); });
const queryStr = `query queryData(${variables}) {${fragment}}`; const queryStr = `query queryData(${variables}) {${fragment}}`;
const conditions = { duration }; const conditions = { duration };
const res: AxiosResponse = await query({ queryStr, conditions }); const res = await fetchQuery({ queryStr, conditions });
if (res.data.errors) { if (res.errors) {
return res.data; return res;
} }
const topo = res.data.data; const topo = res.data;
const calls = [] as Call[]; const calls = [] as Call[];
const nodes = [] as Node[]; const nodes = [] as Node[];
for (const key of Object.keys(topo)) { for (const key of Object.keys(topo)) {
@ -443,14 +447,14 @@ export const topologyStore = defineStore({
return { calls, nodes }; return { calls, nodes };
}, },
async getNodeExpressionValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) { async getTopologyExpressionValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const res: AxiosResponse = await query(param); const res = await fetchQuery(param);
if (res.data.errors) { if (res.errors) {
return res.data; return res;
} }
return res.data; return res;
}, },
async getLinkExpressions(expressions: string[], type: string) { async getLinkExpressions(expressions: string[], type: string) {
if (!expressions.length) { if (!expressions.length) {
@ -461,14 +465,8 @@ export const topologyStore = defineStore({
if (!calls.length) { if (!calls.length) {
return; return;
} }
const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor(expressions, calls); const { getMetrics } = useQueryTopologyExpressionsProcessor(expressions, calls);
const param = getExpressionQuery(); const metrics = await getMetrics();
const res = await this.getNodeExpressionValue(param);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
const metrics = handleExpressionValues(res.data);
if (type === "SERVER") { if (type === "SERVER") {
this.setLinkServerMetrics(metrics); this.setLinkServerMetrics(metrics);
} else { } else {
@ -484,17 +482,11 @@ export const topologyStore = defineStore({
this.setNodeMetricValue({}); this.setNodeMetricValue({});
return; return;
} }
const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor( const { getMetrics } = useQueryTopologyExpressionsProcessor(
expressions, expressions,
this.nodes.filter((d: Node) => d.isReal), this.nodes.filter((d: Node) => d.isReal),
); );
const param = getExpressionQuery(); const metrics = await getMetrics();
const res = await this.getNodeExpressionValue(param);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
const metrics = handleExpressionValues(res.data);
this.setNodeMetricValue(metrics); this.setNodeMetricValue(metrics);
}, },
async getHierarchyServiceTopology() { async getHierarchyServiceTopology() {
@ -510,22 +502,20 @@ export const topologyStore = defineStore({
if (!(id && layer)) { if (!(id && layer)) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql const res = await graphql.query("getHierarchyServiceTopology").params({ serviceId: id, layer: layer });
.query("getHierarchyServiceTopology") if (res.errors) {
.params({ serviceId: id, layer: layer }); return res;
if (res.data.errors) {
return res.data;
} }
const resp = await this.getListLayerLevels(); const resp = await this.getListLayerLevels();
if (resp.errors) { if (resp.errors) {
return resp; return resp;
} }
const levels = resp.data.levels || []; const levels = resp.levels || [];
this.setHierarchyServiceTopology(res.data.data.hierarchyServiceTopology || {}, levels); this.setHierarchyServiceTopology(res.data.hierarchyServiceTopology || {}, levels);
return res.data; return res;
}, },
async getListLayerLevels() { async getListLayerLevels() {
const res: AxiosResponse = await graphql.query("queryListLayerLevels").params({}); const res = await graphql.query("queryListLayerLevels").params({});
return res.data; return res.data;
}, },
@ -536,30 +526,19 @@ export const topologyStore = defineStore({
if (!(currentPod && dashboardStore.layerId)) { if (!(currentPod && dashboardStore.layerId)) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql const res = await graphql
.query("getHierarchyInstanceTopology") .query("getHierarchyInstanceTopology")
.params({ instanceId: currentPod.id, layer: dashboardStore.layerId }); .params({ instanceId: currentPod.id, layer: dashboardStore.layerId });
if (res.data.errors) { if (res.errors) {
return res.data; return res;
} }
const resp = await this.getListLayerLevels(); const resp = await this.getListLayerLevels();
if (resp.errors) { if (resp.errors) {
return resp; return resp;
} }
const levels = resp.data.levels || []; const levels = resp.levels || [];
this.setHierarchyInstanceTopology(res.data.data.hierarchyInstanceTopology || {}, levels); this.setHierarchyInstanceTopology(res.data.hierarchyInstanceTopology || {}, levels);
return res.data; return res;
},
async queryHierarchyExpressions(expressions: string[], nodes: Node[]) {
const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor(expressions, nodes);
const param = getExpressionQuery();
const res = await this.getNodeExpressionValue(param);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
const metrics = handleExpressionValues(res.data);
return metrics;
}, },
async queryHierarchyNodeExpressions(expressions: string[], layer: string) { async queryHierarchyNodeExpressions(expressions: string[], layer: string) {
const nodes = this.hierarchyServiceNodes.filter((n: HierarchyNode) => n.layer === layer); const nodes = this.hierarchyServiceNodes.filter((n: HierarchyNode) => n.layer === layer);
@ -571,7 +550,8 @@ export const topologyStore = defineStore({
this.setHierarchyNodeMetricValue({}, layer); this.setHierarchyNodeMetricValue({}, layer);
return; return;
} }
const metrics = await this.queryHierarchyExpressions(expressions, nodes); const { getMetrics } = useQueryTopologyExpressionsProcessor(expressions, nodes);
const metrics = await getMetrics();
this.setHierarchyNodeMetricValue(metrics, layer); this.setHierarchyNodeMetricValue(metrics, layer);
}, },
async queryHierarchyInstanceNodeExpressions(expressions: string[], layer: string) { async queryHierarchyInstanceNodeExpressions(expressions: string[], layer: string) {
@ -585,7 +565,8 @@ export const topologyStore = defineStore({
this.setHierarchyInstanceNodeMetricValue({}, layer); this.setHierarchyInstanceNodeMetricValue({}, layer);
return; return;
} }
const metrics = await this.queryHierarchyExpressions(expressions, nodes); const { getMetrics } = useQueryTopologyExpressionsProcessor(expressions, nodes);
const metrics = await getMetrics();
this.setHierarchyInstanceNodeMetricValue(metrics, layer); this.setHierarchyInstanceNodeMetricValue(metrics, layer);
}, },
}, },

View File

@ -19,10 +19,10 @@ import type { Instance, Endpoint, Service } from "@/types/selector";
import type { Trace, Span } from "@/types/trace"; import type { Trace, Span } from "@/types/trace";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { QueryOrders } from "@/views/dashboard/data"; import { QueryOrders } from "@/views/dashboard/data";
import { EndpointsTopNDefault } from "../data";
interface TraceState { interface TraceState {
services: Service[]; services: Service[];
instances: Instance[]; instances: Instance[];
@ -33,6 +33,7 @@ interface TraceState {
conditions: Recordable; conditions: Recordable;
traceSpanLogs: Recordable[]; traceSpanLogs: Recordable[];
selectorStore: Recordable; selectorStore: Recordable;
selectedSpan: Recordable<Span>;
} }
export const traceStore = defineStore({ export const traceStore = defineStore({
@ -44,6 +45,7 @@ export const traceStore = defineStore({
traceList: [], traceList: [],
traceSpans: [], traceSpans: [],
currentTrace: {}, currentTrace: {},
selectedSpan: {},
conditions: { conditions: {
queryDuration: useAppStoreWithOut().durationTime, queryDuration: useAppStoreWithOut().durationTime,
traceState: "ALL", traceState: "ALL",
@ -63,6 +65,9 @@ export const traceStore = defineStore({
setTraceSpans(spans: Span[]) { setTraceSpans(spans: Span[]) {
this.traceSpans = spans; this.traceSpans = spans;
}, },
setSelectedSpan(span: Span) {
this.selectedSpan = span;
},
resetState() { resetState() {
this.traceSpans = []; this.traceSpans = [];
this.traceList = []; this.traceList = [];
@ -75,124 +80,115 @@ export const traceStore = defineStore({
}; };
}, },
async getServices(layer: string) { async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({ const response = await graphql.query("queryServices").params({
layer, layer,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.services = res.data.data.services; this.services = response.data.services;
return res.data; return response;
}, },
async getService(serviceId: string) { async getService(serviceId: string) {
if (!serviceId) { if (!serviceId) {
return; return;
} }
const res: AxiosResponse = await graphql.query("queryService").params({ const response = await graphql.query("queryService").params({
serviceId, serviceId,
}); });
return res.data; return response;
}, },
async getInstance(instanceId: string) { async getInstance(instanceId: string) {
if (!instanceId) { if (!instanceId) {
return; return;
} }
const res: AxiosResponse = await graphql.query("queryInstance").params({ const response = await graphql.query("queryInstance").params({
instanceId, instanceId,
}); });
return res.data; return response;
}, },
async getEndpoint(endpointId: string) { async getEndpoint(endpointId: string) {
if (!endpointId) { if (!endpointId) {
return; return;
} }
const res: AxiosResponse = await graphql.query("queryEndpoint").params({ return await graphql.query("queryEndpoint").params({
endpointId, endpointId,
}); });
return res.data;
}, },
async getInstances(id: string) { async getInstances(id: string) {
const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id; const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id;
const res: AxiosResponse = await graphql.query("queryInstances").params({ const response = await graphql.query("queryInstances").params({
serviceId: serviceId, serviceId: serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.instances = [{ value: "0", label: "All" }, ...res.data.data.pods]; this.instances = [{ value: "0", label: "All" }, ...response.data.pods];
return res.data; return response;
}, },
async getEndpoints(id: string, keyword?: string) { async getEndpoints(id: string, keyword?: string) {
const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id; const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id;
const res: AxiosResponse = await graphql.query("queryEndpoints").params({ const response = await graphql.query("queryEndpoints").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
keyword: keyword || "", keyword: keyword || "",
limit: EndpointsTopNDefault,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.endpoints = [{ value: "0", label: "All" }, ...res.data.data.pods]; this.endpoints = [{ value: "0", label: "All" }, ...response.data.pods];
return res.data; return response;
}, },
async getTraces() { async getTraces() {
const res: AxiosResponse = await graphql.query("queryTraces").params({ condition: this.conditions }); const response = await graphql.query("queryTraces").params({ condition: this.conditions });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
if (!res.data.data.data.traces.length) { if (!response.data.data.traces.length) {
this.traceList = []; this.traceList = [];
this.setCurrentTrace({}); this.setCurrentTrace({});
this.setTraceSpans([]); this.setTraceSpans([]);
return res.data; return response;
} }
this.getTraceSpans({ traceId: res.data.data.data.traces[0].traceIds[0] }); this.getTraceSpans({ traceId: response.data.data.traces[0].traceIds[0] });
this.traceList = res.data.data.data.traces.map((d: Trace) => { this.traceList = response.data.data.traces.map((d: Trace) => {
d.traceIds = d.traceIds.map((id: string) => { d.traceIds = d.traceIds.map((id: string) => {
return { value: id, label: id }; return { value: id, label: id };
}); });
return d; return d;
}); });
this.setCurrentTrace(res.data.data.data.traces[0] || {}); this.setCurrentTrace(response.data.data.traces[0] || {});
return res.data; return response;
}, },
async getTraceSpans(params: { traceId: string }) { async getTraceSpans(params: { traceId: string }) {
const res: AxiosResponse = await graphql.query("queryTrace").params(params); const response = await graphql.query("queryTrace").params(params);
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
const data = res.data.data.trace.spans; const data = response.data.trace.spans;
this.setTraceSpans(data || []); this.setTraceSpans(data || []);
return res.data; return response;
}, },
async getSpanLogs(params: Recordable) { async getSpanLogs(params: Recordable) {
const res: AxiosResponse = await graphql.query("queryServiceLogs").params(params); const response = await graphql.query("queryServiceLogs").params(params);
if (res.data.errors) { if (response.errors) {
this.traceSpanLogs = []; this.traceSpanLogs = [];
return res.data; return response;
} }
this.traceSpanLogs = res.data.data.queryLogs.logs || []; this.traceSpanLogs = response.data.queryLogs.logs || [];
return res.data; return response;
}, },
async getTagKeys() { async getTagKeys() {
const res: AxiosResponse = await graphql return await graphql.query("queryTraceTagKeys").params({ duration: useAppStoreWithOut().durationTime });
.query("queryTraceTagKeys")
.params({ duration: useAppStoreWithOut().durationTime });
return res.data;
}, },
async getTagValues(tagKey: string) { async getTagValues(tagKey: string) {
const res: AxiosResponse = await graphql return await graphql.query("queryTraceTagValues").params({ tagKey, duration: useAppStoreWithOut().durationTime });
.query("queryTraceTagValues")
.params({ tagKey, duration: useAppStoreWithOut().durationTime });
return res.data;
}, },
}, },
}); });

View File

@ -139,6 +139,10 @@
margin-bottom: 10px; margin-bottom: 10px;
} }
.mb-20 {
margin-bottom: 20px;
}
.mr-5 { .mr-5 {
margin-right: 5px; margin-right: 5px;
} }

View File

@ -32,6 +32,7 @@
html { html {
--el-color-primary: #409eff; --el-color-primary: #409eff;
--el-color-info-light-9: #666;
--theme-background: #fff; --theme-background: #fff;
--font-color: #3d444f; --font-color: #3d444f;
--disabled-color: #ccc; --disabled-color: #ccc;
@ -69,10 +70,12 @@ html {
--sw-drawer-header: #72767b; --sw-drawer-header: #72767b;
--sw-marketplace-border: #dedfe0; --sw-marketplace-border: #dedfe0;
--sw-grid-item-active: #d4d7de; --sw-grid-item-active: #d4d7de;
--sw-trace-line: #999;
} }
html.dark { html.dark {
--el-color-primary: #409eff; --el-color-primary: #409eff;
--el-color-info-light-9: #333;
--theme-background: #212224; --theme-background: #212224;
--font-color: #fafbfc; --font-color: #fafbfc;
--disabled-color: #999; --disabled-color: #999;
@ -110,14 +113,16 @@ html.dark {
--sw-drawer-header: #e9e9eb; --sw-drawer-header: #e9e9eb;
--sw-marketplace-border: #606266; --sw-marketplace-border: #606266;
--sw-grid-item-active: #73767a; --sw-grid-item-active: #73767a;
--sw-trace-line: #e8e8e8;
} }
.el-drawer__header { .el-drawer__header {
color: var(--sw-drawer-header); color: var(--sw-drawer-header);
} }
.el-table tr { .el-table {
background-color: var(--el-table-tr-bg-color); --el-table-tr-bg-color: var(--theme-background);
--el-table-header-bg-color: var(--theme-background);
} }
.el-popper.is-light { .el-popper.is-light {
@ -129,27 +134,6 @@ html.dark {
--el-switch-off-color: #aaa; --el-switch-off-color: #aaa;
} }
.el-table__body-wrapper tr td.el-table-fixed-column--left,
.el-table__body-wrapper tr td.el-table-fixed-column--right,
.el-table__body-wrapper tr th.el-table-fixed-column--left,
.el-table__body-wrapper tr th.el-table-fixed-column--right,
.el-table__footer-wrapper tr td.el-table-fixed-column--left,
.el-table__footer-wrapper tr td.el-table-fixed-column--right,
.el-table__footer-wrapper tr th.el-table-fixed-column--left,
.el-table__footer-wrapper tr th.el-table-fixed-column--right,
.el-table__header-wrapper tr td.el-table-fixed-column--left,
.el-table__header-wrapper tr td.el-table-fixed-column--right,
.el-table__header-wrapper tr th.el-table-fixed-column--left,
.el-table__header-wrapper tr th.el-table-fixed-column--right {
background-color: var(--sw-table-col);
}
.el-table.is-scrolling-none th.el-table-fixed-column--left,
.el-table.is-scrolling-none th.el-table-fixed-column--right,
.el-table th.el-table__cell {
background-color: var(--sw-table-col);
}
$tool-icon-btn-bg: var(--sw-icon-btn-bg); $tool-icon-btn-bg: var(--sw-icon-btn-bg);
$tool-icon-btn-color: var(--sw-icon-btn-color); $tool-icon-btn-color: var(--sw-icon-btn-color);
$popper-hover-bg-color: var(--popper-hover-bg); $popper-hover-bg-color: var(--popper-hover-bg);
@ -261,6 +245,10 @@ div:has(> a.menu-title) {
text-align: left !important; text-align: left !important;
} }
.el-input--small .el-input__inner {
--el-input-inner-height: calc(var(--el-input-height, 24px));
}
html { html {
&::view-transition-old(root), &::view-transition-old(root),
&::view-transition-new(root) { &::view-transition-new(root) {

View File

@ -27,6 +27,7 @@ export interface Alarm {
scope: string; scope: string;
tags: Array<{ key: string; value: string }>; tags: Array<{ key: string; value: string }>;
events: Event[]; events: Event[];
snapshot: Indexable;
} }
export interface Event { export interface Event {

View File

@ -0,0 +1,68 @@
/**
* 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 type AsyncProfilingTask = {
id: string;
serviceId: string;
serviceInstanceIds: string[];
createTime: number;
events: string;
duration: number;
execArgs: string;
};
export type AsyncProfileTaskCreationRequest = {
serviceId: string;
serviceInstanceIds: string[];
duration: number;
events: string[];
execArgs: string;
};
export type AsyncProfilerStackElement = {
id: string;
parentId: string;
codeSignature: string;
total: number;
self: number;
};
export type AsyncProfilerTaskProgress = {
errorInstanceIds: string[];
successInstanceIds: string[];
logs: AsyncProfilerTaskLog[];
};
type AsyncProfilerTaskLog = {
id: string;
instanceId: string;
instanceName: string;
operationType: string;
operationTime: number;
};
export type StackElement = {
id: string;
originId: string;
name: string;
parentId: string;
codeSignature: string;
total: number;
self: number;
value: number;
children?: StackElement[];
};

View File

@ -12,6 +12,8 @@ declare module 'vue' {
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem'] ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard'] ElCard: typeof import('element-plus/es')['ElCard']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
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']
@ -42,8 +44,9 @@ declare module 'vue' {
ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTag: typeof import('element-plus/es')['ElTag'] ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
Graph: typeof import('./../components/Graph.vue')['default'] Graph: typeof import('./../components/Graph/Graph.vue')['default']
Icon: typeof import('./../components/Icon.vue')['default'] Icon: typeof import('./../components/Icon.vue')['default']
Legend: typeof import('./../components/Graph/Legend.vue')['default']
Radio: typeof import('./../components/Radio.vue')['default'] Radio: typeof import('./../components/Radio.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']

View File

@ -46,6 +46,7 @@ export interface LayoutConfig {
relatedTrace?: RelatedTrace; relatedTrace?: RelatedTrace;
subExpressions?: string[]; subExpressions?: string[];
subTypesOfMQE?: string[]; subTypesOfMQE?: string[];
valueRelatedDashboard?: string;
} }
export type RelatedTrace = { export type RelatedTrace = {
duration: DurationTime; duration: DurationTime;
@ -110,6 +111,7 @@ export interface LineConfig extends AreaConfig {
showYAxis?: boolean; showYAxis?: boolean;
smallTips?: boolean; smallTips?: boolean;
showlabels?: boolean; showlabels?: boolean;
noTooltips?: boolean;
} }
export interface AreaConfig { export interface AreaConfig {
@ -123,6 +125,7 @@ export interface CardConfig {
fontSize?: number; fontSize?: number;
showUnit?: boolean; showUnit?: boolean;
textAlign?: "center" | "right" | "left"; textAlign?: "center" | "right" | "left";
valueMappings?: { [key: string]: string };
} }
export interface TextConfig { export interface TextConfig {
@ -194,4 +197,19 @@ export type LegendOptions = {
asTable: boolean; asTable: boolean;
toTheRight: boolean; toTheRight: boolean;
width: number; width: number;
asSelector: boolean;
};
export type MetricsResults = {
metric: { labels: MetricLabel[] };
values: MetricValue[];
};
type MetricLabel = {
key: string;
value: string;
};
type MetricValue = {
name: string;
value: string;
owner: null | string;
refId: null | string;
}; };

View File

@ -41,6 +41,9 @@ export interface TaskListItem {
dumpPeriod: number; dumpPeriod: number;
maxSamplingCount: number; maxSamplingCount: number;
logs: TaskLog[]; logs: TaskLog[];
errorInstanceIds: string[];
successInstanceIds: string[];
serviceInstanceIds: string[];
} }
export interface SegmentSpan { export interface SegmentSpan {
spanId: string; spanId: string;

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

@ -48,9 +48,10 @@ export interface Span {
logs?: log[]; logs?: log[];
parentSegmentId?: string; parentSegmentId?: string;
refs?: Ref[]; refs?: Ref[];
key?: string;
} }
export type Ref = { export type Ref = {
type: string; type?: string;
parentSegmentId: string; parentSegmentId: string;
parentSpanId: number; parentSpanId: number;
traceId: string; traceId: string;
@ -60,13 +61,6 @@ export interface log {
data: Map<string, string>; data: Map<string, string>;
} }
export interface Ref {
traceId: string;
parentSegmentId: string;
parentSpanId: number;
type: string;
}
export interface StatisticsSpan { export interface StatisticsSpan {
groupRef: StatisticsGroupRef; groupRef: StatisticsGroupRef;
maxTime: number; maxTime: number;

View File

@ -16,18 +16,22 @@
*/ */
import { ElNotification } from "element-plus"; import { ElNotification } from "element-plus";
export default (value: string): void => {
const input = document.createElement("input"); export default (text: string): void => {
input.value = value; navigator.clipboard
document.body.appendChild(input); .writeText(text)
input.select(); .then(() => {
if (document.execCommand("Copy")) { ElNotification({
document.execCommand("Copy"); title: "Success",
} message: "Copied",
input.remove(); type: "success",
ElNotification({ });
title: "Success", })
message: "Copied", .catch((err) => {
type: "success", ElNotification({
}); title: "Error",
message: err,
type: "warning",
});
});
}; };

View File

@ -37,8 +37,10 @@ export const saveFile = (data: any, name: string) => {
tagA.download = name; tagA.download = name;
tagA.style.display = "none"; tagA.style.display = "none";
const blob = new Blob([newData]); const blob = new Blob([newData]);
tagA.href = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
tagA.href = url;
document.body.appendChild(tagA); document.body.appendChild(tagA);
tagA.click(); tagA.click();
document.body.removeChild(tagA); document.body.removeChild(tagA);
URL.revokeObjectURL(url);
}; };

View File

@ -17,7 +17,7 @@
class Vec2 extends Float32Array { class Vec2 extends Float32Array {
constructor(v?: unknown, y?: unknown) { constructor(v?: unknown, y?: unknown) {
super(2); super(2);
if (v instanceof Vec2 || v instanceof Float32Array || (v instanceof Array && v.length == 2)) { if (v instanceof Vec2 || v instanceof Float32Array || (v instanceof Array && v.length === 2)) {
this[0] = v[0]; this[0] = v[0];
this[1] = v[1]; this[1] = v[1];
} else if (typeof v === "number" && typeof y === "number") { } else if (typeof v === "number" && typeof y === "number") {
@ -104,7 +104,7 @@ class Vec2 extends Float32Array {
} }
norm(out?: number[] | Vec2): number[] | Vec2 | undefined { norm(out?: number[] | Vec2): number[] | Vec2 | undefined {
const mag = Math.sqrt(this[0] * this[0] + this[1] * this[1]); const mag = Math.sqrt(this[0] * this[0] + this[1] * this[1]);
if (mag == 0) return this; if (mag === 0) return this;
out = out || this; out = out || this;
out[0] = this[0] / mag; out[0] = this[0] / mag;
out[1] = this[1] / mag; out[1] = this[1] / mag;

View File

@ -17,7 +17,7 @@
class Vec3 extends Float32Array { class Vec3 extends Float32Array {
constructor(v?: unknown, y?: unknown, z?: unknown) { constructor(v?: unknown, y?: unknown, z?: unknown) {
super(3); super(3);
if (v instanceof Vec3 || v instanceof Float32Array || (v instanceof Array && v.length == 3)) { if (v instanceof Vec3 || v instanceof Float32Array || (v instanceof Array && v.length === 3)) {
this[0] = v[0]; this[0] = v[0];
this[1] = v[1]; this[1] = v[1];
this[2] = v[2]; this[2] = v[2];
@ -150,7 +150,7 @@ class Vec3 extends Float32Array {
} }
static norm(x: unknown, y: unknown, z: unknown): Vec3 { static norm(x: unknown, y: unknown, z: unknown): Vec3 {
const rtn = new Vec3(); const rtn = new Vec3();
if (x instanceof Vec3 || x instanceof Float32Array || (x instanceof Array && x.length == 3)) { if (x instanceof Vec3 || x instanceof Float32Array || (x instanceof Array && x.length === 3)) {
rtn.copy(x); rtn.copy(x);
} else if (typeof x === "number" && typeof y === "number" && typeof z === "number") { } else if (typeof x === "number" && typeof y === "number" && typeof z === "number") {
rtn.xyz(x, y, z); rtn.xyz(x, y, z);

View File

@ -22,7 +22,7 @@ limitations under the License. -->
placeholder="Select a language" placeholder="Select a language"
@change="setLang" @change="setLang"
size="small" size="small"
style="font-size: 14px" style="font-size: 14px; width: 180px"
/> />
</div> </div>
<div class="flex-h item"> <div class="flex-h item">

View File

@ -22,15 +22,17 @@ limitations under the License. -->
<div class="message mb-5 b"> <div class="message mb-5 b">
{{ i.message }} {{ i.message }}
</div> </div>
<div <div class="flex-h">
class="timeline-table-i-scope mr-10 l sm" <div
:class="{ class="timeline-table-i-scope"
blue: i.scope === 'Service', :class="{
green: i.scope === 'Endpoint', blue: i.scope === 'Service',
yellow: i.scope === 'ServiceInstance', green: i.scope === 'Endpoint',
}" yellow: i.scope === 'ServiceInstance',
> }"
{{ t(i.scope.toLowerCase()) }} >
{{ t(i.scope.toLowerCase()) }}
</div>
</div> </div>
<div class="grey sm show-xs"> <div class="grey sm show-xs">
{{ dateFormat(parseInt(i.startTime)) }} {{ dateFormat(parseInt(i.startTime)) }}
@ -46,7 +48,7 @@ limitations under the License. -->
:destroy-on-close="true" :destroy-on-close="true"
@closed="isShowDetails = false" @closed="isShowDetails = false"
> >
<div class="mb-10 clear alarm-detail" v-for="(item, index) in AlarmDetailCol" :key="index"> <div class="mb-20 clear alarm-detail" v-for="(item, index) in AlarmDetailCol" :key="index">
<span class="g-sm-2 grey">{{ t(item.value) }}:</span> <span class="g-sm-2 grey">{{ t(item.value) }}:</span>
<span v-if="item.label === 'startTime'"> <span v-if="item.label === 'startTime'">
{{ dateFormat(currentDetail[item.label]) }} {{ dateFormat(currentDetail[item.label]) }}
@ -54,7 +56,7 @@ limitations under the License. -->
<span v-else-if="item.label === 'tags'"> <span v-else-if="item.label === 'tags'">
<div v-for="(d, index) in alarmTags" :key="index">{{ d }}</div> <div v-for="(d, index) in alarmTags" :key="index">{{ d }}</div>
</span> </span>
<span v-else-if="item.label === 'events'" class="event-detail"> <span v-else-if="item.label === 'events'">
<div> <div>
<ul> <ul>
<li> <li>
@ -75,6 +77,12 @@ limitations under the License. -->
</ul> </ul>
</div> </div>
</span> </span>
<span v-else-if="item.label === 'expression'">
{{ currentDetail.snapshot.expression }}
</span>
<span v-else-if="item.label === 'snapshot'">
<Snapshot :snapshot="currentDetail.snapshot" />
</span>
<span v-else>{{ currentDetail[item.label] }}</span> <span v-else>{{ currentDetail[item.label] }}</span>
</div> </div>
</el-dialog> </el-dialog>
@ -85,7 +93,7 @@ limitations under the License. -->
:destroy-on-close="true" :destroy-on-close="true"
@closed="showEventDetails = false" @closed="showEventDetails = false"
> >
<div class="event-detail"> <div>
<div class="mb-10" v-for="(eventKey, index) in EventsDetailKeys" :key="index"> <div class="mb-10" v-for="(eventKey, index) in EventsDetailKeys" :key="index">
<span class="keys">{{ t(eventKey.text) }}</span> <span class="keys">{{ t(eventKey.text) }}</span>
<span v-if="eventKey.class === 'parameters'"> <span v-if="eventKey.class === 'parameters'">
@ -117,6 +125,7 @@ limitations under the License. -->
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"; import { dateFormat } from "@/utils/dateFormat";
import Snapshot from "./components/Snapshot.vue";
const { t } = useI18n(); const { t } = useI18n();
const alarmStore = useAlarmStore(); const alarmStore = useAlarmStore();
@ -186,11 +195,10 @@ limitations under the License. -->
} }
.timeline-table-i-scope { .timeline-table-i-scope {
display: inline-block; padding: 0 5px;
padding: 0 8px;
border: 1px solid; border: 1px solid;
margin-top: -1px; border-radius: 3px;
border-radius: 4px; display: inline-block;
} }
.timeline-item { .timeline-item {
@ -224,9 +232,6 @@ limitations under the License. -->
} }
.alarm-detail { .alarm-detail {
max-height: 600px;
overflow: auto;
ul { ul {
min-height: 100px; min-height: 100px;
overflow: auto; overflow: auto;
@ -247,4 +252,9 @@ limitations under the License. -->
} }
} }
} }
.mini-chart {
height: 20px;
width: 400px;
}
</style> </style>

View File

@ -38,7 +38,7 @@ limitations under the License. -->
:total="total" :total="total"
@current-change="changePage" @current-change="changePage"
:pager-count="5" :pager-count="5"
small size="small"
:style=" :style="
appStore.theme === Themes.Light appStore.theme === Themes.Light
? `--el-pagination-bg-color: #f0f2f5; --el-pagination-button-disabled-bg-color: #f0f2f5;` ? `--el-pagination-bg-color: #f0f2f5; --el-pagination-button-disabled-bg-color: #f0f2f5;`

View File

@ -0,0 +1,141 @@
<!-- 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>
<Graph :option="option" @select="clickEvent" />
</template>
<script lang="ts" setup>
import { computed } from "vue";
import type { PropType } from "vue";
import type { EventParams } from "@/types/dashboard";
import useLegendProcess from "@/hooks/useLegendProcessor";
import { useAppStoreWithOut } from "@/store/modules/app";
import { Themes } from "@/constants/data";
/*global defineProps, defineEmits */
const emits = defineEmits(["click"]);
const props = defineProps({
data: {
type: Array as PropType<any>,
default: () => [],
},
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
});
const appStore = useAppStoreWithOut();
const option = computed(() => getOption());
function getOption() {
const { chartColors } = useLegendProcess();
const color: string[] = chartColors();
const series = [];
const grid = [];
const xAxis = [];
const yAxis = [];
for (const [index, metric] of props.data.entries()) {
grid.push({
top: 300 * index + 30,
left: 0,
right: 10,
bottom: 5,
containLabel: true,
height: 260,
});
xAxis.push({
type: "category",
show: true,
axisTick: {
lineStyle: { color: "#c1c5ca41" },
alignWithLabel: true,
},
splitLine: { show: false },
axisLine: { lineStyle: { color: "rgba(0,0,0,0)" } },
axisLabel: { color: "#9da5b2", fontSize: "11" },
gridIndex: index,
});
yAxis.push({
type: "value",
axisLine: { show: false },
axisTick: { show: false },
splitLine: { lineStyle: { color: "#c1c5ca41", type: "dashed" } },
axisLabel: {
color: "#9da5b2",
fontSize: "11",
show: true,
},
gridIndex: index,
});
for (const item of metric.values) {
series.push({
data: item.values.map((item: number, itemIndex: number) => [props.intervalTime[itemIndex], item]),
name: item.name || metric.name,
type: "line",
symbol: "circle",
symbolSize: 4,
xAxisIndex: index,
yAxisIndex: index,
lineStyle: {
width: 2,
type: "solid",
},
});
}
}
const tooltip = {
trigger: "axis",
backgroundColor: appStore.theme === Themes.Dark ? "#333" : "#fff",
borderColor: appStore.theme === Themes.Dark ? "#333" : "#fff",
textStyle: {
fontSize: 12,
color: appStore.theme === Themes.Dark ? "#eee" : "#333",
},
enterable: true,
confine: true,
extraCssText: "max-height:85%; overflow: auto;",
axisPointer: {
animation: false,
},
};
return {
color,
tooltip,
axisPointer: {
link: { xAxisIndex: "all" },
},
legend: {
type: "scroll",
icon: "circle",
top: -5,
left: 0,
itemWidth: 12,
textStyle: {
color: appStore.theme === Themes.Dark ? "#fff" : "#333",
},
},
grid,
xAxis,
yAxis,
series,
};
}
function clickEvent(params: EventParams) {
emits("click", params);
}
</script>
<style lang="scss" scoped>
.snapshot-charts {
width: 100%;
height: 100%;
}
</style>

View File

@ -0,0 +1,35 @@
<!-- 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>
<LineChart
:intervalTime="appStore.intervalTime"
:data="metrics"
:style="{ width: `800px`, height: `${metrics.length * 300}px` }"
/>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import { useSnapshot } from "@/hooks/useSnapshot";
import { useAppStoreWithOut } from "@/store/modules/app";
import LineChart from "./Line.vue";
/*global defineProps */
const props = defineProps({
snapshot: { type: Object, default: () => {} },
});
const { processResults } = useSnapshot(props.snapshot.metrics);
const metrics = computed(() => processResults());
const appStore = useAppStoreWithOut();
</script>

View File

@ -52,6 +52,14 @@ export const AlarmDetailCol = [
label: "events", label: "events",
value: "eventDetail", value: "eventDetail",
}, },
{
label: "expression",
value: "expression",
},
{
label: "snapshot",
value: "snapshot",
},
]; ];
export const EventsDetailKeys = [ export const EventsDetailKeys = [

View File

@ -612,7 +612,7 @@ limitations under the License. -->
} }
function searchDashboards(pageIndex: number) { function searchDashboards(pageIndex: number) {
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]"); const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
const arr = list.filter((d: { name: string }) => d.name.includes(searchText.value)); const arr = list.filter((d: { name: string }) => d.name.toLowerCase().includes(searchText.value.toLowerCase()));
total.value = arr.length; total.value = arr.length;
dashboards.value = arr.filter( dashboards.value = arr.filter(

View File

@ -32,11 +32,13 @@ limitations under the License. -->
:config="{ :config="{
i: 0, i: 0,
...graph, ...graph,
valueMappings: graph?.valueMappings,
metricConfig: config.metricConfig, metricConfig: config.metricConfig,
expressions: config.expressions || [], expressions: config.expressions || [],
typesOfMQE: typesOfMQE || [], typesOfMQE: typesOfMQE || [],
subExpressions: config.subExpressions || [], subExpressions: config.subExpressions || [],
subTypesOfMQE: config.subTypesOfMQE || [], subTypesOfMQE: config.subTypesOfMQE || [],
valueRelatedDashboard: config.valueRelatedDashboard,
}" }"
:needQuery="true" :needQuery="true"
/> />

View File

@ -126,7 +126,7 @@ limitations under the License. -->
opt.auto = Number(f.value) * 60 * 60 * 1000; opt.auto = Number(f.value) * 60 * 60 * 1000;
} }
if (f.step === TimeType.DAY_TIME) { if (f.step === TimeType.DAY_TIME) {
opt.auto = Number(f.value) * 60 * 60 * 60 * 1000; opt.auto = Number(f.value) * 24 * 60 * 60 * 1000;
} }
} }
const config = JSON.stringify(opt); const config = JSON.stringify(opt);

View File

@ -32,7 +32,8 @@ limitations under the License. -->
:data="states.source" :data="states.source"
:config="{ :config="{
...graph, ...graph,
legend: (dashboardStore.selectedGrid.graph || {}).legend, decorations: dashboardStore.selectedGrid.graph?.decorations,
legend: dashboardStore.selectedGrid.graph?.legend,
i: dashboardStore.selectedGrid.i, i: dashboardStore.selectedGrid.i,
metricConfig: dashboardStore.selectedGrid.metricConfig, metricConfig: dashboardStore.selectedGrid.metricConfig,
relatedTrace: dashboardStore.selectedGrid.relatedTrace, relatedTrace: dashboardStore.selectedGrid.relatedTrace,
@ -40,6 +41,7 @@ limitations under the License. -->
typesOfMQE: dashboardStore.selectedGrid.typesOfMQE || [], typesOfMQE: dashboardStore.selectedGrid.typesOfMQE || [],
subExpressions: dashboardStore.selectedGrid.subExpressions || [], subExpressions: dashboardStore.selectedGrid.subExpressions || [],
subTypesOfMQE: dashboardStore.selectedGrid.subTypesOfMQE || [], subTypesOfMQE: dashboardStore.selectedGrid.subTypesOfMQE || [],
valueRelatedDashboard: dashboardStore.selectedGrid.valueRelatedDashboard,
}" }"
:needQuery="true" :needQuery="true"
@expressionTips="getErrors" @expressionTips="getErrors"

View File

@ -81,7 +81,7 @@ limitations under the License. -->
const latency = ref<boolean>(traceOpt.latency || false); const latency = ref<boolean>(traceOpt.latency || false);
const enableRelate = ref<boolean>(traceOpt.enableRelate || false); const enableRelate = ref<boolean>(traceOpt.enableRelate || false);
const type = ref<string>((graph && graph.type) || ""); const type = ref<string>((graph && graph.type) || "");
const refIdType = ref<string>(traceOpt.refIdType || "traceId"); const refIdType = ref<string>(traceOpt.refIdType || "");
function updateConfig(param: { [key: string]: unknown }) { function updateConfig(param: { [key: string]: unknown }) {
const relatedTrace = { const relatedTrace = {

View File

@ -13,6 +13,9 @@ 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>
<value-mappings />
</div>
<div> <div>
<span class="label">{{ t("fontSize") }}</span> <span class="label">{{ t("fontSize") }}</span>
<el-slider <el-slider
@ -26,7 +29,7 @@ limitations under the License. -->
@change="updateConfig({ fontSize })" @change="updateConfig({ fontSize })"
/> />
</div> </div>
<div class="item"> <div>
<span class="label">{{ t("showUnit") }}</span> <span class="label">{{ t("showUnit") }}</span>
<el-switch v-model="showUnit" active-text="Yes" inactive-text="No" @change="updateConfig({ showUnit })" /> <el-switch v-model="showUnit" active-text="Yes" inactive-text="No" @change="updateConfig({ showUnit })" />
</div> </div>
@ -35,6 +38,7 @@ limitations under the License. -->
import { ref } from "vue"; import { ref } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import ValueMappings from "./components/ValueMappings.vue";
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();

View File

@ -14,6 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div> <div>
<value-mappings />
</div>
<div class="item">
<span class="label">{{ t("showValues") }}</span> <span class="label">{{ t("showValues") }}</span>
<el-switch <el-switch
v-model="showTableValues" v-model="showTableValues"
@ -37,6 +40,7 @@ limitations under the License. -->
import { ref } from "vue"; import { ref } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import ValueMappings from "./components/ValueMappings.vue";
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();

View File

@ -22,6 +22,15 @@ limitations under the License. -->
@change="updateLegendConfig({ show: legend.show })" @change="updateLegendConfig({ show: legend.show })"
/> />
</div> </div>
<div>
<span class="label mr-5">{{ t("asSelector") }}</span>
<el-switch
v-model="legend.asSelector"
active-text="Yes"
inactive-text="No"
@change="updateLegendConfig({ asSelector: legend.asSelector })"
/>
</div>
<div> <div>
<span class="label">{{ t("asTable") }}</span> <span class="label">{{ t("asTable") }}</span>
<el-switch <el-switch
@ -97,6 +106,7 @@ limitations under the License. -->
max: false, max: false,
mean: false, mean: false,
asTable: false, asTable: false,
asSelector: false,
toTheRight: false, toTheRight: false,
width: 130, width: 130,
...graph.value.legend, ...graph.value.legend,

View File

@ -0,0 +1,112 @@
<!-- 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("valueMappings") }}</span>
<span class="label red">{{ t("mappingTip") }}</span>
</div>
<div v-for="(key, index) in keys" :key="index" class="mb-10 flex-h">
<div class="content-decoration" contenteditable="true" @blur="changeKeys($event, index)">
{{ key }}
</div>
<div class="ml-5 mr-10">:</div>
<div class="content-decoration" contenteditable="true" @blur="changeValues($event, key)">
{{ valueMappings[key] }}
</div>
<div v-if="index === keys.length - 1">
<Icon class="cp mr-5" iconName="add_circle_outlinecontrol_point" size="middle" @click="addDecoration" />
<Icon
v-if="index !== 0"
class="cp"
iconName="remove_circle_outline"
size="middle"
@click="deleteDecoration(index)"
/>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const graph = dashboardStore.selectedGrid.graph;
const valueMappings = ref<{ [key: string]: string }>(graph?.valueMappings || {});
const keys = ref<string[]>(graph.valueMappings ? Object.keys(valueMappings.value) : [""]);
function changeKeys(event: any, index: number) {
const params = event.target.textContent || "";
const list = Object.keys(valueMappings.value);
if (params) {
valueMappings.value[params] = valueMappings.value[list[index]];
}
delete valueMappings.value[list[index]];
keys.value = Object.keys(valueMappings.value);
if (!keys.value.length) {
keys.value = [""];
}
updateConfig();
}
function changeValues(event: any, key: string) {
valueMappings.value[key] = event.target.textContent || "";
updateConfig();
}
function addDecoration() {
keys.value.push("");
}
function deleteDecoration(index: number) {
if (!keys.value.length) {
return;
}
delete valueMappings.value[keys.value[index]];
keys.value.splice(index, 1);
updateConfig();
}
function updateConfig() {
const graph = {
...dashboardStore.selectedGrid.graph,
valueMappings: valueMappings.value,
};
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, graph });
}
</script>
<style lang="scss" scoped>
.content-decoration {
width: 350px;
border: 1px solid $border-color;
cursor: text;
padding: 0 5px;
border-radius: 3px;
outline: none;
margin-right: 5px;
min-height: 26px;
&:focus {
border-color: $active-color;
}
}
.label {
font-size: 13px;
font-weight: 500;
padding-right: 10px;
}
</style>

View File

@ -95,6 +95,20 @@ limitations under the License. -->
{{ type.label }} {{ type.label }}
</span> </span>
</div> </div>
<div v-if="states.isTopList" class="mt-10">
<div>{{ t("valueDashboard") }}</div>
<div>
<Selector
:value="states.valueRelatedDashboard || ''"
:options="states.dashboardList"
size="small"
placeholder="Please select a dashboard name"
@change="changeValueDashboard"
class="selectors"
:clearable="true"
/>
</div>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, ref, computed } from "vue"; import { reactive, ref, computed } from "vue";
@ -140,21 +154,25 @@ limitations under the License. -->
metricTypes: string[]; metricTypes: string[];
metricTypeList: Option[][]; metricTypeList: Option[][];
isList: boolean; isList: boolean;
isTopList: boolean;
dashboardName: string; dashboardName: string;
dashboardList: ((DashboardItem & { label: string; value: string }) | any)[]; dashboardList: ((DashboardItem & { label: string; value: string }) | any)[];
tips: string[]; tips: string[];
subTips: string[]; subTips: string[];
valueRelatedDashboard: string;
}>({ }>({
metrics: metrics.value.length ? metrics.value : [""], metrics: metrics.value.length ? metrics.value : [""],
metricTypes: typesOfMQE.value.length ? typesOfMQE.value : [""], metricTypes: typesOfMQE.value.length ? typesOfMQE.value : [""],
metricTypeList: [], metricTypeList: [],
isList: false, isList: false,
isTopList: false,
dashboardName: graph.value.dashboardName, dashboardName: graph.value.dashboardName,
dashboardList: [{ label: "", value: "" }], dashboardList: [{ label: "", value: "" }],
tips: [], tips: [],
subTips: [], subTips: [],
subMetrics: subMetrics.value.length ? subMetrics.value : [""], subMetrics: subMetrics.value.length ? subMetrics.value : [""],
subMetricTypes: subMetricTypes.value.length ? subMetricTypes.value : [""], subMetricTypes: subMetricTypes.value.length ? subMetricTypes.value : [""],
valueRelatedDashboard: dashboardStore.selectedGrid.valueRelatedDashboard,
}); });
const currentMetricConfig = ref<MetricConfigOpt>({ const currentMetricConfig = ref<MetricConfigOpt>({
unit: "", unit: "",
@ -163,6 +181,7 @@ limitations under the License. -->
sortOrder: "DES", sortOrder: "DES",
}); });
states.isTopList = graph.value.type === ChartTypes[4].value;
states.isList = ListChartTypes.includes(graph.value.type); states.isList = ListChartTypes.includes(graph.value.type);
const defaultLen = ref<number>(states.isList ? 5 : 20); const defaultLen = ref<number>(states.isList ? 5 : 20);
@ -187,9 +206,10 @@ limitations under the License. -->
const arr = list.reduce((prev: (DashboardItem & { label: string; value: string })[], d: DashboardItem) => { const arr = list.reduce((prev: (DashboardItem & { label: string; value: string })[], d: DashboardItem) => {
if (d.layer === dashboardStore.layerId) { if (d.layer === dashboardStore.layerId) {
if ( if (
(d.entity === EntityType[0].value && chart === "ServiceList") || (d.entity === EntityType[0].value && chart === ChartTypes[8].value) ||
(d.entity === EntityType[2].value && chart === "EndpointList") || (d.entity === EntityType[2].value && chart === ChartTypes[9].value) ||
(d.entity === EntityType[3].value && chart === "InstanceList") (d.entity === EntityType[3].value && chart === ChartTypes[10].value) ||
states.isTopList
) { ) {
prev.push({ prev.push({
...d, ...d,
@ -254,9 +274,28 @@ limitations under the License. -->
typesOfMQE: states.metricTypes, typesOfMQE: states.metricTypes,
}); });
emit("update", params.source || {}); emit("update", params.source || {});
if (states.isTopList) {
const values: any = Object.values(params.source)[0];
if (!values) {
return;
}
states.dashboardList = states.dashboardList.filter((d) => d.entity === values[0].owner.scope);
}
} }
function changeDashboard(opt: any) { function changeValueDashboard(opt: { value: string }[]) {
if (!opt[0]) {
states.valueRelatedDashboard = "";
} else {
states.valueRelatedDashboard = opt[0].value;
}
dashboardStore.selectWidget({
...dashboardStore.selectedGrid,
valueRelatedDashboard: states.valueRelatedDashboard,
});
}
function changeDashboard(opt: { value: string }[]) {
if (!opt[0]) { if (!opt[0]) {
states.dashboardName = ""; states.dashboardName = "";
} else { } else {

View File

@ -0,0 +1,85 @@
<!-- 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="profile-wrapper flex-v">
<el-popover placement="bottom" trigger="click" :width="100" v-if="dashboardStore.editMode">
<template #reference>
<span class="operation cp">
<Icon iconName="ellipsis_v" size="middle" />
</span>
</template>
<div class="tools" @click="removeWidget">
<span>{{ t("delete") }}</span>
</div>
</el-popover>
<Header />
<Content :config="props.data" />
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Content from "../related/async-profiling/Content.vue";
import Header from "../related/async-profiling/Header.vue";
/*global defineProps*/
const props = defineProps({
data: {
type: Object as PropType<any>,
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);
}
</script>
<style lang="scss" scoped>
.profile-wrapper {
width: 100%;
height: 100%;
font-size: $font-size-smaller;
position: relative;
}
.operation {
position: absolute;
top: 8px;
right: 3px;
}
.header {
padding: 10px;
font-size: $font-size-smaller;
border-bottom: 1px solid $border-color;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: $active-color;
background-color: $popper-hover-bg-color;
}
}
</style>

View File

@ -184,7 +184,7 @@ limitations under the License. -->
position: relative; position: relative;
width: 2px; width: 2px;
height: 100%; height: 100%;
background-color: #e8e8e8; background-color: var(--sw-trace-line);
cursor: ew-resize; cursor: ew-resize;
&:hover { &:hover {

View File

@ -61,6 +61,7 @@ limitations under the License. -->
typesOfMQE: typesOfMQE || [], typesOfMQE: typesOfMQE || [],
subExpressions: data.subExpressions || [], subExpressions: data.subExpressions || [],
subTypesOfMQE: data.subTypesOfMQE || [], subTypesOfMQE: data.subTypesOfMQE || [],
valueRelatedDashboard: data.valueRelatedDashboard,
}" }"
:needQuery="needQuery" :needQuery="needQuery"
@click="clickHandle" @click="clickHandle"

View File

@ -26,6 +26,7 @@ import DemandLog from "./DemandLog.vue";
import Event from "./Event.vue"; import Event from "./Event.vue";
import NetworkProfiling from "./NetworkProfiling.vue"; import NetworkProfiling from "./NetworkProfiling.vue";
import ContinuousProfiling from "./ContinuousProfiling.vue"; import ContinuousProfiling from "./ContinuousProfiling.vue";
import AsyncProfiling from "./AsyncProfiling.vue";
import TimeRange from "./TimeRange.vue"; import TimeRange from "./TimeRange.vue";
import ThirdPartyApp from "./ThirdPartyApp.vue"; import ThirdPartyApp from "./ThirdPartyApp.vue";
import TaskTimeline from "./TaskTimeline.vue"; import TaskTimeline from "./TaskTimeline.vue";
@ -43,6 +44,7 @@ export default {
Event, Event,
NetworkProfiling, NetworkProfiling,
ContinuousProfiling, ContinuousProfiling,
AsyncProfiling,
TimeRange, TimeRange,
ThirdPartyApp, ThirdPartyApp,
TaskTimeline, TaskTimeline,

View File

@ -25,6 +25,7 @@ import DemandLog from "./DemandLog.vue";
import Event from "./Event.vue"; import Event from "./Event.vue";
import NetworkProfiling from "./NetworkProfiling.vue"; import NetworkProfiling from "./NetworkProfiling.vue";
import ContinuousProfiling from "./ContinuousProfiling.vue"; import ContinuousProfiling from "./ContinuousProfiling.vue";
import AsyncProfiling from "./AsyncProfiling.vue";
import TimeRange from "./TimeRange.vue"; import TimeRange from "./TimeRange.vue";
import ThirdPartyApp from "./ThirdPartyApp.vue"; import ThirdPartyApp from "./ThirdPartyApp.vue";
import TaskTimeline from "./TaskTimeline.vue"; import TaskTimeline from "./TaskTimeline.vue";
@ -43,5 +44,6 @@ export default {
TimeRange, TimeRange,
ThirdPartyApp, ThirdPartyApp,
ContinuousProfiling, ContinuousProfiling,
AsyncProfiling,
TaskTimeline, TaskTimeline,
}; };

View File

@ -150,6 +150,7 @@ export enum WidgetType {
Event = "Event", Event = "Event",
NetworkProfiling = "NetworkProfiling", NetworkProfiling = "NetworkProfiling",
ContinuousProfiling = "ContinuousProfiling", ContinuousProfiling = "ContinuousProfiling",
AsyncProfiling = "AsyncProfiling",
ThirdPartyApp = "ThirdPartyApp", ThirdPartyApp = "ThirdPartyApp",
TaskTimeline = "TaskTimeline", TaskTimeline = "TaskTimeline",
} }
@ -171,6 +172,7 @@ export const ServiceTools = [
{ name: "timeline", content: "Add Trace Profiling", id: WidgetType.Profile }, { name: "timeline", content: "Add Trace Profiling", id: WidgetType.Profile },
{ name: "insert_chart", content: "Add eBPF Profiling", id: WidgetType.Ebpf }, { name: "insert_chart", content: "Add eBPF Profiling", id: WidgetType.Ebpf },
{ name: "continuous_profiling", content: "Add Continuous Profiling", id: WidgetType.ContinuousProfiling }, { name: "continuous_profiling", content: "Add Continuous Profiling", id: WidgetType.ContinuousProfiling },
{ name: "async_profiling", content: "Add Async Profiling", id: WidgetType.AsyncProfiling },
{ name: "assignment", content: "Add Log", id: WidgetType.Log }, { name: "assignment", content: "Add Log", id: WidgetType.Log },
{ name: "demand", content: "Add On Demand Log", id: WidgetType.DemandLog }, { name: "demand", content: "Add On Demand Log", id: WidgetType.DemandLog },
{ name: "event", content: "Add Event", id: WidgetType.Event }, { name: "event", content: "Add Event", id: WidgetType.Event },
@ -266,8 +268,9 @@ export const TextColors: { [key: string]: string } = {
}; };
export const RefIdTypes = [ export const RefIdTypes = [
{ label: "Trace ID", value: "traceId" },
{ label: "None", value: "none" }, { label: "None", value: "none" },
{ label: "Trace ID", value: "traceId" },
{ label: "Owner", value: "owner" },
]; ];
export const RefreshOptions = [ export const RefreshOptions = [
{ label: "Last 30 minutes", value: "30", step: "MINUTE" }, { label: "Last 30 minutes", value: "30", step: "MINUTE" },

View File

@ -75,6 +75,34 @@ limitations under the License. -->
}; };
}); });
const color: string[] = chartColors(); const color: string[] = chartColors();
const legend =
appStore.theme === Themes.Dark
? {
pageIconColor: "#ccc",
pageIconInactiveColor: "#444",
backgroundColor: "#333",
borderColor: "#fff",
textStyle: {
fontSize: 12,
color: "#eee",
},
pageTextStyle: {
color: "#eee",
},
}
: {
pageIconColor: "#666",
pageIconInactiveColor: "#ccc",
backgroundColor: appStore.theme === Themes.Dark ? "#333" : "#fff",
borderColor: appStore.theme === Themes.Dark ? "#333" : "#fff",
textStyle: {
fontSize: 12,
color: "#333",
},
pageTextStyle: {
color: "#333",
},
};
return { return {
color, color,
tooltip: { tooltip: {
@ -85,7 +113,7 @@ limitations under the License. -->
}, },
enterable: true, enterable: true,
confine: true, confine: true,
extraCssText: "max-height:85%; overflow: auto;", extraCssText: "max-width: 100%; max-height: 75%; white-space: normal; overflow: auto;",
}, },
legend: { legend: {
type: "scroll", type: "scroll",
@ -94,15 +122,10 @@ limitations under the License. -->
top: 0, top: 0,
left: 0, left: 0,
itemWidth: 12, itemWidth: 12,
backgroundColor: appStore.theme === Themes.Dark ? "#333" : "#fff", ...legend,
borderColor: appStore.theme === Themes.Dark ? "#333" : "#fff",
textStyle: {
fontSize: 12,
color: appStore.theme === Themes.Dark ? "#eee" : "#333",
},
}, },
grid: { grid: {
top: keys.length === 1 ? 15 : 40, top: showEchartsLegend(keys) ? 35 : 10,
left: 0, left: 0,
right: 10, right: 10,
bottom: 5, bottom: 5,

View File

@ -22,7 +22,7 @@ limitations under the License. -->
justifyContent: config.textAlign || 'center', justifyContent: config.textAlign || 'center',
}" }"
> >
{{ singleVal }} {{ getValue() }}
<span class="unit" v-show="config.showUnit && unit"> <span class="unit" v-show="config.showUnit && unit">
{{ decodeURIComponent(unit) }} {{ decodeURIComponent(unit) }}
</span> </span>
@ -34,6 +34,7 @@ limitations under the License. -->
import type { PropType } from "vue"; import type { PropType } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import type { CardConfig, MetricConfigOpt } from "@/types/dashboard"; import type { CardConfig, MetricConfigOpt } from "@/types/dashboard";
import { ElMessage } from "element-plus";
/*global defineProps */ /*global defineProps */
const props = defineProps({ const props = defineProps({
@ -48,16 +49,36 @@ limitations under the License. -->
showUnit: true, showUnit: true,
textAlign: "center", textAlign: "center",
metricConfig: [], metricConfig: [],
valueMappings: {},
}), }),
}, },
}); });
const { t } = useI18n(); const { t } = useI18n();
const metricConfig = computed(() => props.config.metricConfig || []); const metricConfig = computed(() => props.config.metricConfig || []);
const valueMappings = computed(() => props.config.valueMappings || {});
const key = computed(() => Object.keys(props.data)[0]); const key = computed(() => Object.keys(props.data)[0]);
const singleVal = computed(() => const singleVal = computed(() =>
Array.isArray(props.data[key.value]) ? props.data[key.value][0] : props.data[key.value], Array.isArray(props.data[key.value]) ? props.data[key.value][0] : props.data[key.value],
); );
const unit = computed(() => metricConfig.value[0] && encodeURIComponent(metricConfig.value[0].unit || "")); const unit = computed(() => metricConfig.value[0] && encodeURIComponent(metricConfig.value[0].unit || ""));
function getValue() {
if (valueMappings.value[singleVal.value]) {
return valueMappings.value[singleVal.value];
}
const list = Object.keys(valueMappings.value);
for (const i of list) {
try {
if (new RegExp(i).test(String(singleVal.value))) {
return valueMappings.value[i] || singleVal.value;
}
} catch (e) {
ElMessage.error("invalid regex string");
return singleVal.value;
}
}
return singleVal.value;
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.chart-card { .chart-card {

View File

@ -16,16 +16,18 @@ limitations under the License. -->
<div class="table"> <div class="table">
<div class="search"> <div class="search">
<el-input v-model="searchText" placeholder="Search for more endpoints" @change="searchList" class="inputs"> <el-input v-model="searchText" placeholder="Search for more endpoints" @change="searchList" class="inputs">
<template #prepend>
<Selector style="width: 120px" v-model="topN" :options="topNList" placeholder="Select" />
</template>
<template #append> <template #append>
<el-button @click="searchList"> <el-button @click="searchList">
<Icon size="middle" iconName="search" /> <Icon size="middle" iconName="search" />
</el-button> </el-button>
</template> </template>
</el-input> </el-input>
<span class="ml-5 tips">{{ t("endpointTips") }}</span>
</div> </div>
<div class="list"> <div class="list">
<el-table v-loading="chartLoading" :data="endpoints" style="width: 100%"> <el-table v-loading="chartLoading" :data="currentEndpoints" style="width: 100%">
<el-table-column label="Endpoints" fixed min-width="220"> <el-table-column label="Endpoints" fixed min-width="220">
<template #default="scope"> <template #default="scope">
<span class="link" @click="clickEndpoint(scope)" :style="{ fontSize: `${config.fontSize}px` }"> <span class="link" @click="clickEndpoint(scope)" :style="{ fontSize: `${config.fontSize}px` }">
@ -46,13 +48,22 @@ limitations under the License. -->
/> />
</el-table> </el-table>
</div> </div>
<el-pagination
class="pagination flex-h"
layout="prev, pager, next"
:current-page="currentPage"
:page-size="pageSize"
:total="endpoints.length"
@current-change="handleCurrentChange"
@prev-click="changePage"
@next-click="changePage"
/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from "vue"; import { ref, watch } from "vue";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { useI18n } from "vue-i18n";
import type { PropType } from "vue"; import type { PropType } from "vue";
import type { EndpointListConfig } from "@/types/dashboard"; import type { EndpointListConfig } from "@/types/dashboard";
import type { Endpoint } from "@/types/selector"; import type { Endpoint } from "@/types/selector";
@ -90,16 +101,26 @@ limitations under the License. -->
}); });
const emit = defineEmits(["expressionTips"]); const emit = defineEmits(["expressionTips"]);
const { t } = useI18n();
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const chartLoading = ref<boolean>(false); const chartLoading = ref<boolean>(false);
const endpoints = ref<Endpoint[]>([]); const endpoints = ref<Endpoint[]>([]); // all of endpoints
const currentEndpoints = ref<Endpoint[]>([]); // current page of endpoints
const searchText = ref<string>(""); const searchText = ref<string>("");
const colMetrics = ref<string[]>([]); const colMetrics = ref<string[]>([]);
const colSubMetrics = ref<string[]>([]); const colSubMetrics = ref<string[]>([]);
const metricConfig = ref<MetricConfigOpt[]>(props.config.metricConfig || []); const metricConfig = ref<MetricConfigOpt[]>(props.config.metricConfig || []);
const typesOfMQE = ref<string[]>(props.config.typesOfMQE || []); const typesOfMQE = ref<string[]>(props.config.typesOfMQE || []);
const topN = ref<number>(20);
const currentPage = ref<number>(1);
const pageSize = 10;
const topNList = [
{ label: "TopN20", value: 20 },
{ label: "TopN50", value: 50 },
{ label: "TopN100", value: 100 },
{ label: "TopN150", value: 150 },
{ label: "TopN200", value: 200 },
];
if (props.needQuery) { if (props.needQuery) {
queryEndpoints(); queryEndpoints();
@ -108,6 +129,7 @@ limitations under the License. -->
chartLoading.value = true; chartLoading.value = true;
const resp = await selectorStore.getEndpoints({ const resp = await selectorStore.getEndpoints({
keyword: searchText.value, keyword: searchText.value,
limit: topN.value,
}); });
chartLoading.value = false; chartLoading.value = false;
@ -116,7 +138,8 @@ limitations under the License. -->
return; return;
} }
endpoints.value = resp.data.pods || []; endpoints.value = resp.data.pods || [];
queryEndpointMetrics(endpoints.value); currentEndpoints.value = endpoints.value.filter((d: unknown, index: number) => index < pageSize);
queryEndpointMetrics(currentEndpoints.value);
} }
async function queryEndpointMetrics(arr: Endpoint[]) { async function queryEndpointMetrics(arr: Endpoint[]) {
if (!arr.length) { if (!arr.length) {
@ -142,7 +165,7 @@ limitations under the License. -->
{ metricConfig: metricConfig.value || [], expressions, subExpressions }, { metricConfig: metricConfig.value || [], expressions, subExpressions },
EntityType[2].value, EntityType[2].value,
); );
endpoints.value = params.data; currentEndpoints.value = params.data;
colMetrics.value = params.names; colMetrics.value = params.names;
colSubMetrics.value = params.subNames; colSubMetrics.value = params.subNames;
metricConfig.value = params.metricConfigArr; metricConfig.value = params.metricConfigArr;
@ -151,7 +174,7 @@ limitations under the License. -->
return; return;
} }
endpoints.value = currentPods; currentEndpoints.value = currentPods;
colMetrics.value = []; colMetrics.value = [];
colSubMetrics.value = []; colSubMetrics.value = [];
metricConfig.value = []; metricConfig.value = [];
@ -175,6 +198,16 @@ limitations under the License. -->
async function searchList() { async function searchList() {
await queryEndpoints(); await queryEndpoints();
} }
function changePage() {
currentEndpoints.value = endpoints.value.filter(
(_, index: number) => index >= (currentPage.value - 1) * pageSize && index < currentPage.value * pageSize,
);
queryEndpointMetrics(currentEndpoints.value);
}
function handleCurrentChange(val: number) {
currentPage.value = val;
changePage();
}
watch( watch(
() => [ () => [
...(props.config.metricConfig || []), ...(props.config.metricConfig || []),

View File

@ -16,10 +16,11 @@ limitations under the License. -->
<div class="graph flex-v" :class="setRight ? 'flex-h' : 'flex-v'"> <div class="graph flex-v" :class="setRight ? 'flex-h' : 'flex-v'">
<Graph <Graph
:option="option" :option="option"
@select="clickEvent"
:filters="config.filters" :filters="config.filters"
:relatedTrace="config.relatedTrace" :relatedTrace="config.relatedTrace"
:associate="config.associate || []" :associate="config.associate || []"
:legendSelector="{ isSelector: legendSelector, sConfigPage: dashboardStore.showConfig }"
@select="clickEvent"
/> />
<Legend :config="config.legend" :data="data" :intervalTime="intervalTime" /> <Legend :config="config.legend" :data="data" :intervalTime="intervalTime" />
</div> </div>
@ -32,6 +33,7 @@ limitations under the License. -->
import useLegendProcess from "@/hooks/useLegendProcessor"; import useLegendProcess from "@/hooks/useLegendProcessor";
import { isDef } from "@/utils/is"; import { isDef } from "@/utils/is";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { useDashboardStore } from "@/store/modules/dashboard";
import { Themes } from "@/constants/data"; import { Themes } from "@/constants/data";
/*global defineProps, defineEmits */ /*global defineProps, defineEmits */
@ -60,19 +62,24 @@ limitations under the License. -->
showYAxis: true, showYAxis: true,
smallTips: false, smallTips: false,
showlabels: true, showlabels: true,
noTooltips: false,
}), }),
}, },
}); });
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore();
const setRight = ref<boolean>(false); const setRight = ref<boolean>(false);
const legendSelector = computed(() => props.config.legend?.asSelector);
const option = computed(() => getOption()); const option = computed(() => getOption());
function getOption() { function getOption() {
const { showEchartsLegend, isRight, chartColors } = useLegendProcess(props.config.legend); const { showEchartsLegend, isRight, chartColors } = useLegendProcess(props.config.legend);
setRight.value = isRight; setRight.value = isRight;
const keys = Object.keys(props.data || {}).filter((i: any) => Array.isArray(props.data[i]) && props.data[i].length); const keys = Object.keys(props.data || {}).filter(
const temp = keys.map((i: any) => { (i: string) => Array.isArray(props.data[i]) && props.data[i].length,
);
const temp = keys.map((i: string) => {
const serie: any = { const serie: any = {
data: props.data[i].map((item: any, itemIndex: number) => [props.intervalTime[itemIndex], item]), data: props.data[i].map((item: number, itemIndex: number) => [props.intervalTime[itemIndex], item]),
name: i, name: i,
type: "line", type: "line",
symbol: "circle", symbol: "circle",
@ -95,6 +102,7 @@ limitations under the License. -->
const color: string[] = chartColors(); const color: string[] = chartColors();
const tooltip = { const tooltip = {
trigger: "axis", trigger: "axis",
show: !props.config.noTooltips,
backgroundColor: appStore.theme === Themes.Dark ? "#333" : "#fff", backgroundColor: appStore.theme === Themes.Dark ? "#333" : "#fff",
borderColor: appStore.theme === Themes.Dark ? "#333" : "#fff", borderColor: appStore.theme === Themes.Dark ? "#333" : "#fff",
textStyle: { textStyle: {
@ -103,13 +111,13 @@ limitations under the License. -->
}, },
enterable: true, enterable: true,
confine: true, confine: true,
extraCssText: "max-height:85%; overflow: auto;", extraCssText: "max-width: 100%; max-height: 75%; white-space: normal; overflow: auto;",
}; };
const tips = { const tips = {
show: !props.config.noTooltips,
formatter(params: any) { formatter(params: any) {
return `${params[0].value[1]}`; return `${params[0].value[1]}`;
}, },
confine: true,
extraCssText: `height: 20px; padding:0 2px;`, extraCssText: `height: 20px; padding:0 2px;`,
trigger: "axis", trigger: "axis",
backgroundColor: appStore.theme === Themes.Dark ? "#666" : "#eee", backgroundColor: appStore.theme === Themes.Dark ? "#666" : "#eee",
@ -119,7 +127,30 @@ limitations under the License. -->
color: appStore.theme === Themes.Dark ? "#eee" : "#333", color: appStore.theme === Themes.Dark ? "#eee" : "#333",
}, },
}; };
const legend =
appStore.theme === Themes.Dark
? {
pageIconColor: "#ccc",
pageIconInactiveColor: "#444",
textStyle: {
fontSize: 12,
color: "#eee",
},
pageTextStyle: {
color: "#eee",
},
}
: {
pageIconColor: "#666",
pageIconInactiveColor: "#ccc",
textStyle: {
fontSize: 12,
color: "#333",
},
pageTextStyle: {
color: "#333",
},
};
return { return {
color, color,
tooltip: props.config.smallTips ? tips : tooltip, tooltip: props.config.smallTips ? tips : tooltip,
@ -130,16 +161,15 @@ limitations under the License. -->
top: 0, top: 0,
left: 0, left: 0,
itemWidth: 12, itemWidth: 12,
textStyle: { data: keys.map((d: string) => ({ name: d })),
color: appStore.theme === Themes.Dark ? "#fff" : "#333", ...legend,
},
}, },
grid: { grid: {
top: showEchartsLegend(keys) ? 35 : 10, top: showEchartsLegend(keys) ? 35 : 10,
left: 0, left: 0,
right: 10, right: 10,
bottom: 5, bottom: 5,
containLabel: props.config.showlabels === undefined ? true : props.config.showlabels, containLabel: isDef(props.config.showlabels) ? props.config.showlabels : true,
}, },
xAxis: { xAxis: {
type: "category", type: "category",

View File

@ -37,7 +37,7 @@ limitations under the License. -->
>{{ k.split("=")[1] }}</div >{{ k.split("=")[1] }}</div
> >
<div class="value-col" v-if="config.showTableValues"> <div class="value-col" v-if="config.showTableValues">
{{ data[(keys as string[]).join(",")][data[(keys as string[]).join(",")].length - 1 || 0] }} {{ getColValue(keys) }}
</div> </div>
</div> </div>
</div> </div>
@ -47,6 +47,8 @@ limitations under the License. -->
import { computed } from "vue"; import { computed } from "vue";
import type { PropType } from "vue"; import type { PropType } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
/*global defineProps */ /*global defineProps */
const props = defineProps({ const props = defineProps({
data: { data: {
@ -58,12 +60,14 @@ limitations under the License. -->
showTableValues: boolean; showTableValues: boolean;
tableHeaderCol2: string; tableHeaderCol2: string;
typesOfMQE: string[]; typesOfMQE: string[];
valueMappings: {};
}>, }>,
default: () => ({ showTableValues: true }), default: () => ({ showTableValues: true }),
}, },
}); });
const { t } = useI18n(); const { t } = useI18n();
const valueMappings = computed<{ [key: string]: string }>(() => props.config.valueMappings || {});
const nameWidth = computed(() => (props.config.showTableValues ? 80 : 100)); const nameWidth = computed(() => (props.config.showTableValues ? 80 : 100));
const dataKeys = computed(() => { const dataKeys = computed(() => {
const keys = Object.keys(props.data || {}).filter( const keys = Object.keys(props.data || {}).filter(
@ -73,6 +77,25 @@ limitations under the License. -->
return list; return list;
}); });
function getColValue(keys: string[]) {
const source = props.data[(keys as string[]).join(",")][props.data[(keys as string[]).join(",")].length - 1 || 0];
if (valueMappings.value[source]) {
return valueMappings.value[source];
}
const list = Object.keys(valueMappings.value);
for (const i of list) {
try {
if (new RegExp(i).test(String(source))) {
return valueMappings.value[i] || source;
}
} catch (e) {
ElMessage.error("invalid regex string");
return source;
}
}
return source;
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.chart-table { .chart-table {

View File

@ -32,9 +32,12 @@ limitations under the License. -->
<div class="operation" @click="handleClick(i.name)"> <div class="operation" @click="handleClick(i.name)">
<span>{{ t("copy") }}</span> <span>{{ t("copy") }}</span>
</div> </div>
<div class="operation" @click="viewTrace(i)" v-show="refIdType === RefIdTypes[0].value"> <div class="operation" @click="viewTrace(i)" v-show="![RefIdTypes[0].value].includes(refIdType)">
<span>{{ t("viewTrace") }}</span> <span>{{ t("viewTrace") }}</span>
</div> </div>
<div class="operation" @click="viewDashboard(i)" v-show="props.config.valueRelatedDashboard">
<span>{{ t("viewValueDashboard") }}</span>
</div>
</el-popover> </el-popover>
</div> </div>
<el-progress <el-progress
@ -61,16 +64,18 @@ limitations under the License. -->
import type { PropType } from "vue"; import type { PropType } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import router from "@/router";
import { useDashboardStore } from "@/store/modules/dashboard";
import copy from "@/utils/copy"; import copy from "@/utils/copy";
import { TextColors } from "@/views/dashboard/data"; import { TextColors, MetricCatalog } from "@/views/dashboard/data";
import Trace from "@/views/dashboard/related/trace/Index.vue"; import Trace from "@/views/dashboard/related/trace/Index.vue";
import { WidgetType, QueryOrders, Status, RefIdTypes, ExpressionResultType } from "@/views/dashboard/data"; import { WidgetType, QueryOrders, Status, RefIdTypes, ExpressionResultType } from "@/views/dashboard/data";
/*global defineProps */ /*global defineProps, Recordable*/
const props = defineProps({ const props = defineProps({
data: { data: {
type: Object as PropType<{ type: Object as PropType<{
[key: string]: { name: string; value: number; refId: string }[]; [key: string]: { name: string; value: number; refId: string; owner: object }[];
}>, }>,
default: () => ({}), default: () => ({}),
}, },
@ -80,12 +85,14 @@ limitations under the License. -->
expressions: string[]; expressions: string[];
typesOfMQE: string[]; typesOfMQE: string[];
relatedTrace: any; relatedTrace: any;
valueRelatedDashboard: string;
}>, }>,
default: () => ({ color: "purple" }), default: () => ({ color: "purple" }),
}, },
intervalTime: { type: Array as PropType<string[]>, default: () => [] }, intervalTime: { type: Array as PropType<string[]>, default: () => [] },
}); });
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore();
const showTrace = ref<boolean>(false); const showTrace = ref<boolean>(false);
const traceOptions = ref<{ type: string; filters?: unknown }>({ const traceOptions = ref<{ type: string; filters?: unknown }>({
type: WidgetType.Trace, type: WidgetType.Trace,
@ -107,14 +114,15 @@ limitations under the License. -->
function handleClick(i: string) { function handleClick(i: string) {
copy(i); copy(i);
} }
function viewTrace(item: { name: string; refId: string; value: unknown }) { function viewTrace(item: { name: string; refId: string; value: unknown; owner: object }) {
const filters = { const filters = {
...item, ...item,
queryOrder: QueryOrders[1].value, queryOrder: QueryOrders[1].value,
status: Status[2].value, status: Status[2].value,
id: item.refId, id: refIdType.value === RefIdTypes[1].value ? item.refId : undefined,
metricValue: [{ label: props.config.expressions[0], data: item.value, value: item.name }], metricValue: [{ label: props.config.expressions[0], data: item.value, value: item.name }],
isReadRecords: props.config.typesOfMQE.includes(ExpressionResultType.RECORD_LIST) || undefined, isReadRecords: props.config.typesOfMQE.includes(ExpressionResultType.RECORD_LIST) || undefined,
owner: refIdType.value === RefIdTypes[2].value ? item.owner : null,
}; };
traceOptions.value = { traceOptions.value = {
...traceOptions.value, ...traceOptions.value,
@ -122,6 +130,23 @@ limitations under the License. -->
}; };
showTrace.value = true; showTrace.value = true;
} }
function viewDashboard(item: Recordable) {
const { owner } = item;
let path;
if (owner.scope === MetricCatalog.SERVICE) {
path = `/dashboard/${dashboardStore.layerId}/${owner.scope}/${owner.serviceID}/${props.config.valueRelatedDashboard}`;
}
if (owner.scope === MetricCatalog.SERVICE_INSTANCE) {
path = `/dashboard/${dashboardStore.layerId}/${owner.scope}/${owner.serviceID}/${owner.serviceInstanceID}/${props.config.valueRelatedDashboard}`;
}
if (owner.scope === MetricCatalog.ENDPOINT) {
path = `/dashboard/${dashboardStore.layerId}/${owner.scope}/${owner.serviceID}/${owner.endpointID}/${props.config.valueRelatedDashboard}`;
}
if (!path) {
return;
}
router.push(path);
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.top-list { .top-list {
@ -187,11 +212,10 @@ limitations under the License. -->
} }
.operation { .operation {
padding: 5px 0; padding: 5px;
color: $font-color; color: $font-color;
cursor: pointer; cursor: pointer;
position: relative; position: relative;
text-align: center;
font-size: $font-size-smaller; font-size: $font-size-smaller;
&:hover { &:hover {

View File

@ -53,5 +53,5 @@
} }
.inputs { .inputs {
width: 300px; width: 400px;
} }

View File

@ -77,16 +77,20 @@ limitations under the License. -->
} }
} }
async function queryMetrics() { async function queryMetrics() {
const widgets = []; const widgets: LayoutConfig[] = [];
for (const item of dashboardStore.layout) { for (const item of dashboardStore.layout) {
const isList = ListChartTypes.includes(item.type || ""); if (item.type === WidgetType.Widget) {
if (item.type === WidgetType.Widget && !isList) { if (!ListChartTypes.includes(item.graph?.type || "")) {
widgets.push(item); widgets.push(item);
}
} }
if (item.type === WidgetType.Tab) { if (item.type === WidgetType.Tab) {
const index = isNaN(item.activedTabIndex) ? 0 : item.activedTabIndex; const index = isNaN(item.activedTabIndex) ? 0 : item.activedTabIndex;
widgets.push(...item.children[index].children); widgets.push(
...item.children[index].children.filter(
(d: LayoutConfig) => d.type === WidgetType.Widget && !ListChartTypes.includes(d.graph?.type || ""),
),
);
} }
} }
const configList = widgets.map((d: LayoutConfig) => ({ const configList = widgets.map((d: LayoutConfig) => ({
@ -103,7 +107,9 @@ limitations under the License. -->
} }
async function queryTabsMetrics() { async function queryTabsMetrics() {
const configList = dashboardStore.currentTabItems const configList = dashboardStore.currentTabItems
.filter((item: LayoutConfig) => item.type === WidgetType.Widget && !ListChartTypes.includes(item.type || "")) .filter(
(item: LayoutConfig) => item.type === WidgetType.Widget && !ListChartTypes.includes(item.graph?.type || ""),
)
.map((d: LayoutConfig) => ({ .map((d: LayoutConfig) => ({
metrics: d.expressions || [], metrics: d.expressions || [],
metricConfig: d.metricConfig || [], metricConfig: d.metricConfig || [],
@ -151,7 +157,7 @@ limitations under the License. -->
}, },
); );
watch( watch(
() => appStore.durationTime, () => [appStore.durationTime, dashboardStore.layout],
() => { () => {
if (dashboardStore.entity === EntityType[1].value) { if (dashboardStore.entity === EntityType[1].value) {
queryMetrics(); queryMetrics();

View File

@ -16,7 +16,7 @@ limitations under the License. -->
<div class="dashboard-tool flex-h"> <div class="dashboard-tool flex-h">
<div :class="isRelation ? 'flex-v' : 'flex-h'" class="tool-selectors"> <div :class="isRelation ? 'flex-v' : 'flex-h'" class="tool-selectors">
<div class="flex-h"> <div class="flex-h">
<div class="selectors-item" v-if="key !== 10"> <div class="flex-h selectors-item" v-if="key !== 10">
<span class="label">$Service</span> <span class="label">$Service</span>
<Selector <Selector
v-model="states.currentService" v-model="states.currentService"
@ -30,7 +30,7 @@ limitations under the License. -->
<Icon size="small" iconName="hierarchy_topology" /> <Icon size="small" iconName="hierarchy_topology" />
</span> </span>
</div> </div>
<div class="selectors-item" v-if="key === 3 || key === 4 || key === 5 || key === 6"> <div class="flex-h selectors-item" v-if="key === 3 || key === 4 || key === 5 || key === 6">
<span class="label"> <span class="label">
{{ ["EndpointRelation", "Endpoint"].includes(dashboardStore.entity) ? "$Endpoint" : "$ServiceInstance" }} {{ ["EndpointRelation", "Endpoint"].includes(dashboardStore.entity) ? "$Endpoint" : "$ServiceInstance" }}
</span> </span>
@ -52,7 +52,7 @@ limitations under the License. -->
<Icon size="small" iconName="hierarchy_topology" /> <Icon size="small" iconName="hierarchy_topology" />
</span> </span>
</div> </div>
<div class="selectors-item" v-if="key === 5 || key === 6"> <div class="flex-h selectors-item" v-if="key === 5 || key === 6">
<span class="label"> $Process </span> <span class="label"> $Process </span>
<Selector <Selector
v-model="states.currentProcess" v-model="states.currentProcess"
@ -65,7 +65,7 @@ limitations under the License. -->
</div> </div>
</div> </div>
<div class="flex-h" :class="isRelation ? 'relation' : ''"> <div class="flex-h" :class="isRelation ? 'relation' : ''">
<div class="selectors-item" v-if="key === 2 || key === 4 || key === 5"> <div class="flex-h selectors-item" v-if="key === 2 || key === 4 || key === 5">
<span class="label">$DestinationService</span> <span class="label">$DestinationService</span>
<Selector <Selector
v-model="states.currentDestService" v-model="states.currentDestService"
@ -76,7 +76,7 @@ limitations under the License. -->
class="selectors" class="selectors"
/> />
</div> </div>
<div class="selectors-item" v-if="key === 4 || key === 5"> <div class="flex-h selectors-item" v-if="key === 4 || key === 5">
<span class="label"> <span class="label">
{{ dashboardStore.entity === "EndpointRelation" ? "$DestinationEndpoint" : "$DestinationServiceInstance" }} {{ dashboardStore.entity === "EndpointRelation" ? "$DestinationEndpoint" : "$DestinationServiceInstance" }}
</span> </span>
@ -91,7 +91,7 @@ limitations under the License. -->
:isRemote="dashboardStore.entity === 'EndpointRelation'" :isRemote="dashboardStore.entity === 'EndpointRelation'"
/> />
</div> </div>
<div class="selectors-item" v-if="key === 5"> <div class="flex-h selectors-item" v-if="key === 5">
<span class="label"> $DestinationProcess </span> <span class="label"> $DestinationProcess </span>
<Selector <Selector
v-model="states.currentDestProcess" v-model="states.currentDestProcess"
@ -105,11 +105,11 @@ limitations under the License. -->
</div> </div>
</div> </div>
<div class="flex-h tools" v-loading="loading" v-if="!appStore.isMobile"> <div class="flex-h tools" v-loading="loading" v-if="!appStore.isMobile">
<div class="tool-icons" v-if="dashboardStore.editMode"> <div class="flex-h" v-if="dashboardStore.editMode">
<el-dropdown content="Controls" placement="bottom" :persistent="false"> <el-dropdown content="Controls" placement="bottom" :persistent="false">
<i> <div class="icon-btn">
<Icon class="icon-btn" size="sm" iconName="control" /> <Icon size="sm" iconName="control" />
</i> </div>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item @click="clickIcons(t)" v-for="(t, index) in toolIcons" :key="index" :title="t.content"> <el-dropdown-item @click="clickIcons(t)" v-for="(t, index) in toolIcons" :key="index" :title="t.content">
@ -120,9 +120,9 @@ limitations under the License. -->
</template> </template>
</el-dropdown> </el-dropdown>
<el-tooltip content="Apply" placement="bottom"> <el-tooltip content="Apply" placement="bottom">
<i @click="applyDashboard"> <div class="icon-btn" @click="applyDashboard">
<Icon class="icon-btn" size="sm" iconName="save" /> <Icon size="sm" iconName="save" />
</i> </div>
</el-tooltip> </el-tooltip>
</div> </div>
<div class="ml-5"> <div class="ml-5">
@ -693,10 +693,6 @@ limitations under the License. -->
padding: 4px 2px; padding: 4px 2px;
} }
.tool-icons {
margin-top: 2px;
}
.tools { .tools {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
@ -704,9 +700,10 @@ limitations under the License. -->
} }
.icon-btn { .icon-btn {
display: inline-block; width: 22px;
padding: 3px; height: 22px;
text-align: center; text-align: center;
line-height: 18px;
border: 1px solid var(--sw-icon-btn-border); border: 1px solid var(--sw-icon-btn-border);
border-radius: 3px; border-radius: 3px;
margin-left: 6px; margin-left: 6px;
@ -734,7 +731,7 @@ limitations under the License. -->
.hierarchy-btn { .hierarchy-btn {
display: inline-block; display: inline-block;
padding: 0 2px 2px; padding: 0 3px;
border: 1px solid #666; border: 1px solid #666;
border-radius: 4px; border-radius: 4px;
text-align: center; text-align: center;

View File

@ -0,0 +1,69 @@
<!-- 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="flex-h content">
<TaskList />
<div class="vis-graph ml-5">
<div class="mb-20">
<Filter />
</div>
<div class="stack" v-loading="asyncProfilingStore.loadingTree">
<EBPFStack :type="ComponentType" />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted } from "vue";
import { ElMessage } from "element-plus";
import { useAsyncProfilingStore } from "@/store/modules/async-profiling";
import { useSelectorStore } from "@/store/modules/selectors";
import TaskList from "./components/TaskList.vue";
import Filter from "./components/Filter.vue";
import EBPFStack from "@/views/dashboard/related/ebpf/components/EBPFStack.vue";
import { ComponentType } from "./components/data";
const asyncProfilingStore = useAsyncProfilingStore();
const selectorStore = useSelectorStore();
onMounted(async () => {
const resp = await asyncProfilingStore.getServiceInstances({ serviceId: selectorStore.currentService.id });
if (resp && resp.errors) {
ElMessage.error(resp.errors);
}
});
</script>
<style lang="scss" scoped>
.content {
height: calc(100% - 30px);
width: 100%;
}
.vis-graph {
height: 100%;
flex-grow: 2;
min-width: 700px;
overflow: hidden;
position: relative;
width: calc(100% - 330px);
}
.stack {
width: 100%;
overflow: auto;
height: calc(100% - 100px);
padding-bottom: 10px;
}
</style>

View File

@ -0,0 +1,50 @@
<!-- 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="flex-h header">
<div class="title">Async Profiling</div>
<el-button class="mr-20" size="small" type="primary" @click="() => (newTask = true)">
{{ t("newTask") }}
</el-button>
</div>
<el-dialog v-model="newTask" :destroy-on-close="true" fullscreen @closed="newTask = false">
<NewTask @close="newTask = false" />
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import NewTask from "./components/NewTask.vue";
const { t } = useI18n();
const newTask = ref<boolean>(false);
</script>
<style lang="scss" scoped>
.header {
padding: 10px;
font-size: $font-size-smaller;
border-bottom: 1px solid $border-color;
justify-content: space-between;
}
.name {
width: 270px;
}
.title {
font-weight: bold;
line-height: 24px;
}
</style>

View File

@ -0,0 +1,115 @@
<!-- 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="flex-h">
<Selector
class="filter-selector"
:multiple="true"
:value="serviceInstanceIds"
size="small"
:options="instances"
placeholder="Select instances"
@change="changeInstances"
/>
<Selector
class="filter-events"
:value="selectedEventType"
size="small"
:options="eventTypes"
placeholder="Select a event"
@change="changeEventType"
/>
<el-button type="primary" size="small" @click="analyzeProfiling">
{{ t("analyze") }}
</el-button>
</div>
</template>
<script lang="ts" setup>
import { computed, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
import { useAsyncProfilingStore } from "@/store/modules/async-profiling";
import type { Instance } from "@/types/selector";
import type { Option } from "@/types/app";
import { EventsMap, ProfilingEvents, JFREventType } from "./data";
const { t } = useI18n();
const asyncProfilingStore = useAsyncProfilingStore();
const serviceInstanceIds = ref<string[]>([]);
const selectedEventType = ref<string>("");
const eventTypes = computed(() =>
(asyncProfilingStore.selectedTask.events ?? [])
.map((d: string) => {
if (d === ProfilingEvents[1]) {
return [
{ label: JFREventType.OBJECT_ALLOCATION_IN_NEW_TLAB, value: JFREventType.OBJECT_ALLOCATION_IN_NEW_TLAB },
{ label: JFREventType.OBJECT_ALLOCATION_OUTSIDE_TLAB, value: JFREventType.OBJECT_ALLOCATION_OUTSIDE_TLAB },
];
}
return { label: d, value: d };
})
.flat(),
);
const instances = computed(() =>
asyncProfilingStore.instances.filter((d: Instance) =>
(asyncProfilingStore.selectedTask.successInstanceIds ?? []).includes(d.id),
),
);
function changeInstances(options: Option[]) {
serviceInstanceIds.value = options.map((d: Option) => d.value);
asyncProfilingStore.setAnalyzeTrees([]);
}
function changeEventType(options: Option[]) {
selectedEventType.value = options[0].value;
asyncProfilingStore.setAnalyzeTrees([]);
}
async function analyzeProfiling() {
const instanceIds = asyncProfilingStore.instances
.filter((d: Instance) => (serviceInstanceIds.value ?? []).includes(d.value))
.map((d: Instance) => d.id);
const res = await asyncProfilingStore.getAsyncProfilingAnalyze({
instanceIds,
taskId: asyncProfilingStore.selectedTask.id,
eventType: (EventsMap as any)[selectedEventType.value],
});
if (res.data && res.data.errors) {
ElMessage.error(res.data.errors);
return;
}
}
watch(
() => asyncProfilingStore.selectedTask.successInstanceIds,
() => {
serviceInstanceIds.value = [];
selectedEventType.value = "";
},
);
</script>
<style>
.filter-selector {
width: 400px;
margin-right: 10px;
}
.filter-events {
width: 300px;
margin-right: 10px;
}
</style>

View File

@ -0,0 +1,187 @@
<!-- 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="async-profile-task">
<div>
<div class="label">{{ t("instance") }}</div>
<Selector
class="profile-input"
:multiple="true"
:value="serviceInstanceIds"
size="small"
:options="asyncProfilingStore.instances"
placeholder="Select instances"
@change="changeInstances"
:filterable="false"
/>
</div>
<div>
<div class="label">{{ t("duration") }}</div>
<Radio class="mb-5" :value="duration" :options="DurationOptions" @change="changeDuration" />
</div>
<div>
<div class="label">{{ t("profilingEvents") }}</div>
<el-checkbox-group v-model="asyncEvents" class="profile-input mb-5">
<el-checkbox
v-for="event in ProfilingEvents"
:label="event"
:value="event"
:key="event"
:disabled="disableEvents(event)"
/>
</el-checkbox-group>
</div>
<div>
<div class="label">
<span class="mr-5 cp">{{ t("execArgs") }}</span>
<el-popover placement="right" :width="480" trigger="hover" title="Async profiler extension parameters">
<template #reference>
<span>
<Icon iconName="help" />
</span>
</template>
<div>
<p>
<span class="mr-10">live </span>
<span>- build allocation profile from live objects only</span>
</p>
<p>
<span class="mr-10">lock[=DURATION] </span>
<span>- profile contended locks overflowing the DURATION ns bucket (default: 10us)</span>
</p>
<p>
<span class="mr-10">alloc[=BYTES] </span>
<span>- profile allocations with BYTES interval</span>
</p>
<p>
<span class="mr-10">interval=N </span>
<span>- sampling interval in ns (default: 10'000'000, i.e. 10 ms)</span>
</p>
<p>
<span class="mr-10">jstackdepth=N </span>
<span>- maximum Java stack depth (default: 2048)</span>
</p>
<p>
<span class="mr-10">chunksize=N </span>
<span>- approximate size of JFR chunk in bytes (default: 100 MB)</span>
</p>
<p>
<span class="mr-10">chunktime=N </span>
<span>- duration of JFR chunk in seconds (default: 1 hour)</span>
</p>
</div>
</el-popover>
</div>
<el-input size="small" class="profile-input" v-model="execArgs" />
</div>
<div>
<el-button @click="createTask" type="primary" class="create-task-btn" :loading="loading">
{{ t("createTask") }}
</el-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useAsyncProfilingStore } from "@/store/modules/async-profiling";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
import { DurationOptions, ProfilingEvents } from "./data";
/* global defineEmits */
const emits = defineEmits(["close"]);
const asyncProfilingStore = useAsyncProfilingStore();
const selectorStore = useSelectorStore();
const { t } = useI18n();
const serviceInstanceIds = ref<string[]>([]);
const asyncEvents = ref<string[]>([]);
const duration = ref<string>(DurationOptions[0].value);
const execArgs = ref<string>("");
const loading = ref<boolean>(false);
const PartofEvents = [ProfilingEvents[3], ProfilingEvents[4], ProfilingEvents[5]];
function changeDuration(val: string) {
duration.value = val;
}
function changeInstances(options: { id: string }[]) {
serviceInstanceIds.value = options.map((d: { id: string }) => d.id);
}
function disableEvents(item: string) {
if (asyncEvents.value.includes(ProfilingEvents[0]) && PartofEvents.includes(item)) {
return true;
}
if (item === ProfilingEvents[0]) {
for (const event of PartofEvents) {
if (asyncEvents.value.includes(event)) {
return true;
}
}
}
return false;
}
async function createTask() {
const params = {
serviceId: selectorStore.currentService.id,
serviceInstanceIds: serviceInstanceIds.value,
duration: Number(duration.value) * 60,
events: asyncEvents.value,
execArgs: execArgs.value,
};
loading.value = true;
const res = await asyncProfilingStore.createTask(params);
loading.value = false;
if (res.errors) {
ElMessage.error(res.errors);
return;
}
const { errorReason } = res.data;
if (errorReason) {
ElMessage.error(errorReason);
return;
}
emits("close");
ElMessage.success("Task created successfully");
}
</script>
<style lang="scss" scoped>
.async-profile-task {
margin: 0 auto;
width: 600px;
}
.date {
font-size: $font-size-smaller;
}
.label {
margin-top: 10px;
font-size: $font-size-normal;
}
.profile-input {
width: 600px;
}
.create-task-btn {
width: 600px;
margin-top: 50px;
}
</style>

View File

@ -0,0 +1,259 @@
<!-- 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="profile-task-list flex-v" v-loading="asyncProfilingStore.loadingTasks">
<div class="profile-t-tool flex-h">{{ t("taskList") }}</div>
<div class="profile-t-wrapper">
<div class="no-data" v-show="!asyncProfilingStore.taskList.length">
{{ t("noData") }}
</div>
<table class="profile-t">
<tr
class="profile-tr cp"
v-for="(i, index) in asyncProfilingStore.taskList"
@click="changeTask(i)"
:key="index"
:class="{
selected: asyncProfilingStore.selectedTask.id === i.id,
}"
>
<td class="profile-td">
<div class="ell">
<span>{{ i.id }}</span>
<a class="profile-btn r" @click="() => (showDetail = true)">
<Icon iconName="view" size="middle" />
</a>
</div>
<div class="grey ell sm">
<span class="mr-10 sm">
{{ dateFormat(i.createTime) }}
</span>
<span class="mr-10 sm">
{{ dateFormat(i.createTime + i.duration * 1000) }}
</span>
</div>
</td>
</tr>
</table>
</div>
</div>
<el-dialog v-model="showDetail" :destroy-on-close="true" fullscreen @closed="showDetail = false">
<div class="profile-detail flex-v" v-if="asyncProfilingStore.selectedTask.id">
<div>
<h5 class="mb-10">{{ t("task") }}.</h5>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">ID:</span>
<span class="g-sm-8 wba">{{ asyncProfilingStore.selectedTask.id }}</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("service") }}:</span>
<span class="g-sm-8 wba">{{ service }}</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("execArgs") }}:</span>
<span class="g-sm-8 wba">{{ asyncProfilingStore.selectedTask.execArgs }}</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("duration") }}:</span>
<span class="g-sm-8 wba">{{ asyncProfilingStore.selectedTask.duration / 60 }}min</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("events") }}:</span>
<span class="g-sm-8 wba"> {{ asyncProfilingStore.selectedTask.events.join(", ") }} </span>
</div>
</div>
<div>
<h5
class="mb-5 mt-10"
v-show="asyncProfilingStore.selectedTask.logs && asyncProfilingStore.selectedTask.logs.length"
>
{{ t("logs") }}.
</h5>
<div v-for="(i, index) in Object.keys(instanceLogs)" :key="index">
<div class="sm">
<span class="mr-10 grey">{{ t("instance") }}:</span>
<span>{{ i }}</span>
</div>
<div v-for="(d, index) in instanceLogs[i]" :key="index">
<span class="mr-10 grey">{{ t("operationType") }}:</span>
<span class="mr-20">{{ d.operationType }}</span>
<span class="mr-10 grey">{{ t("time") }}:</span>
<span>{{ dateFormat(d.operationTime) }}</span>
</div>
</div>
</div>
<div>
<h5 class="mb-10 mt-10" v-show="errorInstances.length"> {{ t("errorInstances") }}</h5>
<div v-for="(instance, index) in errorInstances" :key="instance.value || index">
<div class="mb-10 sm">
<span class="mr-10 grey">{{ t("instance") }}:</span>
<span>{{ instance.label }}</span>
</div>
<div v-for="(d, index) in instance.attributes" :key="d.value + index">
<span class="mr-10 grey">{{ d.name }}:</span>
<span class="mr-20">{{ d.value }}</span>
</div>
</div>
</div>
<div>
<h5 class="mb-10 mt-10" v-show="successInstances.length"> {{ t("successInstances") }}</h5>
<div v-for="(instance, index) in successInstances" :key="instance.value || index">
<div class="mb-10 sm">
<span class="mr-10 grey">{{ t("instance") }}:</span>
<span>{{ instance.label }}</span>
</div>
<div v-for="(d, index) in instance.attributes" :key="d.value + index">
<span class="mr-10 grey">{{ d.name }}:</span>
<span class="mr-20">{{ d.value }}</span>
</div>
</div>
</div>
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, onMounted } from "vue";
import { useI18n } from "vue-i18n";
import { useSelectorStore } from "@/store/modules/selectors";
import { useAsyncProfilingStore } from "@/store/modules/async-profiling";
import type { TaskLog, TaskListItem } from "@/types/profile";
import { ElMessage } from "element-plus";
import { dateFormat } from "@/utils/dateFormat";
import type { Instance, Service } from "@/types/selector";
const { t } = useI18n();
const asyncProfilingStore = useAsyncProfilingStore();
const selectorStore = useSelectorStore();
const showDetail = ref<boolean>(false);
const service = ref<string>("");
const instanceLogs = ref<TaskLog | any>({});
const errorInstances = ref<Instance[]>([]);
const successInstances = ref<Instance[]>([]);
onMounted(() => {
fetchTasks();
});
async function fetchTasks() {
const res = await asyncProfilingStore.getTaskList();
if (res.errors) {
return ElMessage.error(res.errors);
}
if (res.data.errorReason) {
ElMessage.error(res.errors);
}
}
async function changeTask(item: TaskListItem) {
if (item.id !== asyncProfilingStore.selectedTask.id) {
asyncProfilingStore.setAnalyzeTrees([]);
asyncProfilingStore.setSelectedTask(item);
}
service.value = (selectorStore.services.filter((s: Service) => s.id === item.serviceId)[0] ?? {}).label;
const res = await asyncProfilingStore.getTaskLogs({ taskId: item.id });
if (res.errors) {
ElMessage.error(res.errors);
return;
}
item = {
...item,
...asyncProfilingStore.taskProgress,
};
asyncProfilingStore.setSelectedTask(item);
errorInstances.value = asyncProfilingStore.instances.filter(
(d: Instance) => d.id && item.errorInstanceIds.includes(d.id),
);
successInstances.value = asyncProfilingStore.instances.filter(
(d: Instance) => d.id && item.successInstanceIds.includes(d.id),
);
instanceLogs.value = {};
for (const d of item.logs) {
if (instanceLogs.value[d.instanceName]) {
instanceLogs.value[d.instanceName].push({
operationType: d.operationType,
operationTime: d.operationTime,
});
} else {
instanceLogs.value[d.instanceName] = [{ operationType: d.operationType, operationTime: d.operationTime }];
}
}
}
</script>
<style lang="scss" scoped>
.profile-task-list {
width: 300px;
height: calc(100% - 20px);
overflow: auto;
border-right: 1px solid var(--sw-trace-list-border);
}
.item span {
height: 21px;
}
.profile-td {
padding: 5px 10px;
border-bottom: 1px solid var(--sw-trace-list-border);
}
.selected {
background-color: var(--sw-list-selected);
}
.no-data {
text-align: center;
margin-top: 10px;
}
.profile-t-wrapper {
overflow: auto;
flex-grow: 1;
}
.profile-t {
width: 100%;
border-spacing: 0;
table-layout: fixed;
flex-grow: 1;
position: relative;
}
.profile-tr {
&:hover {
background-color: var(--sw-list-selected);
}
}
.profile-segment {
border-top: 1px solid var(--sw-trace-list-border);
}
.profile-t-tool {
padding: 5px 10px;
font-weight: bold;
border-right: 1px solid var(--sw-trace-list-border);
border-bottom: 1px solid var(--sw-trace-list-border);
background-color: var(--sw-table-header);
}
.profile-btn {
color: $font-color;
padding: 1px 3px;
border-radius: 2px;
font-size: $font-size-smaller;
float: right;
}
</style>

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