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

7508
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "skywalking-booster-ui",
"version": "9.4.0",
"version": "10.2.0",
"private": true,
"scripts": {
"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"
},
"dependencies": {
"axios": "^1.7.5",
"d3": "^7.3.0",
"d3-flame-graph": "^4.1.3",
"d3-tip": "^0.9.1",
"echarts": "^5.2.2",
"element-plus": "^2.2.5",
"lodash": "^4.17.21",
"element-plus": "^2.9.4",
"monaco-editor": "^0.34.1",
"pinia": "^2.0.28",
"vis-timeline": "^7.5.1",
"vue": "^3.2.45",
"vue-grid-layout": "^3.0.0-beta1",
"vue-i18n": "^9.1.9",
"vue-i18n": "^9.14.3",
"vue-router": "^4.1.6",
"vue-types": "^4.1.1"
},
@ -42,11 +40,10 @@
"@types/d3-tip": "^3.5.5",
"@types/echarts": "^4.9.12",
"@types/jsdom": "^20.0.1",
"@types/lodash": "^4.14.179",
"@types/node": "^18.11.12",
"@types/three": "^0.131.0",
"@vitejs/plugin-vue": "^4.0.0",
"@vitejs/plugin-vue-jsx": "^3.0.0",
"@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^4.1.1",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^11.0.0",
"@vue/test-utils": "^2.2.6",
@ -64,21 +61,21 @@
"postcss-html": "^1.3.0",
"postcss-scss": "^4.0.2",
"prettier": "^2.7.1",
"sass": "^1.56.1",
"sass": "^1.85.0",
"start-server-and-test": "^2.0.5",
"stylelint": "15.9.0",
"stylelint-config-html": "^1.0.0",
"stylelint-config-prettier": "9.0.4",
"stylelint-config-standard": "^33.0.0",
"stylelint-order": "^6.0.3",
"typescript": "~4.7.4",
"typescript": "^5.7.3",
"unplugin-auto-import": "^0.18.2",
"unplugin-vue-components": "^0.27.3",
"vite": "^4.5.3",
"vite": "^6.3.4",
"vite-plugin-monaco-editor": "^1.1.0",
"vite-plugin-svg-icons": "^2.0.1",
"vitest": "^0.25.6",
"vue-tsc": "^1.8.27"
"vitest": "^3.0.5",
"vue-tsc": "^2.2.2"
},
"browserslist": [
"> 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
limitations under the License. -->
<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 v-if="!available" class="no-data">No Data</div>
<div
@ -54,6 +61,7 @@ limitations under the License. -->
import Trace from "@/views/dashboard/related/trace/Index.vue";
import associateProcessor from "@/hooks/useAssociateProcessor";
import { WidgetType } from "@/views/dashboard/data";
import SelectorLegend from "./Legend.vue";
/*global Nullable, defineProps, defineEmits, Indexable*/
const emits = defineEmits(["select"]);
@ -84,6 +92,10 @@ limitations under the License. -->
type: Array as PropType<{ widgetId: string }[]>,
default: () => [],
},
legendSelector: {
type: Object as PropType<Indexable>,
default: () => ({ isConfigPage: false, isSelector: false }),
},
});
const available = computed(
() =>
@ -103,6 +115,7 @@ limitations under the License. -->
if (!instance) {
return;
}
instance.on("click", (params: EventParams) => {
currentParams.value = params;
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(
() => props.option,
(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"
:remote-method="remoteMethod"
:filterable="filterable"
:collapse-tags="collapseTags"
:collapse-tags-tooltip="collapseTagsTooltip"
>
<el-option
v-for="(item, index) in options"
@ -41,11 +43,6 @@ limitations under the License. -->
import { ref, watch } from "vue";
import type { PropType } from "vue";
// interface Option {
// label: string | number;
// value: string | number;
// }
/*global defineProps, defineEmits, Indexable*/
const emit = defineEmits(["change", "query"]);
const props = defineProps({
@ -73,6 +70,8 @@ limitations under the License. -->
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);

View File

@ -18,7 +18,7 @@ import type { App } from "vue";
import Icon from "./Icon.vue";
import TimePicker from "./TimePicker.vue";
import Selector from "./Selector.vue";
import Graph from "./Graph.vue";
import Graph from "./Graph/Graph.vue";
import Radio from "./Radio.vue";
import SelectSingle from "./SelectSingle.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
* limitations under the License.
*/
import type { AxiosResponse } from "axios";
import axios from "axios";
import { cancelToken } from "@/utils/cancelToken";
import { httpQuery } from "./base";
async function query(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const res: AxiosResponse = await axios.post(
"/graphql",
{ query: param.queryStr, variables: { ...param.conditions } },
{ cancelToken: cancelToken() },
);
if (res.data.errors) {
res.data.errors = res.data.errors.map((e: { message: string }) => e.message).join(" ");
async function fetchQuery(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const response = await httpQuery({
method: "post",
json: { query: param.queryStr, variables: { ...param.conditions } },
headers: {},
});
if (response.errors) {
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
startTime
scope
name
tags {
key
value
@ -43,6 +44,35 @@ export const Alarm = {
startTime
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
* 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 = {
query: `

View File

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

View File

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

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
* limitations under the License.
*/
import {
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}}`;
import { getAllTemplates, addTemplate, changeTemplate, deleteTemplate } from "../fragments/dashboard";
export const addNewTemplate = `mutation template(${addTemplate.variable}) {${addTemplate.query}}`;

View File

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

View File

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

View File

@ -14,9 +14,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { RespFields, MaximumEntities } from "./data";
import { RespFields, MaximumEntities, MaxQueryLength } from "./data";
import { EntityType, ExpressionResultType } from "@/views/dashboard/data";
import { ElMessage } from "element-plus";
import { useTopologyStore } from "@/store/modules/topology";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
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 { 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[]) {
function expressionsGraphql(config: Indexable, idx: number) {
if (!(config.metrics && config.metrics[0])) {
@ -100,12 +109,12 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
return { source: {}, tips: [], typesOfMQE: [] };
}
const tips: string[] = [];
const source: { [key: string]: unknown } = {};
const source: Indexable<unknown> = {};
const keys = Object.keys(resp.data);
const typesOfMQE: string[] = [];
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 results = obj.results || [];
const name = config.metrics[i];
@ -116,15 +125,15 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
if (!obj.error) {
if ([ExpressionResultType.SINGLE_VALUE, ExpressionResultType.TIME_SERIES_VALUES].includes(type)) {
for (const item of results) {
const label =
let label =
item.metric &&
item.metric.labels.map((d: { key: string; value: string }) => `${d.key}=${d.value}`).join(",");
const values = item.values.map((d: { value: unknown }) => d.value) || [];
if (results.length === 1) {
source[label || c.label || name] = values;
} else {
source[label] = values;
// If the metrics label does not exist, use the configuration label or expression
label = label ? `${metricConfig.label || name}, ${label}` : metricConfig.label || name;
}
source[label] = values;
}
}
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 variables: string[] = [`$duration: Duration!`];
let fragments = "";
let conditions: Recordable = {
let conditions: Recordable<unknown> = {
duration: appStore.durationTime,
};
for (let i = 0; i < configArr.length; i++) {
@ -181,13 +190,6 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
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 promiseArr = partArr.map((d: Array<Indexable>) => fetchMetrics(d));
@ -394,7 +396,7 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
const appStore = useAppStoreWithOut();
const dashboardStore = useDashboardStore();
function getExpressionQuery() {
function getExpressionQuery(partMetrics?: string[]) {
const conditions: { [key: string]: unknown } = {
duration: appStore.durationTime,
};
@ -448,7 +450,7 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
};
variables.push(`$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) {
variables.push(`$expression${idx}: String!`);
conditions[`expression${idx}`] = name;
@ -462,19 +464,19 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
return { queryStr, conditions };
}
function handleExpressionValues(resp: { [key: string]: any }) {
const obj: any = {};
function handleExpressionValues(partMetrics: string[], resp: { [key: string]: any }) {
const obj: Indexable = {};
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;
if (metrics[index]) {
if (!obj[metrics[index]]) {
obj[metrics[index]] = {
if (partMetrics[index]) {
if (!obj[partMetrics[index]]) {
obj[partMetrics[index]] = {
values: [],
};
}
obj[metrics[index]].values.push({
value: resp[k].results[0] && resp[k].results[0].values[0].value,
obj[partMetrics[index]].values.push({
value: resp[k] && resp[k].results[0] && resp[k].results[0].values[0].value,
id: instances[idx].id,
});
}
@ -482,6 +484,31 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
}
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) {
return false;
}
if (legend && legend.asTable) {
if (legend && (legend.asTable || legend.asSelector)) {
return false;
}
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" />
</span>
<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>
<span>
<Icon iconName="info_outline" size="middle" />

View File

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

View File

@ -139,7 +139,6 @@ const msg = {
enableAssociate: "Enable association",
text: "Text",
query: "Query",
endpointTips: "The table shows up to 20 pieces of endpoints.",
viewTrace: "View Related Traces",
relatedTraceOptions: "Related Trace Options",
setLatencyDuration: "Latency Related Metrics",
@ -154,6 +153,7 @@ const msg = {
legendOptions: "Legend Options",
showLegend: "Show Legend",
asTable: "As Table",
asSelector: "As Selector",
toTheRight: "To The Right",
legendValues: "Legend Values",
minDuration: "Minimal Request Duration",
@ -386,5 +386,16 @@ const msg = {
tabExpressions: "Tab Expressions",
hierarchyNodeMetrics: "Metrics 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;

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",
enableAssociate: "Activar asociación",
query: "Consulta",
endpointTips: "Aquí, la tabla muestra hasta 20 punto final.",
queryOrder: "Consulta por duración",
setOrder: "Orden de consulta",
latency: "Retraso",
@ -386,5 +385,17 @@ const msg = {
tabExpressions: "Tab Expressions",
hierarchyNodeMetrics: "Metrics 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;

View File

@ -128,11 +128,20 @@ const titles = {
self_observability_java_agent: "SkyWalking Java Agent",
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.",
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_desc:
"Cilium is a CNI plugin for Kubernetes that provides eBPF-based networking, security, and load balancing.",
cilium_service: "Cilium Service",
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;

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.",
self_observability_java_agent: "SkyWalking Java Agent",
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_desc:
"Cilium es un complemento CNI para Kubernetes que proporciona redes, seguridad y equilibrio de carga basados en eBPF.",
cilium_service: "Cilium Service",
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;

View File

@ -113,10 +113,17 @@ const titles = {
"Satellite为云原生基础设施设计的开源代理提供了一种低成本、高效、更安全的遥测数据收集方式。它是遥测采集的推荐负载均衡器。",
self_observability_java_agent: "SkyWalking Java 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_desc: "Cilium是Kubernetes上的CNI插件提供基于eBPF的网络、安全和负载均衡。",
cilium_service: "Cilium服务",
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;

View File

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

View File

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

View File

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

View File

@ -37,16 +37,8 @@ const router = createRouter({
routes,
});
(window as any).axiosCancel = [];
router.beforeEach((to, from, next) => {
router.beforeEach((to, _, next) => {
// 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 === "/") {
let defaultPath = "";

View File

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

View File

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

View File

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

View File

@ -19,7 +19,6 @@ import { store } from "@/store";
import graphql from "@/graphql";
import type { Duration, DurationTime } from "@/types/app";
import getLocalTime from "@/utils/localtime";
import type { AxiosResponse } from "axios";
import dateFormatStep, { dateFormatTime } from "@/utils/dateFormat";
import { TimeType } from "@/constants/data";
import type { MenuOptions, SubItem } from "@/types/app";
@ -118,12 +117,6 @@ export const appStore = defineStore({
actions: {
setDuration(data: Duration): void {
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();
},
updateDurationRow(data: Duration) {
@ -185,11 +178,11 @@ export const appStore = defineStore({
});
},
async queryOAPTimeInfo() {
const res: AxiosResponse = await graphql.query("queryOAPTimeInfo").params({});
if (res.data.errors) {
const res = await graphql.query("queryOAPTimeInfo").params({});
if (res.errors) {
this.utc = -(new Date().getTimezoneOffset() / 60) + ":0";
} else {
this.utc = res.data.data.getTimeInfo.timezone / 100 + ":0";
this.utc = res.data.getTimeInfo.timezone / 100 + ":0";
}
const utcArr = this.utc.split(":");
this.utcHour = isNaN(Number(utcArr[0])) ? 0 : Number(utcArr[0]);
@ -197,21 +190,21 @@ export const appStore = defineStore({
return res.data;
},
async fetchVersion(): Promise<void> {
const res: AxiosResponse = await graphql.query("queryOAPVersion").params({});
if (res.data.errors) {
return res.data;
async fetchVersion() {
const res = await graphql.query("queryOAPVersion").params({});
if (res.errors) {
return res;
}
this.version = res.data.data.version;
this.version = res.data.version;
return res.data;
},
async queryMenuItems() {
const res: AxiosResponse = await graphql.query("queryMenuItems").params({});
if (res.data.errors) {
return res.data;
const res = await graphql.query("queryMenuItems").params({});
if (res.errors) {
return res;
}
return res.data.data;
return res.data;
},
setReloadTimer(timer: IntervalHandle) {
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 graphql from "@/graphql";
import type { MonitorInstance, MonitorProcess } from "@/types/continous-profiling";
import type { AxiosResponse } from "axios";
import { dateFormat } from "@/utils/dateFormat";
interface ContinousProfilingState {
@ -84,37 +83,37 @@ export const continousProfilingStore = defineStore({
checkItems: CheckItems[];
}[],
) {
const res: AxiosResponse = await graphql.query("editStrategy").params({
const response = await graphql.query("editStrategy").params({
request: {
serviceId,
targets,
},
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
return res.data;
return response;
},
async getStrategyList(params: { serviceId: string }) {
if (!params.serviceId) {
return new Promise((resolve) => resolve({}));
}
this.policyLoading = true;
const res: AxiosResponse = await graphql.query("getStrategyList").params(params);
const response = await graphql.query("getStrategyList").params(params);
this.policyLoading = false;
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
const list = res.data.data.strategyList || [];
const list = response.data.strategyList || [];
if (!list.length) {
this.taskList = [];
this.instances = [];
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) => {
return {
...d,
@ -123,25 +122,25 @@ export const continousProfilingStore = defineStore({
});
this.setSelectedStrategy(this.strategyList[0]);
if (!this.selectedStrategy.type) {
return res.data;
return response;
}
this.getMonitoringInstances(params.serviceId);
return res.data;
return response;
},
async getMonitoringInstances(serviceId: string): Promise<Nullable<AxiosResponse>> {
async getMonitoringInstances(serviceId: string) {
this.instancesLoading = true;
if (!serviceId) {
return null;
}
const res: AxiosResponse = await graphql.query("getMonitoringInstances").params({
const response = await graphql.query("getMonitoringInstances").params({
serviceId,
target: this.selectedStrategy.type,
});
this.instancesLoading = false;
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.instances = (res.data.data.instances || [])
this.instances = (response.data.instances || [])
.map((d: MonitorInstance) => {
const processes = (d.processes || [])
.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);
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 type { LayoutConfig } from "@/types/dashboard";
import graphql from "@/graphql";
import query from "@/graphql/fetch";
import fetchQuery from "@/graphql/fetch";
import type { DashboardItem } from "@/types/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { NewControl, TextConfig, TimeRangeConfig, ControlsTypes } from "../data";
import type { AxiosResponse } from "axios";
import { ElMessage } from "element-plus";
import { EntityType, WidgetType } from "@/views/dashboard/data";
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 } }) {
const res: AxiosResponse = await query(param);
return res.data;
return await fetchQuery(param);
},
async fetchTemplates() {
const res: AxiosResponse = await graphql.query("getTemplates").params({});
const res = await graphql.query("getTemplates").params({});
if (res.data.errors) {
return res.data;
if (res.errors) {
return res;
}
const data = res.data.data.getAllTemplates;
const data = res.data.getAllTemplates;
let list = [];
for (const t of data) {
const c = JSON.parse(t.configuration);
const key = [c.layer, c.entity, c.name].join("_");
list.push({
...c,
id: t.id,
@ -372,20 +354,20 @@ export const dashboardStore = defineStore({
this.dashboards = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
},
async updateDashboard(setting: { id: string; configuration: string }) {
const res: AxiosResponse = await graphql.query("updateTemplate").params({
const resp = await graphql.query("updateTemplate").params({
setting,
});
if (res.data.errors) {
ElMessage.error(res.data.errors);
return res.data;
if (resp.errors) {
ElMessage.error(resp.errors);
return resp;
}
const json = res.data.data.changeTemplate;
const json = resp.data.changeTemplate;
if (!json.status) {
ElMessage.error(json.message);
return res.data;
return resp;
}
ElMessage.success("Saved successfully");
return res.data;
return resp;
},
async saveDashboard() {
if (!this.currentDashboard?.name) {
@ -419,14 +401,14 @@ export const dashboardStore = defineStore({
}
res = await graphql.query("addNewTemplate").params({ setting: { configuration: JSON.stringify(c) } });
json = res.data.data.addTemplate;
json = res.data.addTemplate;
if (!json.status) {
ElMessage.error(json.message);
}
}
if (res.data.errors || res.errors) {
ElMessage.error(res.data.errors);
return res.data;
if (res.errors) {
ElMessage.error(res.errors);
return res;
}
if (!json.status) {
return json;
@ -448,16 +430,16 @@ export const dashboardStore = defineStore({
return json;
},
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) {
ElMessage.error(res.data.errors);
return res.data;
if (res.errors) {
ElMessage.error(res.errors);
return res;
}
const json = res.data.data.disableTemplate;
const json = res.data.disableTemplate;
if (!json.status) {
ElMessage.error(json.message);
return res.data;
return res;
}
this.dashboards = this.dashboards.filter((d: Recordable) => d.id !== this.currentDashboard?.id);
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 { store } from "@/store";
import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import type { Conditions, Log } from "@/types/demand-log";
@ -60,16 +59,16 @@ export const demandLogStore = defineStore({
},
async getInstances(id: string) {
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,
duration: useAppStoreWithOut().durationTime,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.instances = res.data.data.pods || [];
return res.data;
this.instances = response.data.pods || [];
return response;
},
async getContainers(serviceInstanceId: string) {
if (!serviceInstanceId) {
@ -78,35 +77,35 @@ export const demandLogStore = defineStore({
const condition = {
serviceInstanceId,
};
const res: AxiosResponse = await graphql.query("fetchContainers").params({ condition });
const response = await graphql.query("fetchContainers").params({ condition });
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
if (res.data.data.containers.errorReason) {
if (response.data.containers.errorReason) {
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 res.data;
return response;
},
async getDemandLogs() {
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;
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
if (res.data.data.logs.errorReason) {
this.setLogs([], res.data.data.logs.errorReason);
return res.data;
if (response.data.logs.errorReason) {
this.setLogs([], response.data.logs.errorReason);
return response;
}
this.total = res.data.data.logs.logs.length;
const logs = res.data.data.logs.logs.map((d: Log) => d.content).join("\n");
this.total = response.data.logs.logs.length;
const logs = response.data.logs.logs.map((d: Log) => d.content).join("\n");
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 { store } from "@/store";
import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { EBPFProfilingTriggerType } from "../data";
interface EbpfState {
taskList: Array<Recordable<EBPFTaskList>>;
@ -57,70 +56,70 @@ export const ebpfStore = defineStore({
this.analyzeTrees = tree;
},
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) {
return res.data;
if (response.errors) {
return response;
}
const json = res.data.data.createTaskData;
const json = response.data.createTaskData;
this.couldProfiling = json.couldProfiling || false;
this.labels = json.processLabels.map((d: string) => {
return { label: d, value: d };
});
return res.data;
return response;
},
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) {
return res.data;
if (response.errors) {
return response;
}
this.getTaskList({
serviceId: param.serviceId,
targets: ["ON_CPU", "OFF_CPU"],
triggerType: EBPFProfilingTriggerType.FIXED_TIME,
});
return res.data;
return response;
},
async getTaskList(params: { serviceId: string; targets: string[] }) {
if (!params.serviceId) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql.query("getEBPFTasks").params(params);
const response = await graphql.query("getEBPFTasks").params(params);
this.ebpfTips = "";
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.taskList = res.data.data.queryEBPFTasks || [];
this.taskList = response.data.queryEBPFTasks || [];
this.selectedTask = this.taskList[0] || {};
this.setSelectedTask(this.selectedTask);
if (!this.taskList.length) {
return res.data;
return response;
}
this.getEBPFSchedules({ taskId: String(this.taskList[0].taskId) });
return res.data;
return response;
},
async getEBPFSchedules(params: { taskId: string }) {
if (!params.taskId) {
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 = [];
return res.data;
return response;
}
this.ebpfTips = "";
const { eBPFSchedules } = res.data.data;
const { eBPFSchedules } = response.data;
this.eBPFSchedules = eBPFSchedules;
if (!eBPFSchedules.length) {
this.eBPFSchedules = [];
this.analyzeTrees = [];
}
return res.data;
return response;
},
async getEBPFAnalyze(params: {
scheduleIdList: string[];
@ -134,24 +133,24 @@ export const ebpfStore = defineStore({
if (!params.timeRanges.length) {
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 = [];
return res.data;
return response;
}
const { analysisEBPFResult } = res.data.data;
const { analysisEBPFResult } = response.data;
this.ebpfTips = analysisEBPFResult.tip;
if (!analysisEBPFResult) {
this.analyzeTrees = [];
return res.data;
return response;
}
if (analysisEBPFResult.tip) {
this.analyzeTrees = [];
return res.data;
return response;
}
this.analyzeTrees = analysisEBPFResult.trees;
return res.data;
return response;
},
},
});

View File

@ -17,11 +17,11 @@
import { defineStore } from "pinia";
import { store } from "@/store";
import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import type { Event, QueryEventCondition } from "@/types/events";
import type { Instance, Endpoint } from "@/types/selector";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { EndpointsTopNDefault } from "../data";
interface eventState {
loading: boolean;
@ -46,47 +46,48 @@ export const eventStore = defineStore({
},
async getInstances() {
const serviceId = useSelectorStore().currentService ? useSelectorStore().currentService.id : "";
const res: AxiosResponse = await graphql.query("queryInstances").params({
const response = await graphql.query("queryInstances").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.instances = [{ value: "", label: "All" }, ...res.data.data.pods] || [{ value: "", label: "All" }];
return res.data;
this.instances = [{ value: "", label: "All" }, ...response.data.pods];
return response;
},
async getEndpoints(keyword: string) {
const serviceId = useSelectorStore().currentService ? useSelectorStore().currentService.id : "";
if (!serviceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
const response = await graphql.query("queryEndpoints").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
keyword: keyword || "",
limit: EndpointsTopNDefault,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.endpoints = [{ value: "", label: "All" }, ...res.data.data.pods] || [{ value: "", label: "All" }];
return res.data;
this.endpoints = [{ value: "", label: "All" }, ...response.data.pods];
return response;
},
async getEvents() {
this.loading = true;
const res: AxiosResponse = await graphql.query("queryEvents").params({
const response = await graphql.query("queryEvents").params({
condition: {
...this.condition,
time: useAppStoreWithOut().durationTime,
},
});
this.loading = false;
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
if (res.data.data.fetchEvents) {
this.events = (res.data.data.fetchEvents.events || []).map((item: Event) => {
if (response.data.fetchEvents) {
this.events = (response.data.fetchEvents.events || []).map((item: Event) => {
let scope = "Service";
if (item.source.serviceInstance) {
scope = "ServiceInstance";
@ -101,7 +102,7 @@ export const eventStore = defineStore({
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 { store } from "@/store";
import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard";
import { EndpointsTopNDefault } from "../data";
interface LogState {
services: Service[];
@ -61,50 +61,51 @@ export const logStore = defineStore({
};
},
async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({
const response = await graphql.query("queryServices").params({
layer,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.services = res.data.data.services;
return res.data;
this.services = response.data.services;
return response;
},
async getInstances(id: string) {
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,
duration: useAppStoreWithOut().durationTime,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.instances = [{ value: "0", label: "All" }, ...res.data.data.pods] || [{ value: " 0", label: "All" }];
return res.data;
this.instances = [{ value: "0", label: "All" }, ...response.data.pods];
return response;
},
async getEndpoints(id: string, keyword?: string) {
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,
duration: useAppStoreWithOut().durationTime,
keyword: keyword || "",
limit: EndpointsTopNDefault,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.endpoints = [{ value: "0", label: "All" }, ...res.data.data.pods] || [{ value: "0", label: "All" }];
return res.data;
this.endpoints = [{ value: "0", label: "All" }, ...response.data.pods];
return response;
},
async getLogsByKeywords() {
const res: AxiosResponse = await graphql.query("queryLogsByKeywords").params({});
const response = await graphql.query("queryLogsByKeywords").params({});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.supportQueryLogsByKeywords = res.data.data.support;
return res.data;
this.supportQueryLogsByKeywords = response.data.support;
return response;
},
async getLogs() {
const dashboardStore = useDashboardStore();
@ -115,39 +116,31 @@ export const logStore = defineStore({
},
async getServiceLogs() {
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;
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.logs = res.data.data.queryLogs.logs;
return res.data;
this.logs = response.data.queryLogs.logs;
return response;
},
async getBrowserLogs() {
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;
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.logs = res.data.data.queryBrowserErrorLogs.logs;
return res.data;
this.logs = response.data.queryBrowserErrorLogs.logs;
return response;
},
async getLogTagKeys() {
const res: AxiosResponse = await graphql
.query("queryLogTagKeys")
.params({ duration: useAppStoreWithOut().durationTime });
return res.data;
return await graphql.query("queryLogTagKeys").params({ duration: useAppStoreWithOut().durationTime });
},
async getLogTagValues(tagKey: string) {
const res: AxiosResponse = await graphql
.query("queryLogTagValues")
.params({ tagKey, duration: useAppStoreWithOut().durationTime });
return res.data;
return await graphql.query("queryLogTagValues").params({ tagKey, duration: useAppStoreWithOut().durationTime });
},
},
});

View File

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

View File

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

View File

@ -18,8 +18,8 @@ import { defineStore } from "pinia";
import type { Service, Instance, Endpoint, Process } from "@/types/selector";
import { store } from "@/store";
import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
import { EndpointsTopNDefault } from "../data";
interface SelectorState {
services: Service[];
destServices: Service[];
@ -76,62 +76,55 @@ export const selectorStore = defineStore({
setDestProcesses(processes: Array<Process>) {
this.destProcesses = processes;
},
async fetchLayers(): Promise<AxiosResponse> {
const res: AxiosResponse = await graphql.query("queryLayers").params({});
return res.data || {};
async fetchLayers() {
return await graphql.query("queryLayers").params({});
},
async fetchServices(layer: string): Promise<AxiosResponse> {
const res: AxiosResponse = await graphql.query("queryServices").params({ layer });
async fetchServices(layer: string) {
const res = await graphql.query("queryServices").params({ layer });
if (!res.data.errors) {
this.services = res.data.data.services || [];
this.destServices = res.data.data.services || [];
if (!res.errors) {
this.services = res.data.services || [];
this.destServices = res.data.services || [];
}
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;
if (!serviceId) {
return null;
}
const res: AxiosResponse = await graphql.query("queryInstances").params({
const resp = await graphql.query("queryInstances").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
});
if (!res.data.errors) {
if (!resp.errors) {
if (param && param.isRelation) {
this.destPods = res.data.data.pods || [];
return res.data;
this.destPods = resp.data.pods || [];
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;
if (!instanceId) {
return null;
}
const res: AxiosResponse = await graphql.query("queryProcesses").params({
const res = await graphql.query("queryProcesses").params({
instanceId,
duration: useAppStoreWithOut().durationTime,
});
if (!res.data.errors) {
if (!res.errors) {
if (param && param.isRelation) {
this.destProcesses = res.data.data.processes || [];
return res.data;
this.destProcesses = res.data.processes || [];
return res;
}
this.processes = res.data.data.processes || [];
this.processes = res.data.processes || [];
}
return res.data;
return res;
},
async getEndpoints(params: {
keyword?: string;
serviceId?: string;
isRelation?: boolean;
limit?: number;
}): Promise<Nullable<AxiosResponse>> {
async getEndpoints(params: { keyword?: string; serviceId?: string; isRelation?: boolean; limit?: number }) {
if (!params) {
params = {};
}
@ -139,96 +132,96 @@ export const selectorStore = defineStore({
if (!serviceId) {
return null;
}
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
const res = await graphql.query("queryEndpoints").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
keyword: params.keyword || "",
limit: params.limit,
limit: params.limit || EndpointsTopNDefault,
});
if (!res.data.errors) {
if (!res.errors) {
if (params.isRelation) {
this.destPods = res.data.data.pods || [];
return res.data;
this.destPods = res.data.pods || [];
return res;
}
this.pods = res.data.data.pods || [];
this.pods = res.data.pods || [];
}
return res.data;
return res;
},
async getService(serviceId: string, isRelation: boolean) {
if (!serviceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryService").params({
const res = await graphql.query("queryService").params({
serviceId,
});
if (!res.data.errors) {
if (!res.errors) {
if (isRelation) {
this.setCurrentDestService(res.data.data.service);
this.destServices = [res.data.data.service];
return res.data;
this.setCurrentDestService(res.data.service);
this.destServices = [res.data.service];
return res;
}
this.setCurrentService(res.data.data.service);
this.services = [res.data.data.service];
this.setCurrentService(res.data.service);
this.services = [res.data.service];
}
return res.data;
return res;
},
async getInstance(instanceId: string, isRelation?: boolean) {
if (!instanceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryInstance").params({
const res = await graphql.query("queryInstance").params({
instanceId,
});
if (!res.data.errors) {
if (!res.errors) {
if (isRelation) {
this.currentDestPod = res.data.data.instance || null;
this.destPods = [res.data.data.instance];
return res.data;
this.currentDestPod = res.data.instance || null;
this.destPods = [res.data.instance];
return res;
}
this.currentPod = res.data.data.instance || null;
this.pods = [res.data.data.instance];
this.currentPod = res.data.instance || null;
this.pods = [res.data.instance];
}
return res.data;
return res;
},
async getEndpoint(endpointId: string, isRelation?: string) {
if (!endpointId) {
return;
}
const res: AxiosResponse = await graphql.query("queryEndpoint").params({
const res = await graphql.query("queryEndpoint").params({
endpointId,
});
if (res.data.errors) {
return res.data;
if (res.errors) {
return res;
}
if (isRelation) {
this.currentDestPod = res.data.data.endpoint || null;
this.destPods = [res.data.data.endpoint];
return res.data;
this.currentDestPod = res.data.endpoint || null;
this.destPods = [res.data.endpoint];
return res;
}
this.currentPod = res.data.data.endpoint || null;
this.pods = [res.data.data.endpoint];
return res.data;
this.currentPod = res.data.endpoint || null;
this.pods = [res.data.endpoint];
return res;
},
async getProcess(processId: string, isRelation?: boolean) {
if (!processId) {
return;
}
const res: AxiosResponse = await graphql.query("queryProcess").params({
const res = await graphql.query("queryProcess").params({
processId,
});
if (!res.data.errors) {
if (!res.errors) {
if (isRelation) {
this.currentDestProcess = res.data.data.process || null;
this.destProcesses = [res.data.data.process];
this.currentDestProcess = res.data.process || null;
this.destProcesses = [res.data.process];
return res.data;
}
this.currentProcess = res.data.data.process || null;
this.processes = [res.data.data.process];
this.currentProcess = res.data.process || null;
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 { store } from "@/store";
import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
import type { EBPFTaskList } from "@/types/ebpf";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
@ -57,20 +56,18 @@ export const taskTimelineStore = defineStore({
return new Promise((resolve) => resolve({}));
}
this.loading = true;
const res: AxiosResponse = await graphql.query("getEBPFTasks").params(params);
const response = await graphql.query("getEBPFTasks").params(params);
this.loading = false;
this.errorTip = "";
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
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,
);
// this.selectedTask = this.taskList[0] || {};
// await this.getGraphData();
return res.data;
return response;
},
async getGraphData() {
let res: any = {};

View File

@ -21,10 +21,8 @@ import graphql from "@/graphql";
import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import type { AxiosResponse } from "axios";
import query from "@/graphql/fetch";
import fetchQuery from "@/graphql/fetch";
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
import { ElMessage } from "element-plus";
interface MetricVal {
[key: string]: { values: { id: string; value: unknown }[] };
@ -306,14 +304,14 @@ export const topologyStore = defineStore({
return new Promise((resolve) => resolve({}));
}
const duration = useAppStoreWithOut().durationTime;
const res: AxiosResponse = await graphql.query("getServicesTopology").params({
const res = await graphql.query("getServicesTopology").params({
serviceIds,
duration,
});
if (res.data.errors) {
return res.data;
if (res.errors) {
return res;
}
return res.data.data.topology;
return res.data.topology;
},
async getInstanceTopology() {
const { currentService, currentDestService } = useSelectorStore();
@ -323,15 +321,15 @@ export const topologyStore = defineStore({
if (!(serverServiceId && clientServiceId)) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql.query("getInstanceTopology").params({
const res = await graphql.query("getInstanceTopology").params({
clientServiceId,
serverServiceId,
duration,
});
if (!res.data.errors) {
this.setInstanceTopology(res.data.data.topology);
if (!res.errors) {
this.setInstanceTopology(res.data.topology);
}
return res.data;
return res;
},
async updateEndpointTopology(endpointIds: string[], depth: number) {
if (!endpointIds.length) {
@ -339,7 +337,10 @@ export const topologyStore = defineStore({
}
const res = await this.getEndpointTopology(endpointIds);
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) {
this.setTopology(res);
return;
@ -347,8 +348,8 @@ export const topologyStore = defineStore({
const json = await this.getEndpointTopology(ids);
if (depth > 2) {
const pods = json.nodes
.map((item: Node) => item.id)
.filter((d: string) => ![...ids, ...endpointIds].includes(d));
.filter((d: Node) => ![...ids, ...endpointIds].includes(d.id) && d.name !== userNodeName)
.map((item: Node) => item.id);
if (!pods.length) {
const nodes = [...res.nodes, ...json.nodes];
const calls = [...res.calls, ...json.calls];
@ -358,8 +359,8 @@ export const topologyStore = defineStore({
const topo = await this.getEndpointTopology(pods);
if (depth > 3) {
const endpoints = topo.nodes
.map((item: Node) => item.id)
.filter((d: string) => ![...ids, ...pods, ...endpointIds].includes(d));
.filter((d: Node) => ![...ids, ...pods, ...endpointIds].includes(d.id) && d.name !== userNodeName)
.map((item: Node) => item.id);
if (!endpoints.length) {
const nodes = [...res.nodes, ...json.nodes, ...topo.nodes];
const calls = [...res.calls, ...json.calls, ...topo.calls];
@ -369,8 +370,11 @@ export const topologyStore = defineStore({
const data = await this.getEndpointTopology(endpoints);
if (depth > 4) {
const nodeIds = data.nodes
.map((item: Node) => item.id)
.filter((d: string) => ![...endpoints, ...ids, ...pods, ...endpointIds].includes(d));
.filter(
(d: Node) =>
![...endpoints, ...ids, ...pods, ...endpointIds].includes(d.id) && d.name !== userNodeName,
)
.map((item: Node) => item.id);
if (!nodeIds.length) {
const nodes = [...res.nodes, ...json.nodes, ...topo.nodes, ...data.nodes];
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 conditions = { duration };
const res: AxiosResponse = await query({ queryStr, conditions });
const res = await fetchQuery({ queryStr, conditions });
if (res.data.errors) {
return res.data;
if (res.errors) {
return res;
}
const topo = res.data.data;
const topo = res.data;
const calls = [] as Call[];
const nodes = [] as Node[];
for (const key of Object.keys(topo)) {
@ -443,14 +447,14 @@ export const topologyStore = defineStore({
return { calls, nodes };
},
async getNodeExpressionValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const res: AxiosResponse = await query(param);
async getTopologyExpressionValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const res = await fetchQuery(param);
if (res.data.errors) {
return res.data;
if (res.errors) {
return res;
}
return res.data;
return res;
},
async getLinkExpressions(expressions: string[], type: string) {
if (!expressions.length) {
@ -461,14 +465,8 @@ export const topologyStore = defineStore({
if (!calls.length) {
return;
}
const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor(expressions, calls);
const param = getExpressionQuery();
const res = await this.getNodeExpressionValue(param);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
const metrics = handleExpressionValues(res.data);
const { getMetrics } = useQueryTopologyExpressionsProcessor(expressions, calls);
const metrics = await getMetrics();
if (type === "SERVER") {
this.setLinkServerMetrics(metrics);
} else {
@ -484,17 +482,11 @@ export const topologyStore = defineStore({
this.setNodeMetricValue({});
return;
}
const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor(
const { getMetrics } = useQueryTopologyExpressionsProcessor(
expressions,
this.nodes.filter((d: Node) => d.isReal),
);
const param = getExpressionQuery();
const res = await this.getNodeExpressionValue(param);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
const metrics = handleExpressionValues(res.data);
const metrics = await getMetrics();
this.setNodeMetricValue(metrics);
},
async getHierarchyServiceTopology() {
@ -510,22 +502,20 @@ export const topologyStore = defineStore({
if (!(id && layer)) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql
.query("getHierarchyServiceTopology")
.params({ serviceId: id, layer: layer });
if (res.data.errors) {
return res.data;
const res = await graphql.query("getHierarchyServiceTopology").params({ serviceId: id, layer: layer });
if (res.errors) {
return res;
}
const resp = await this.getListLayerLevels();
if (resp.errors) {
return resp;
}
const levels = resp.data.levels || [];
this.setHierarchyServiceTopology(res.data.data.hierarchyServiceTopology || {}, levels);
return res.data;
const levels = resp.levels || [];
this.setHierarchyServiceTopology(res.data.hierarchyServiceTopology || {}, levels);
return res;
},
async getListLayerLevels() {
const res: AxiosResponse = await graphql.query("queryListLayerLevels").params({});
const res = await graphql.query("queryListLayerLevels").params({});
return res.data;
},
@ -536,30 +526,19 @@ export const topologyStore = defineStore({
if (!(currentPod && dashboardStore.layerId)) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql
const res = await graphql
.query("getHierarchyInstanceTopology")
.params({ instanceId: currentPod.id, layer: dashboardStore.layerId });
if (res.data.errors) {
return res.data;
if (res.errors) {
return res;
}
const resp = await this.getListLayerLevels();
if (resp.errors) {
return resp;
}
const levels = resp.data.levels || [];
this.setHierarchyInstanceTopology(res.data.data.hierarchyInstanceTopology || {}, levels);
return res.data;
},
async queryHierarchyExpressions(expressions: string[], nodes: Node[]) {
const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor(expressions, nodes);
const param = getExpressionQuery();
const res = await this.getNodeExpressionValue(param);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
const metrics = handleExpressionValues(res.data);
return metrics;
const levels = resp.levels || [];
this.setHierarchyInstanceTopology(res.data.hierarchyInstanceTopology || {}, levels);
return res;
},
async queryHierarchyNodeExpressions(expressions: string[], layer: string) {
const nodes = this.hierarchyServiceNodes.filter((n: HierarchyNode) => n.layer === layer);
@ -571,7 +550,8 @@ export const topologyStore = defineStore({
this.setHierarchyNodeMetricValue({}, layer);
return;
}
const metrics = await this.queryHierarchyExpressions(expressions, nodes);
const { getMetrics } = useQueryTopologyExpressionsProcessor(expressions, nodes);
const metrics = await getMetrics();
this.setHierarchyNodeMetricValue(metrics, layer);
},
async queryHierarchyInstanceNodeExpressions(expressions: string[], layer: string) {
@ -585,7 +565,8 @@ export const topologyStore = defineStore({
this.setHierarchyInstanceNodeMetricValue({}, layer);
return;
}
const metrics = await this.queryHierarchyExpressions(expressions, nodes);
const { getMetrics } = useQueryTopologyExpressionsProcessor(expressions, nodes);
const metrics = await getMetrics();
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 { store } from "@/store";
import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { QueryOrders } from "@/views/dashboard/data";
import { EndpointsTopNDefault } from "../data";
interface TraceState {
services: Service[];
instances: Instance[];
@ -33,6 +33,7 @@ interface TraceState {
conditions: Recordable;
traceSpanLogs: Recordable[];
selectorStore: Recordable;
selectedSpan: Recordable<Span>;
}
export const traceStore = defineStore({
@ -44,6 +45,7 @@ export const traceStore = defineStore({
traceList: [],
traceSpans: [],
currentTrace: {},
selectedSpan: {},
conditions: {
queryDuration: useAppStoreWithOut().durationTime,
traceState: "ALL",
@ -63,6 +65,9 @@ export const traceStore = defineStore({
setTraceSpans(spans: Span[]) {
this.traceSpans = spans;
},
setSelectedSpan(span: Span) {
this.selectedSpan = span;
},
resetState() {
this.traceSpans = [];
this.traceList = [];
@ -75,124 +80,115 @@ export const traceStore = defineStore({
};
},
async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({
const response = await graphql.query("queryServices").params({
layer,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.services = res.data.data.services;
return res.data;
this.services = response.data.services;
return response;
},
async getService(serviceId: string) {
if (!serviceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryService").params({
const response = await graphql.query("queryService").params({
serviceId,
});
return res.data;
return response;
},
async getInstance(instanceId: string) {
if (!instanceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryInstance").params({
const response = await graphql.query("queryInstance").params({
instanceId,
});
return res.data;
return response;
},
async getEndpoint(endpointId: string) {
if (!endpointId) {
return;
}
const res: AxiosResponse = await graphql.query("queryEndpoint").params({
return await graphql.query("queryEndpoint").params({
endpointId,
});
return res.data;
},
async getInstances(id: string) {
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,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.instances = [{ value: "0", label: "All" }, ...res.data.data.pods];
return res.data;
this.instances = [{ value: "0", label: "All" }, ...response.data.pods];
return response;
},
async getEndpoints(id: string, keyword?: string) {
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,
duration: useAppStoreWithOut().durationTime,
keyword: keyword || "",
limit: EndpointsTopNDefault,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.endpoints = [{ value: "0", label: "All" }, ...res.data.data.pods];
return res.data;
this.endpoints = [{ value: "0", label: "All" }, ...response.data.pods];
return response;
},
async getTraces() {
const res: AxiosResponse = await graphql.query("queryTraces").params({ condition: this.conditions });
if (res.data.errors) {
return res.data;
const response = await graphql.query("queryTraces").params({ condition: this.conditions });
if (response.errors) {
return response;
}
if (!res.data.data.data.traces.length) {
if (!response.data.data.traces.length) {
this.traceList = [];
this.setCurrentTrace({});
this.setTraceSpans([]);
return res.data;
return response;
}
this.getTraceSpans({ traceId: res.data.data.data.traces[0].traceIds[0] });
this.traceList = res.data.data.data.traces.map((d: Trace) => {
this.getTraceSpans({ traceId: response.data.data.traces[0].traceIds[0] });
this.traceList = response.data.data.traces.map((d: Trace) => {
d.traceIds = d.traceIds.map((id: string) => {
return { value: id, label: id };
});
return d;
});
this.setCurrentTrace(res.data.data.data.traces[0] || {});
return res.data;
this.setCurrentTrace(response.data.data.traces[0] || {});
return response;
},
async getTraceSpans(params: { traceId: string }) {
const res: AxiosResponse = await graphql.query("queryTrace").params(params);
if (res.data.errors) {
return res.data;
const response = await graphql.query("queryTrace").params(params);
if (response.errors) {
return response;
}
const data = res.data.data.trace.spans;
const data = response.data.trace.spans;
this.setTraceSpans(data || []);
return res.data;
return response;
},
async getSpanLogs(params: Recordable) {
const res: AxiosResponse = await graphql.query("queryServiceLogs").params(params);
if (res.data.errors) {
const response = await graphql.query("queryServiceLogs").params(params);
if (response.errors) {
this.traceSpanLogs = [];
return res.data;
return response;
}
this.traceSpanLogs = res.data.data.queryLogs.logs || [];
return res.data;
this.traceSpanLogs = response.data.queryLogs.logs || [];
return response;
},
async getTagKeys() {
const res: AxiosResponse = await graphql
.query("queryTraceTagKeys")
.params({ duration: useAppStoreWithOut().durationTime });
return res.data;
return await graphql.query("queryTraceTagKeys").params({ duration: useAppStoreWithOut().durationTime });
},
async getTagValues(tagKey: string) {
const res: AxiosResponse = await graphql
.query("queryTraceTagValues")
.params({ tagKey, duration: useAppStoreWithOut().durationTime });
return res.data;
return await graphql.query("queryTraceTagValues").params({ tagKey, duration: useAppStoreWithOut().durationTime });
},
},
});

View File

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

View File

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

View File

@ -27,6 +27,7 @@ export interface Alarm {
scope: string;
tags: Array<{ key: string; value: string }>;
events: Event[];
snapshot: Indexable;
}
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']
ElButton: typeof import('element-plus/es')['ElButton']
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']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
@ -42,8 +44,9 @@ declare module 'vue' {
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTag: typeof import('element-plus/es')['ElTag']
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']
Legend: typeof import('./../components/Graph/Legend.vue')['default']
Radio: typeof import('./../components/Radio.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']

View File

@ -46,6 +46,7 @@ export interface LayoutConfig {
relatedTrace?: RelatedTrace;
subExpressions?: string[];
subTypesOfMQE?: string[];
valueRelatedDashboard?: string;
}
export type RelatedTrace = {
duration: DurationTime;
@ -110,6 +111,7 @@ export interface LineConfig extends AreaConfig {
showYAxis?: boolean;
smallTips?: boolean;
showlabels?: boolean;
noTooltips?: boolean;
}
export interface AreaConfig {
@ -123,6 +125,7 @@ export interface CardConfig {
fontSize?: number;
showUnit?: boolean;
textAlign?: "center" | "right" | "left";
valueMappings?: { [key: string]: string };
}
export interface TextConfig {
@ -194,4 +197,19 @@ export type LegendOptions = {
asTable: boolean;
toTheRight: boolean;
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;
maxSamplingCount: number;
logs: TaskLog[];
errorInstanceIds: string[];
successInstanceIds: string[];
serviceInstanceIds: string[];
}
export interface SegmentSpan {
spanId: string;

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

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@
class Vec2 extends Float32Array {
constructor(v?: unknown, y?: unknown) {
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[1] = v[1];
} else if (typeof v === "number" && typeof y === "number") {
@ -104,7 +104,7 @@ class Vec2 extends Float32Array {
}
norm(out?: number[] | Vec2): number[] | Vec2 | undefined {
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[0] = this[0] / mag;
out[1] = this[1] / mag;

View File

@ -17,7 +17,7 @@
class Vec3 extends Float32Array {
constructor(v?: unknown, y?: unknown, z?: unknown) {
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[1] = v[1];
this[2] = v[2];
@ -150,7 +150,7 @@ class Vec3 extends Float32Array {
}
static norm(x: unknown, y: unknown, z: unknown): 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);
} else if (typeof x === "number" && typeof y === "number" && typeof z === "number") {
rtn.xyz(x, y, z);

View File

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

View File

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

View File

@ -38,7 +38,7 @@ limitations under the License. -->
:total="total"
@current-change="changePage"
:pager-count="5"
small
size="small"
:style="
appStore.theme === Themes.Light
? `--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",
value: "eventDetail",
},
{
label: "expression",
value: "expression",
},
{
label: "snapshot",
value: "snapshot",
},
];
export const EventsDetailKeys = [

View File

@ -612,7 +612,7 @@ limitations under the License. -->
}
function searchDashboards(pageIndex: number) {
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;
dashboards.value = arr.filter(

View File

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

View File

@ -126,7 +126,7 @@ limitations under the License. -->
opt.auto = Number(f.value) * 60 * 60 * 1000;
}
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);

View File

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

View File

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

View File

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

View File

@ -22,6 +22,15 @@ limitations under the License. -->
@change="updateLegendConfig({ show: legend.show })"
/>
</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>
<span class="label">{{ t("asTable") }}</span>
<el-switch
@ -97,6 +106,7 @@ limitations under the License. -->
max: false,
mean: false,
asTable: false,
asSelector: false,
toTheRight: false,
width: 130,
...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 }}
</span>
</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>
<script lang="ts" setup>
import { reactive, ref, computed } from "vue";
@ -140,21 +154,25 @@ limitations under the License. -->
metricTypes: string[];
metricTypeList: Option[][];
isList: boolean;
isTopList: boolean;
dashboardName: string;
dashboardList: ((DashboardItem & { label: string; value: string }) | any)[];
tips: string[];
subTips: string[];
valueRelatedDashboard: string;
}>({
metrics: metrics.value.length ? metrics.value : [""],
metricTypes: typesOfMQE.value.length ? typesOfMQE.value : [""],
metricTypeList: [],
isList: false,
isTopList: false,
dashboardName: graph.value.dashboardName,
dashboardList: [{ label: "", value: "" }],
tips: [],
subTips: [],
subMetrics: subMetrics.value.length ? subMetrics.value : [""],
subMetricTypes: subMetricTypes.value.length ? subMetricTypes.value : [""],
valueRelatedDashboard: dashboardStore.selectedGrid.valueRelatedDashboard,
});
const currentMetricConfig = ref<MetricConfigOpt>({
unit: "",
@ -163,6 +181,7 @@ limitations under the License. -->
sortOrder: "DES",
});
states.isTopList = graph.value.type === ChartTypes[4].value;
states.isList = ListChartTypes.includes(graph.value.type);
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) => {
if (d.layer === dashboardStore.layerId) {
if (
(d.entity === EntityType[0].value && chart === "ServiceList") ||
(d.entity === EntityType[2].value && chart === "EndpointList") ||
(d.entity === EntityType[3].value && chart === "InstanceList")
(d.entity === EntityType[0].value && chart === ChartTypes[8].value) ||
(d.entity === EntityType[2].value && chart === ChartTypes[9].value) ||
(d.entity === EntityType[3].value && chart === ChartTypes[10].value) ||
states.isTopList
) {
prev.push({
...d,
@ -254,9 +274,28 @@ limitations under the License. -->
typesOfMQE: states.metricTypes,
});
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]) {
states.dashboardName = "";
} 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;
width: 2px;
height: 100%;
background-color: #e8e8e8;
background-color: var(--sw-trace-line);
cursor: ew-resize;
&:hover {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,7 +22,7 @@ limitations under the License. -->
justifyContent: config.textAlign || 'center',
}"
>
{{ singleVal }}
{{ getValue() }}
<span class="unit" v-show="config.showUnit && unit">
{{ decodeURIComponent(unit) }}
</span>
@ -34,6 +34,7 @@ limitations under the License. -->
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import type { CardConfig, MetricConfigOpt } from "@/types/dashboard";
import { ElMessage } from "element-plus";
/*global defineProps */
const props = defineProps({
@ -48,16 +49,36 @@ limitations under the License. -->
showUnit: true,
textAlign: "center",
metricConfig: [],
valueMappings: {},
}),
},
});
const { t } = useI18n();
const metricConfig = computed(() => props.config.metricConfig || []);
const valueMappings = computed(() => props.config.valueMappings || {});
const key = computed(() => Object.keys(props.data)[0]);
const singleVal = computed(() =>
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 || ""));
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>
<style lang="scss" scoped>
.chart-card {

View File

@ -16,16 +16,18 @@ limitations under the License. -->
<div class="table">
<div class="search">
<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>
<el-button @click="searchList">
<Icon size="middle" iconName="search" />
</el-button>
</template>
</el-input>
<span class="ml-5 tips">{{ t("endpointTips") }}</span>
</div>
<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">
<template #default="scope">
<span class="link" @click="clickEndpoint(scope)" :style="{ fontSize: `${config.fontSize}px` }">
@ -46,13 +48,22 @@ limitations under the License. -->
/>
</el-table>
</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>
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
import { useI18n } from "vue-i18n";
import type { PropType } from "vue";
import type { EndpointListConfig } from "@/types/dashboard";
import type { Endpoint } from "@/types/selector";
@ -90,16 +101,26 @@ limitations under the License. -->
});
const emit = defineEmits(["expressionTips"]);
const { t } = useI18n();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
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 colMetrics = ref<string[]>([]);
const colSubMetrics = ref<string[]>([]);
const metricConfig = ref<MetricConfigOpt[]>(props.config.metricConfig || []);
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) {
queryEndpoints();
@ -108,6 +129,7 @@ limitations under the License. -->
chartLoading.value = true;
const resp = await selectorStore.getEndpoints({
keyword: searchText.value,
limit: topN.value,
});
chartLoading.value = false;
@ -116,7 +138,8 @@ limitations under the License. -->
return;
}
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[]) {
if (!arr.length) {
@ -142,7 +165,7 @@ limitations under the License. -->
{ metricConfig: metricConfig.value || [], expressions, subExpressions },
EntityType[2].value,
);
endpoints.value = params.data;
currentEndpoints.value = params.data;
colMetrics.value = params.names;
colSubMetrics.value = params.subNames;
metricConfig.value = params.metricConfigArr;
@ -151,7 +174,7 @@ limitations under the License. -->
return;
}
endpoints.value = currentPods;
currentEndpoints.value = currentPods;
colMetrics.value = [];
colSubMetrics.value = [];
metricConfig.value = [];
@ -175,6 +198,16 @@ limitations under the License. -->
async function searchList() {
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(
() => [
...(props.config.metricConfig || []),

View File

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

View File

@ -37,7 +37,7 @@ limitations under the License. -->
>{{ k.split("=")[1] }}</div
>
<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>
@ -47,6 +47,8 @@ limitations under the License. -->
import { computed } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
/*global defineProps */
const props = defineProps({
data: {
@ -58,12 +60,14 @@ limitations under the License. -->
showTableValues: boolean;
tableHeaderCol2: string;
typesOfMQE: string[];
valueMappings: {};
}>,
default: () => ({ showTableValues: true }),
},
});
const { t } = useI18n();
const valueMappings = computed<{ [key: string]: string }>(() => props.config.valueMappings || {});
const nameWidth = computed(() => (props.config.showTableValues ? 80 : 100));
const dataKeys = computed(() => {
const keys = Object.keys(props.data || {}).filter(
@ -73,6 +77,25 @@ limitations under the License. -->
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>
<style lang="scss" scoped>
.chart-table {

View File

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

View File

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

View File

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

View File

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