mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-09-20 21:29:28 +00:00
Compare commits
59 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0d2bedf529 | ||
![]() |
b525f84fa0 | ||
![]() |
1fe58f5f6c | ||
![]() |
012ae1db6c | ||
![]() |
79ec865ee7 | ||
![]() |
9ab8ac44bc | ||
![]() |
7a690e6704 | ||
![]() |
65607a5540 | ||
![]() |
5bb4218bfe | ||
![]() |
2b6f3ecaa8 | ||
![]() |
2246a9a045 | ||
![]() |
9318d32b0b | ||
![]() |
8ea50c8680 | ||
![]() |
55b3867bea | ||
![]() |
c33d6c4180 | ||
![]() |
70ea9fd06f | ||
![]() |
2fca7a79a2 | ||
![]() |
f5cfb030a3 | ||
![]() |
fbeeca8d9a | ||
![]() |
ea0f5e5f62 | ||
![]() |
8771ce4a19 | ||
![]() |
99a2461734 | ||
![]() |
fb0817eed8 | ||
![]() |
e164d87209 | ||
![]() |
5c92a46569 | ||
![]() |
aff69c057f | ||
![]() |
7338cec6b4 | ||
![]() |
536df8c052 | ||
![]() |
64d4a2b59b | ||
![]() |
6e1a6cf19b | ||
![]() |
aeddb39637 | ||
![]() |
14fa5d65b6 | ||
![]() |
224e761d70 | ||
![]() |
4c60f69aef | ||
![]() |
0007e3e3ae | ||
![]() |
b6522f4555 | ||
![]() |
bddbe40974 | ||
![]() |
61449f4b17 | ||
![]() |
a92365efcf | ||
![]() |
d65c18bd38 | ||
![]() |
a5b0acda06 | ||
![]() |
e251626374 | ||
![]() |
ed0ec0ac1f | ||
![]() |
1945f23419 | ||
![]() |
d10f4ca0cc | ||
![]() |
a5073dd3d4 | ||
![]() |
ddcc49cb42 | ||
![]() |
ae63538baf | ||
![]() |
d9f819d143 | ||
![]() |
3c8b316b76 | ||
![]() |
6b2b6a5dd2 | ||
![]() |
4e00073ec2 | ||
![]() |
8f179f00a2 | ||
![]() |
fe6e853c57 | ||
![]() |
f664e786ac | ||
![]() |
b6f57aa54e | ||
![]() |
afb70a371b | ||
![]() |
c35bdce399 | ||
![]() |
5419a69700 |
2
.github/workflows/nodejs.yml
vendored
2
.github/workflows/nodejs.yml
vendored
@@ -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 }}
|
||||
|
@@ -17,35 +17,10 @@
|
||||
|
||||
module.exports = {
|
||||
ignores: [(commit) => commit.includes("init")],
|
||||
extends: ["@commitlint/config-conventional"],
|
||||
rules: {
|
||||
"body-leading-blank": [2, "always"],
|
||||
"footer-leading-blank": [1, "always"],
|
||||
"header-max-length": [2, "always", 108],
|
||||
"subject-empty": [2, "never"],
|
||||
"type-empty": [2, "never"],
|
||||
"subject-case": [0],
|
||||
"type-enum": [
|
||||
2,
|
||||
"always",
|
||||
[
|
||||
"feat",
|
||||
"fix",
|
||||
"perf",
|
||||
"style",
|
||||
"docs",
|
||||
"test",
|
||||
"refactor",
|
||||
"build",
|
||||
"ci",
|
||||
"chore",
|
||||
"revert",
|
||||
"wip",
|
||||
"workflow",
|
||||
"types",
|
||||
"release",
|
||||
"merge",
|
||||
],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
10138
package-lock.json
generated
10138
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
35
package.json
35
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "skywalking-booster-ui",
|
||||
"version": "9.4.0",
|
||||
"version": "10.2.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -18,19 +18,18 @@
|
||||
"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.6.0",
|
||||
"axios": "^1.8.2",
|
||||
"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 +41,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 +62,21 @@
|
||||
"postcss-html": "^1.3.0",
|
||||
"postcss-scss": "^4.0.2",
|
||||
"prettier": "^2.7.1",
|
||||
"sass": "^1.56.1",
|
||||
"start-server-and-test": "^1.15.2",
|
||||
"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",
|
||||
"unplugin-auto-import": "^0.7.0",
|
||||
"unplugin-vue-components": "^0.19.2",
|
||||
"vite": "^4.5.3",
|
||||
"typescript": "^5.7.3",
|
||||
"unplugin-auto-import": "^0.18.2",
|
||||
"unplugin-vue-components": "^0.27.3",
|
||||
"vite": "^6.1.0",
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vitest": "^0.25.6",
|
||||
"vue-tsc": "^1.0.12"
|
||||
"vitest": "^3.0.5",
|
||||
"vue-tsc": "^2.2.2"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
@@ -98,10 +96,7 @@
|
||||
"{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": [
|
||||
"prettier --write"
|
||||
],
|
||||
"package.json": [
|
||||
"prettier --write"
|
||||
],
|
||||
"*.md": [
|
||||
"package.json, *.md": [
|
||||
"prettier --write"
|
||||
]
|
||||
}
|
||||
|
18
src/assets/icons/async_profiling.svg
Normal file
18
src/assets/icons/async_profiling.svg
Normal 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 |
16
src/assets/icons/cilium.svg
Normal file
16
src/assets/icons/cilium.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License. -->
|
||||
|
||||
<svg width="16" height="16" viewBox="20 0 70 72" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="m49.72 45.923-5.505-9.69 5.505-9.69h10.974l5.506 9.69-5.506 9.69H49.72ZM49.72 69.367l-5.505-9.69 5.505-9.689h10.974l5.506 9.69-5.506 9.69H49.72ZM49.72 22.477l-5.505-9.689 5.505-9.69h10.974l5.506 9.69-5.506 9.69H49.72ZM70.06 57.644l-5.506-9.69 5.506-9.69h10.974l5.506 9.69-5.506 9.69H70.06ZM70.06 34.2l-5.506-9.69 5.506-9.69h10.974l5.506 9.69-5.506 9.69H70.06ZM29.357 57.644l-5.506-9.69 5.506-9.69h10.974l5.506 9.69-5.506 9.69H29.357ZM29.357 34.2l-5.506-9.69 5.506-9.69h10.974l5.506 9.69-5.506 9.69H29.357Z" stroke="#141A1F" stroke-width="2.771"/><path d="M10.784 95.947c1.026.007 " fill="#141A1F"/></svg>
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src/assets/img/technologies/SOLONMVC.png
Normal file
BIN
src/assets/img/technologies/SOLONMVC.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
BIN
src/assets/img/tools/CILIUM_SERVICE.png
Normal file
BIN
src/assets/img/tools/CILIUM_SERVICE.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
@@ -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) => {
|
74
src/components/Graph/Legend.vue
Normal file
74
src/components/Graph/Legend.vue
Normal 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>
|
107
src/components/Graph/Selector.vue
Normal file
107
src/components/Graph/Selector.vue
Normal 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>
|
@@ -26,10 +26,12 @@ limitations under the License. -->
|
||||
:reserve-keyword="isRemote"
|
||||
:remote-method="remoteMethod"
|
||||
:filterable="filterable"
|
||||
:collapse-tags="collapseTags"
|
||||
:collapse-tags-tooltip="collapseTagsTooltip"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value || ''"
|
||||
v-for="(item, index) in options"
|
||||
:key="`${item.value}${index}`"
|
||||
:label="item.label || ''"
|
||||
:value="item.value || ''"
|
||||
:disabled="item.disabled || false"
|
||||
@@ -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);
|
||||
|
@@ -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";
|
||||
|
@@ -24,6 +24,7 @@ export const Alarm = {
|
||||
message
|
||||
startTime
|
||||
scope
|
||||
name
|
||||
tags {
|
||||
key
|
||||
value
|
||||
@@ -43,6 +44,46 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
};
|
||||
export const AlarmTagKeys = {
|
||||
variable: "$duration: Duration!",
|
||||
query: `
|
||||
tagKeys: queryAlarmTagAutocompleteKeys(duration: $duration)`,
|
||||
};
|
||||
|
||||
export const AlarmTagValues = {
|
||||
variable: "$tagKey: String!, $duration: Duration!",
|
||||
query: `
|
||||
tagValues: queryAlarmTagAutocompleteValues(tagKey: $tagKey, duration: $duration)`,
|
||||
};
|
||||
|
80
src/graphql/fragments/async-profile.ts
Normal file
80
src/graphql/fragments/async-profile.ts
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
@@ -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
|
||||
|
@@ -28,6 +28,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,6 +42,7 @@ const query: { [key: string]: string } = {
|
||||
...event,
|
||||
...ebpf,
|
||||
...demandLog,
|
||||
...asyncProfile,
|
||||
};
|
||||
class Graphql {
|
||||
private queryData = "";
|
||||
|
@@ -15,6 +15,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Alarm } from "../fragments/alarm";
|
||||
import { Alarm, AlarmTagKeys, AlarmTagValues } from "../fragments/alarm";
|
||||
|
||||
export const queryAlarms = `query queryAlarms(${Alarm.variable}) {${Alarm.query}}`;
|
||||
export const queryAlarmTagValues = `query queryTagValues(${AlarmTagValues.variable}) {${AlarmTagValues.query}}`;
|
||||
export const queryAlarmTagKeys = `query queryTagKeys(${AlarmTagKeys.variable}) {${AlarmTagKeys.query}}`;
|
||||
|
31
src/graphql/query/async-profile.ts
Normal file
31
src/graphql/query/async-profile.ts
Normal 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}}`;
|
@@ -14,6 +14,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export const MaximumEntities = 20;
|
||||
|
||||
export enum sizeEnum {
|
||||
XS = "XS",
|
||||
@@ -56,6 +57,16 @@ export const RespFields: Indexable = {
|
||||
name: id
|
||||
value
|
||||
refId: traceID
|
||||
owner {
|
||||
scope
|
||||
serviceID
|
||||
serviceName
|
||||
normal
|
||||
serviceInstanceID
|
||||
serviceInstanceName
|
||||
endpointID
|
||||
endpointName
|
||||
}
|
||||
}
|
||||
}
|
||||
error
|
||||
@@ -101,3 +112,5 @@ export const LightChartColors = [
|
||||
"#546570",
|
||||
"#c4ccd3",
|
||||
];
|
||||
|
||||
export const MaxQueryLength = 120;
|
||||
|
@@ -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) {
|
||||
|
@@ -14,9 +14,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { RespFields } 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,31 +25,35 @@ import type { MetricConfigOpt } from "@/types/dashboard";
|
||||
import type { Instance, Endpoint, Service } from "@/types/selector";
|
||||
import type { Node, Call } from "@/types/topology";
|
||||
|
||||
export async function useExpressionsQueryProcessor(config: Indexable) {
|
||||
function expressionsGraphqlPods() {
|
||||
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])) {
|
||||
return;
|
||||
}
|
||||
const appStore = useAppStoreWithOut();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const selectorStore = useSelectorStore();
|
||||
|
||||
if (!selectorStore.currentService && dashboardStore.entity !== "All") {
|
||||
return;
|
||||
}
|
||||
const conditions: Recordable = {
|
||||
duration: appStore.durationTime,
|
||||
};
|
||||
const variables: string[] = [`$duration: Duration!`];
|
||||
const conditions: Recordable = {};
|
||||
const variables: string[] = [];
|
||||
const isRelation = ["ServiceRelation", "ServiceInstanceRelation", "EndpointRelation", "ProcessRelation"].includes(
|
||||
dashboardStore.entity,
|
||||
);
|
||||
if (isRelation && !selectorStore.currentDestService) {
|
||||
return;
|
||||
}
|
||||
const fragment = config.metrics.map((name: string, index: number) => {
|
||||
variables.push(`$expression${index}: String!`, `$entity${index}: Entity!`);
|
||||
conditions[`expression${index}`] = name;
|
||||
if (idx === 0) {
|
||||
variables.push(`$entity: Entity!`);
|
||||
const entity = {
|
||||
serviceName: dashboardStore.entity === "All" ? undefined : selectorStore.currentService.value,
|
||||
normal: dashboardStore.entity === "All" ? undefined : selectorStore.currentService.normal,
|
||||
@@ -76,19 +81,21 @@ export async function useExpressionsQueryProcessor(config: Indexable) {
|
||||
? selectorStore.currentDestProcess && selectorStore.currentDestProcess.value
|
||||
: undefined,
|
||||
};
|
||||
conditions[`entity${index}`] = entity;
|
||||
conditions[`entity`] = entity;
|
||||
}
|
||||
const fragment = config.metrics.map((name: string, index: number) => {
|
||||
variables.push(`$expression${idx}${index}: String!`);
|
||||
conditions[`expression${idx}${index}`] = name;
|
||||
|
||||
return `expression${index}: execExpression(expression: $expression${index}, entity: $entity${index}, duration: $duration)${RespFields.execExpression}`;
|
||||
return `expression${idx}${index}: execExpression(expression: $expression${idx}${index}, entity: $entity, duration: $duration)${RespFields.execExpression}`;
|
||||
});
|
||||
const queryStr = `query queryData(${variables}) {${fragment}}`;
|
||||
|
||||
return {
|
||||
queryStr,
|
||||
variables,
|
||||
fragment,
|
||||
conditions,
|
||||
};
|
||||
}
|
||||
|
||||
function expressionsSource(resp: { errors: string; data: Indexable }) {
|
||||
function expressionsSource(config: Indexable, resp: { errors: string; data: Indexable | any }) {
|
||||
if (resp.errors) {
|
||||
ElMessage.error(resp.errors);
|
||||
return { source: {}, tips: [], typesOfMQE: [] };
|
||||
@@ -97,6 +104,10 @@ export async function useExpressionsQueryProcessor(config: Indexable) {
|
||||
ElMessage.error("The query is wrong");
|
||||
return { source: {}, tips: [], typesOfMQE: [] };
|
||||
}
|
||||
if (resp.data.error) {
|
||||
ElMessage.error(resp.data.error);
|
||||
return { source: {}, tips: [], typesOfMQE: [] };
|
||||
}
|
||||
const tips: string[] = [];
|
||||
const source: { [key: string]: unknown } = {};
|
||||
const keys = Object.keys(resp.data);
|
||||
@@ -133,30 +144,69 @@ export async function useExpressionsQueryProcessor(config: Indexable) {
|
||||
|
||||
return { source, tips, typesOfMQE };
|
||||
}
|
||||
async function fetchMetrics(configArr: any) {
|
||||
const appStore = useAppStoreWithOut();
|
||||
const variables: string[] = [`$duration: Duration!`];
|
||||
let fragments = "";
|
||||
let conditions: Recordable = {
|
||||
duration: appStore.durationTime,
|
||||
};
|
||||
for (let i = 0; i < configArr.length; i++) {
|
||||
const params = await expressionsGraphql(configArr[i], i);
|
||||
if (params) {
|
||||
fragments += params?.fragment;
|
||||
conditions = { ...conditions, ...params.conditions };
|
||||
variables.push(...params.variables);
|
||||
}
|
||||
}
|
||||
if (!fragments) {
|
||||
return { 0: { source: {}, tips: [], typesOfMQE: [] } };
|
||||
}
|
||||
const queryStr = `query queryData(${variables}) {${fragments}}`;
|
||||
const dashboardStore = useDashboardStore();
|
||||
const json = await dashboardStore.fetchMetricValue({
|
||||
queryStr,
|
||||
conditions,
|
||||
});
|
||||
if (json.errors) {
|
||||
ElMessage.error(json.errors);
|
||||
return { 0: { source: {}, tips: [], typesOfMQE: [] } };
|
||||
}
|
||||
try {
|
||||
const pageData: Recordable = {};
|
||||
|
||||
const params = await expressionsGraphqlPods();
|
||||
if (!params) {
|
||||
return { source: {}, tips: [], typesOfMQE: [] };
|
||||
for (let i = 0; i < configArr.length; i++) {
|
||||
const resp: any = {};
|
||||
for (let m = 0; m < configArr[i].metrics.length; m++) {
|
||||
resp[`expression${i}${m}`] = json.data[`expression${i}${m}`];
|
||||
}
|
||||
const data = expressionsSource(configArr[i], { ...json, data: resp });
|
||||
const id = configArr[i].id;
|
||||
pageData[id] = data;
|
||||
}
|
||||
return pageData;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return { 0: { source: {}, tips: [], typesOfMQE: [] } };
|
||||
}
|
||||
}
|
||||
|
||||
const dashboardStore = useDashboardStore();
|
||||
const json = await dashboardStore.fetchMetricValue(params);
|
||||
if (json.errors) {
|
||||
ElMessage.error(json.errors);
|
||||
return { source: {}, tips: [], typesOfMQE: [] };
|
||||
const partArr = chunkArray(configList, 6);
|
||||
const promiseArr = partArr.map((d: Array<Indexable>) => fetchMetrics(d));
|
||||
const responseList = await Promise.all(promiseArr);
|
||||
let resp = {};
|
||||
for (const item of responseList) {
|
||||
resp = {
|
||||
...resp,
|
||||
...item,
|
||||
};
|
||||
}
|
||||
try {
|
||||
const data = expressionsSource(json);
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return { source: {}, tips: [], typesOfMQE: [] };
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
export async function useExpressionsQueryPodsMetrics(
|
||||
pods: Array<(Instance | Endpoint | Service) & Indexable>,
|
||||
allPods: Array<(Instance | Endpoint | Service) & Indexable>,
|
||||
config: {
|
||||
expressions: string[];
|
||||
subExpressions: string[];
|
||||
@@ -164,7 +214,7 @@ export async function useExpressionsQueryPodsMetrics(
|
||||
},
|
||||
scope: string,
|
||||
) {
|
||||
function expressionsGraphqlPods() {
|
||||
function expressionsGraphqlPods(pods: Array<(Instance | Endpoint | Service) & Indexable>) {
|
||||
const metrics: string[] = [];
|
||||
const subMetrics: string[] = [];
|
||||
config.expressions = config.expressions || [];
|
||||
@@ -196,18 +246,22 @@ export async function useExpressionsQueryPodsMetrics(
|
||||
variables.push(`$entity${index}: Entity!`);
|
||||
conditions[`entity${index}`] = entity;
|
||||
const f = metrics.map((name: string, idx: number) => {
|
||||
variables.push(`$expression${index}${idx}: String!`);
|
||||
conditions[`expression${index}${idx}`] = name;
|
||||
if (index === 0) {
|
||||
variables.push(`$expression${idx}: String!`);
|
||||
conditions[`expression${idx}`] = name;
|
||||
}
|
||||
let str = "";
|
||||
if (config.subExpressions[idx]) {
|
||||
variables.push(`$subExpression${index}${idx}: String!`);
|
||||
conditions[`subExpression${index}${idx}`] = config.subExpressions[idx];
|
||||
str = `subexpression${index}${idx}: execExpression(expression: $subExpression${index}${idx}, entity: $entity${index}, duration: $duration)${RespFields.execExpression}`;
|
||||
if (index === 0) {
|
||||
variables.push(`$subExpression${idx}: String!`);
|
||||
conditions[`subExpression${idx}`] = config.subExpressions[idx];
|
||||
}
|
||||
str = `subexpression${index}${idx}: execExpression(expression: $subExpression${idx}, entity: $entity${index}, duration: $duration)${RespFields.execExpression}`;
|
||||
}
|
||||
|
||||
return (
|
||||
str +
|
||||
`expression${index}${idx}: execExpression(expression: $expression${index}${idx}, entity: $entity${index}, duration: $duration)${RespFields.execExpression}`
|
||||
`expression${index}${idx}: execExpression(expression: $expression${idx}, entity: $entity${index}, duration: $duration)${RespFields.execExpression}`
|
||||
);
|
||||
});
|
||||
return f;
|
||||
@@ -218,7 +272,10 @@ export async function useExpressionsQueryPodsMetrics(
|
||||
return { queryStr, conditions };
|
||||
}
|
||||
|
||||
function expressionsPodsSource(resp: { errors: string; data: Indexable }): Indexable {
|
||||
function expressionsPodsSource(
|
||||
resp: { errors: string; data: Indexable },
|
||||
pods: Array<(Instance | Endpoint | Service) & Indexable>,
|
||||
): Indexable {
|
||||
if (resp.errors) {
|
||||
ElMessage.error(resp.errors);
|
||||
return {};
|
||||
@@ -300,24 +357,46 @@ export async function useExpressionsQueryPodsMetrics(
|
||||
return { data, names, subNames, metricConfigArr, metricTypesArr, expressionsTips, subExpressionsTips };
|
||||
}
|
||||
|
||||
const dashboardStore = useDashboardStore();
|
||||
const params = await expressionsGraphqlPods();
|
||||
const json = await dashboardStore.fetchMetricValue(params);
|
||||
async function fetchPodsExpressionValues(pods: Array<(Instance | Endpoint | Service) & Indexable>) {
|
||||
const dashboardStore = useDashboardStore();
|
||||
const params = await expressionsGraphqlPods(pods);
|
||||
|
||||
if (json.errors) {
|
||||
ElMessage.error(json.errors);
|
||||
return {};
|
||||
const json = await dashboardStore.fetchMetricValue(params);
|
||||
|
||||
if (json.errors) {
|
||||
ElMessage.error(json.errors);
|
||||
return {};
|
||||
}
|
||||
const expressionParams = expressionsPodsSource(json, pods);
|
||||
|
||||
return expressionParams;
|
||||
}
|
||||
const expressionParams = expressionsPodsSource(json);
|
||||
|
||||
return expressionParams;
|
||||
const result = [];
|
||||
for (let i = 0; i < allPods.length; i += MaximumEntities) {
|
||||
result.push(allPods.slice(i, i + MaximumEntities));
|
||||
}
|
||||
const promiseArr = result.map((d: Array<(Instance | Endpoint | Service) & Indexable>) =>
|
||||
fetchPodsExpressionValues(d),
|
||||
);
|
||||
const responseList = await Promise.all(promiseArr);
|
||||
let resp: Indexable = { data: [], expressionsTips: [], subExpressionsTips: [] };
|
||||
for (const item of responseList) {
|
||||
resp = {
|
||||
...item,
|
||||
data: [...resp.data, ...item.data],
|
||||
expressionsTips: [...resp.expressionsTips, ...item.expressionsTips],
|
||||
subExpressionsTips: [...resp.subExpressionsTips, ...item.subExpressionsTips],
|
||||
};
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
export function useQueryTopologyExpressionsProcessor(metrics: string[], instances: (Call | Node)[]) {
|
||||
const appStore = useAppStoreWithOut();
|
||||
const dashboardStore = useDashboardStore();
|
||||
|
||||
function getExpressionQuery() {
|
||||
function getExpressionQuery(partMetrics?: string[]) {
|
||||
const conditions: { [key: string]: unknown } = {
|
||||
duration: appStore.durationTime,
|
||||
};
|
||||
@@ -371,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;
|
||||
@@ -385,19 +464,19 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
|
||||
|
||||
return { queryStr, conditions };
|
||||
}
|
||||
function handleExpressionValues(resp: { [key: string]: any }) {
|
||||
function handleExpressionValues(partMetrics: string[], resp: { [key: string]: any }) {
|
||||
const obj: any = {};
|
||||
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,
|
||||
});
|
||||
}
|
||||
@@ -405,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 };
|
||||
}
|
||||
|
@@ -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
47
src/hooks/useSnapshot.ts
Normal 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,
|
||||
};
|
||||
}
|
@@ -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" />
|
||||
|
@@ -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);
|
||||
};
|
||||
|
@@ -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",
|
||||
@@ -284,7 +284,8 @@ const msg = {
|
||||
errorInfo: "Error Info",
|
||||
stack: "Stack",
|
||||
serviceVersion: "Service Version",
|
||||
errorPage: "Error Page",
|
||||
pagePath: "Page Path",
|
||||
errorUrl: "Error Url",
|
||||
category: "Category",
|
||||
grade: "Grade",
|
||||
relatedTraceLogs: "Related Logs",
|
||||
@@ -300,7 +301,7 @@ const msg = {
|
||||
viewLogs: "View Logs",
|
||||
logsTagsTip: `Only tags defined in the core/default/searchableLogsTags are searchable.
|
||||
Check more details on the Configuration Vocabulary page`,
|
||||
keywordsOfContentLogTips: "Current storage of SkyWalking OAP server does not support this.",
|
||||
keywordsOfContentLogTips: "Current storage of SkyWalking OAP server does not support this",
|
||||
setEvent: "Set Event",
|
||||
viewAttributes: "View",
|
||||
attributes: "Attributes",
|
||||
@@ -355,7 +356,7 @@ const msg = {
|
||||
addKeywordsOfContent: "Please input a keyword of content",
|
||||
addExcludingKeywordsOfContent: "Please input a keyword of excluding content",
|
||||
noticeTag: "Please press Enter after inputting a tag(key=value).",
|
||||
conditionNotice: "Notice: Please press Enter after inputting a key of content, exclude key of content(key=value).",
|
||||
conditionNotice: "Notice: Please press Enter after inputting a key of content, exclude key of content(key=value)",
|
||||
language: "Language",
|
||||
save: "Save",
|
||||
editStrategy: "Edit Policies",
|
||||
@@ -385,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;
|
||||
|
@@ -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",
|
||||
@@ -283,7 +282,8 @@ const msg = {
|
||||
errorInfo: "Info Error",
|
||||
stack: "Pila",
|
||||
serviceVersion: "Versión Servicio",
|
||||
errorPage: "Página de Error",
|
||||
pagePath: "Página de Error",
|
||||
errorUrl: "Ruta de Error",
|
||||
category: "Categoría",
|
||||
grade: "Grado",
|
||||
relatedTraceLogs: "Registro de Datos Relacionados",
|
||||
@@ -299,7 +299,7 @@ const msg = {
|
||||
viewLogs: "Ver Registro de Datos",
|
||||
logsTagsTip: `Solamente etiquetas definidas en core/default/searchableLogsTags pueden ser buscadas.
|
||||
Más información en la página de Vocabulario de Configuración`,
|
||||
keywordsOfContentLogTips: "El almacenamiento actual del servidor SkyWalking OAP no lo soporta.",
|
||||
keywordsOfContentLogTips: "El almacenamiento actual del servidor SkyWalking OAP no lo soporta",
|
||||
setEvent: "Establecer Evento",
|
||||
viewAttributes: "Ver",
|
||||
serviceEvents: "Eventos Servico",
|
||||
@@ -385,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;
|
||||
|
@@ -125,6 +125,17 @@ const titles = {
|
||||
self_observability_satellite: "Satellite",
|
||||
self_observability_satellite_desc:
|
||||
"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.",
|
||||
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.",
|
||||
};
|
||||
|
||||
export default titles;
|
||||
|
@@ -126,6 +126,17 @@ const titles = {
|
||||
self_observability_satellite: "Satellite",
|
||||
self_observability_satellite_desc:
|
||||
"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:
|
||||
"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.",
|
||||
};
|
||||
|
||||
export default titles;
|
||||
|
@@ -111,6 +111,14 @@ const titles = {
|
||||
self_observability_satellite: "Satellite",
|
||||
self_observability_satellite_desc:
|
||||
"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收集的遥测数据观察服务。",
|
||||
};
|
||||
|
||||
export default titles;
|
||||
|
@@ -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: "最小请求持续时间",
|
||||
@@ -281,7 +281,8 @@ const msg = {
|
||||
errorInfo: "错误信息",
|
||||
stack: "堆栈",
|
||||
serviceVersion: "服务版本",
|
||||
errorPage: "错误页面",
|
||||
pagePath: "错误页面",
|
||||
errorUrl: "错误路径",
|
||||
category: "类别",
|
||||
grade: "等级",
|
||||
relatedTraceLogs: "相关的日志",
|
||||
@@ -383,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;
|
||||
|
@@ -15,6 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { createApp } from "vue";
|
||||
import { ElLoading } from "element-plus";
|
||||
import App from "./App.vue";
|
||||
import { store } from "./store";
|
||||
import components from "@/components";
|
||||
@@ -23,6 +24,11 @@ import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import "./styles/index.ts";
|
||||
import "virtual:svg-icons-register";
|
||||
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: "Loading...",
|
||||
background: "rgba(0, 0, 0, 0.8)",
|
||||
});
|
||||
const app = createApp(App);
|
||||
const appStore = useAppStoreWithOut();
|
||||
|
||||
@@ -34,7 +40,7 @@ mountApp();
|
||||
async function mountApp() {
|
||||
await appStore.getActivateMenus();
|
||||
await appStore.queryOAPTimeInfo();
|
||||
|
||||
const router = await import("./router");
|
||||
app.use(router.default).mount("#app");
|
||||
loading.close();
|
||||
}
|
||||
|
@@ -33,7 +33,7 @@ export const routesAlarm: Array<RouteRecordRaw> = [
|
||||
children: [
|
||||
{
|
||||
path: "/alerting",
|
||||
name: "Alarm",
|
||||
name: "ViewAlarm",
|
||||
component: Alarm,
|
||||
},
|
||||
],
|
||||
|
@@ -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,
|
||||
},
|
||||
|
@@ -33,7 +33,7 @@ export const routesSettings: Array<RouteRecordRaw> = [
|
||||
children: [
|
||||
{
|
||||
path: "/settings",
|
||||
name: "Settings",
|
||||
name: "ViewSettings",
|
||||
component: Settings,
|
||||
},
|
||||
],
|
||||
|
@@ -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;
|
||||
|
@@ -19,6 +19,7 @@ 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";
|
||||
|
||||
interface AlarmState {
|
||||
loading: boolean;
|
||||
@@ -35,7 +36,9 @@ export const alarmStore = defineStore({
|
||||
}),
|
||||
actions: {
|
||||
async getAlarms(params: Recordable) {
|
||||
this.loading = true;
|
||||
const res: AxiosResponse = await graphql.query("queryAlarms").params(params);
|
||||
this.loading = false;
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
@@ -45,6 +48,20 @@ export const alarmStore = defineStore({
|
||||
}
|
||||
return res.data;
|
||||
},
|
||||
async getAlarmTagKeys() {
|
||||
const res: AxiosResponse = await graphql
|
||||
.query("queryAlarmTagKeys")
|
||||
.params({ duration: useAppStoreWithOut().durationTime });
|
||||
|
||||
return res.data;
|
||||
},
|
||||
async getAlarmTagValues(tagKey: string) {
|
||||
const res: AxiosResponse = await graphql
|
||||
.query("queryAlarmTagValues")
|
||||
.params({ tagKey, duration: useAppStoreWithOut().durationTime });
|
||||
|
||||
return res.data;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
141
src/store/modules/async-profiling.ts
Normal file
141
src/store/modules/async-profiling.ts
Normal 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.
|
||||
*/
|
||||
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 { AxiosResponse } from "axios";
|
||||
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 res: AxiosResponse = await graphql.query("getAsyncTaskList").params({
|
||||
request: {
|
||||
serviceId: selectorStore.currentService.id,
|
||||
limit: 10000,
|
||||
},
|
||||
});
|
||||
this.loadingTasks = false;
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
this.taskList = res.data.data.asyncTaskList.tasks || [];
|
||||
this.selectedTask = this.taskList[0] || {};
|
||||
this.setAnalyzeTrees([]);
|
||||
this.setSelectedTask(this.selectedTask);
|
||||
if (!this.taskList.length) {
|
||||
return res.data;
|
||||
}
|
||||
return res.data;
|
||||
},
|
||||
async getTaskLogs(param: { taskID: string }) {
|
||||
const res: AxiosResponse = await graphql.query("getAsyncProfileTaskProcess").params(param);
|
||||
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
this.taskProgress = res.data.data.taskProgress;
|
||||
return res.data;
|
||||
},
|
||||
async getServiceInstances(param: { serviceId: string; isRelation: boolean }): Promise<Nullable<AxiosResponse>> {
|
||||
if (!param.serviceId) {
|
||||
return null;
|
||||
}
|
||||
const res: AxiosResponse = await graphql.query("queryInstances").params({
|
||||
serviceId: param.serviceId,
|
||||
duration: useAppStoreWithOut().durationTime,
|
||||
});
|
||||
if (!res.data.errors) {
|
||||
this.instances = (res.data.data.pods || []).map((d: Instance) => {
|
||||
d.value = d.id || "";
|
||||
return d;
|
||||
});
|
||||
}
|
||||
return res.data;
|
||||
},
|
||||
async createTask(param: AsyncProfileTaskCreationRequest) {
|
||||
const res: AxiosResponse = await graphql
|
||||
.query("saveAsyncProfileTask")
|
||||
.params({ asyncProfilerTaskCreationRequest: param });
|
||||
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
this.getTaskList();
|
||||
return res.data;
|
||||
},
|
||||
async getAsyncProfilingAnalyze(params: { taskId: string; instanceIds: Array<string>; eventType: string }) {
|
||||
if (!params.instanceIds.length) {
|
||||
return new Promise((resolve) => resolve({}));
|
||||
}
|
||||
this.loadingTree = true;
|
||||
const res: AxiosResponse = await graphql.query("getAsyncProfileAnalyze").params({ request: params });
|
||||
this.loadingTree = false;
|
||||
if (res.data.errors) {
|
||||
this.analyzeTrees = [];
|
||||
return res.data;
|
||||
}
|
||||
const { analysisResult } = res.data.data;
|
||||
if (!analysisResult) {
|
||||
this.analyzeTrees = [];
|
||||
return res.data;
|
||||
}
|
||||
this.analyzeTrees = [analysisResult.tree];
|
||||
return res.data;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export function useAsyncProfilingStore(): Recordable {
|
||||
return asyncProfilingStore(store);
|
||||
}
|
@@ -22,6 +22,7 @@ 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;
|
||||
@@ -54,7 +55,7 @@ export const eventStore = defineStore({
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
this.instances = [{ value: "", label: "All" }, ...res.data.data.pods] || [{ value: "", label: "All" }];
|
||||
this.instances = [{ value: "", label: "All" }, ...res.data.data.pods];
|
||||
return res.data;
|
||||
},
|
||||
async getEndpoints(keyword: string) {
|
||||
@@ -66,11 +67,12 @@ export const eventStore = defineStore({
|
||||
serviceId,
|
||||
duration: useAppStoreWithOut().durationTime,
|
||||
keyword: keyword || "",
|
||||
limit: EndpointsTopNDefault,
|
||||
});
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
this.endpoints = [{ value: "", label: "All" }, ...res.data.data.pods] || [{ value: "", label: "All" }];
|
||||
this.endpoints = [{ value: "", label: "All" }, ...res.data.data.pods];
|
||||
return res.data;
|
||||
},
|
||||
async getEvents() {
|
||||
|
@@ -22,6 +22,7 @@ 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[];
|
||||
@@ -80,7 +81,7 @@ export const logStore = defineStore({
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
this.instances = [{ value: "0", label: "All" }, ...res.data.data.pods] || [{ value: " 0", label: "All" }];
|
||||
this.instances = [{ value: "0", label: "All" }, ...res.data.data.pods];
|
||||
return res.data;
|
||||
},
|
||||
async getEndpoints(id: string, keyword?: string) {
|
||||
@@ -89,11 +90,12 @@ export const logStore = defineStore({
|
||||
serviceId,
|
||||
duration: useAppStoreWithOut().durationTime,
|
||||
keyword: keyword || "",
|
||||
limit: EndpointsTopNDefault,
|
||||
});
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
this.endpoints = [{ value: "0", label: "All" }, ...res.data.data.pods] || [{ value: "0", label: "All" }];
|
||||
this.endpoints = [{ value: "0", label: "All" }, ...res.data.data.pods];
|
||||
return res.data;
|
||||
},
|
||||
async getLogsByKeywords() {
|
||||
|
@@ -28,6 +28,7 @@ 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 +81,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] || {};
|
||||
@@ -97,6 +98,7 @@ export const profileStore = defineStore({
|
||||
serviceId,
|
||||
duration: useAppStoreWithOut().durationTime,
|
||||
keyword: keyword || "",
|
||||
limit: EndpointsTopNDefault,
|
||||
});
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
@@ -109,6 +111,7 @@ export const profileStore = defineStore({
|
||||
serviceId,
|
||||
duration: useAppStoreWithOut().durationTime,
|
||||
keyword: keyword || "",
|
||||
limit: EndpointsTopNDefault,
|
||||
});
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
@@ -155,10 +158,10 @@ export const profileStore = defineStore({
|
||||
return res.data;
|
||||
}
|
||||
if (segmentList[0]) {
|
||||
this.currentSegment = segmentList[0];
|
||||
this.setCurrentSegment(segmentList[0]);
|
||||
this.getSegmentSpans(segmentList[0].segmentId);
|
||||
} else {
|
||||
this.currentSegment = {};
|
||||
this.setCurrentSegment({});
|
||||
}
|
||||
return res.data;
|
||||
},
|
||||
|
@@ -20,6 +20,7 @@ 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[];
|
||||
@@ -143,7 +144,7 @@ export const selectorStore = defineStore({
|
||||
serviceId,
|
||||
duration: useAppStoreWithOut().durationTime,
|
||||
keyword: params.keyword || "",
|
||||
limit: params.limit,
|
||||
limit: params.limit || EndpointsTopNDefault,
|
||||
});
|
||||
if (!res.data.errors) {
|
||||
if (params.isRelation) {
|
||||
|
@@ -24,7 +24,6 @@ import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import type { AxiosResponse } from "axios";
|
||||
import query from "@/graphql/fetch";
|
||||
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
interface MetricVal {
|
||||
[key: string]: { values: { id: string; value: unknown }[] };
|
||||
@@ -339,7 +338,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 +349,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 +360,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 +371,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];
|
||||
@@ -443,7 +448,7 @@ export const topologyStore = defineStore({
|
||||
|
||||
return { calls, nodes };
|
||||
},
|
||||
async getNodeExpressionValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
|
||||
async getTopologyExpressionValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
|
||||
const res: AxiosResponse = await query(param);
|
||||
|
||||
if (res.data.errors) {
|
||||
@@ -461,14 +466,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 +483,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() {
|
||||
@@ -550,17 +543,6 @@ export const topologyStore = defineStore({
|
||||
this.setHierarchyInstanceTopology(res.data.data.hierarchyInstanceTopology || {}, levels);
|
||||
return res.data;
|
||||
},
|
||||
async queryHierarchyExpressions(expressions: string[], nodes: Node[]) {
|
||||
const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor(expressions, nodes);
|
||||
const param = getExpressionQuery();
|
||||
const res = await this.getNodeExpressionValue(param);
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
return;
|
||||
}
|
||||
const metrics = handleExpressionValues(res.data);
|
||||
return metrics;
|
||||
},
|
||||
async queryHierarchyNodeExpressions(expressions: string[], layer: string) {
|
||||
const nodes = this.hierarchyServiceNodes.filter((n: HierarchyNode) => n.layer === layer);
|
||||
if (!nodes.length) {
|
||||
@@ -571,7 +553,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 +568,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);
|
||||
},
|
||||
},
|
||||
|
@@ -23,6 +23,7 @@ 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[];
|
||||
@@ -133,6 +134,7 @@ export const traceStore = defineStore({
|
||||
serviceId,
|
||||
duration: useAppStoreWithOut().durationTime,
|
||||
keyword: keyword || "",
|
||||
limit: EndpointsTopNDefault,
|
||||
});
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
|
@@ -139,6 +139,10 @@
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.mb-20 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mr-5 {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
1
src/types/alarm.d.ts
vendored
1
src/types/alarm.d.ts
vendored
@@ -27,6 +27,7 @@ export interface Alarm {
|
||||
scope: string;
|
||||
tags: Array<{ key: string; value: string }>;
|
||||
events: Event[];
|
||||
snapshot: Indexable;
|
||||
}
|
||||
|
||||
export interface Event {
|
||||
|
68
src/types/async-profiling.ts
Normal file
68
src/types/async-profiling.ts
Normal 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[];
|
||||
};
|
59
src/types/auto-imports.d.ts
vendored
59
src/types/auto-imports.d.ts
vendored
@@ -1,52 +1,9 @@
|
||||
// Generated by 'unplugin-auto-import'
|
||||
// We suggest you to commit this file into source control
|
||||
declare global {
|
||||
const computed: typeof import('vue')['computed']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||
const defineComponent: typeof import('vue')['defineComponent']
|
||||
const effectScope: typeof import('vue')['effectScope']
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const h: typeof import('vue')['h']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const markRaw: typeof import('vue')['markRaw']
|
||||
const nextTick: typeof import('vue')['nextTick']
|
||||
const onActivated: typeof import('vue')['onActivated']
|
||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||
const onMounted: typeof import('vue')['onMounted']
|
||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
const readonly: typeof import('vue')['readonly']
|
||||
const ref: typeof import('vue')['ref']
|
||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const unref: typeof import('vue')['unref']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
const useCssVars: typeof import('vue')['useCssVars']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
const watch: typeof import('vue')['watch']
|
||||
const watchEffect: typeof import('vue')['watchEffect']
|
||||
}
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
export {}
|
||||
declare global {
|
||||
|
||||
}
|
||||
|
21
src/types/components.d.ts
vendored
21
src/types/components.d.ts
vendored
@@ -1,15 +1,19 @@
|
||||
// generated by unplugin-vue-components
|
||||
// We suggest you to commit this file into source control
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
import '@vue/runtime-core'
|
||||
export {}
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
DateCalendar: typeof import('./../components/DateCalendar.vue')['default']
|
||||
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
|
||||
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']
|
||||
@@ -40,9 +44,9 @@ declare module '@vue/runtime-core' {
|
||||
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']
|
||||
Loading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||
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']
|
||||
@@ -51,6 +55,7 @@ declare module '@vue/runtime-core' {
|
||||
Tags: typeof import('./../components/Tags.vue')['default']
|
||||
TimePicker: typeof import('./../components/TimePicker.vue')['default']
|
||||
}
|
||||
export interface ComponentCustomProperties {
|
||||
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
||||
|
18
src/types/dashboard.d.ts
vendored
18
src/types/dashboard.d.ts
vendored
@@ -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;
|
||||
};
|
||||
|
15
src/types/ebpf.d.ts
vendored
15
src/types/ebpf.d.ts
vendored
@@ -77,6 +77,21 @@ export type StackElement = {
|
||||
rateOfRoot?: string;
|
||||
rateOfParent: string;
|
||||
};
|
||||
export type TraceProfilingElement = {
|
||||
id: string;
|
||||
originId: string;
|
||||
name: string;
|
||||
parentId: string;
|
||||
codeSignature: string;
|
||||
count: number;
|
||||
stackType: string;
|
||||
value: number;
|
||||
children?: TraceProfilingElement[];
|
||||
rateOfRoot?: string;
|
||||
rateOfParent: string;
|
||||
duration: number;
|
||||
durationChildExcluded: number;
|
||||
};
|
||||
export type AnalyzationTrees = {
|
||||
id: string;
|
||||
parentId: string;
|
||||
|
3
src/types/profile.d.ts
vendored
3
src/types/profile.d.ts
vendored
@@ -41,6 +41,9 @@ export interface TaskListItem {
|
||||
dumpPeriod: number;
|
||||
maxSamplingCount: number;
|
||||
logs: TaskLog[];
|
||||
errorInstanceIds: string[];
|
||||
successInstanceIds: string[];
|
||||
serviceInstanceIds: string[];
|
||||
}
|
||||
export interface SegmentSpan {
|
||||
spanId: string;
|
||||
|
@@ -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();
|
||||
ElNotification({
|
||||
title: "Success",
|
||||
message: "Copied",
|
||||
type: "success",
|
||||
});
|
||||
|
||||
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",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@@ -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);
|
||||
};
|
||||
|
24
src/utils/flameGraph.ts
Normal file
24
src/utils/flameGraph.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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 function treeForeach(tree: any, func: (node: any) => void) {
|
||||
for (const data of tree) {
|
||||
data.children && treeForeach(data.children, func);
|
||||
func(data);
|
||||
}
|
||||
return tree;
|
||||
}
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
@@ -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">
|
||||
|
@@ -13,7 +13,7 @@ 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="timeline-table clear">
|
||||
<div class="timeline-table clear" v-loading="alarmStore.loading">
|
||||
<div v-for="(i, index) in alarmStore.alarms" :key="index" class="clear timeline-item">
|
||||
<div class="g-sm-3 grey sm hide-xs time-line tr">
|
||||
{{ dateFormat(parseInt(i.startTime)) }}
|
||||
@@ -22,15 +22,17 @@ limitations under the License. -->
|
||||
<div class="message mb-5 b">
|
||||
{{ i.message }}
|
||||
</div>
|
||||
<div
|
||||
class="timeline-table-i-scope mr-10 l sm"
|
||||
:class="{
|
||||
blue: i.scope === 'Service',
|
||||
green: i.scope === 'Endpoint',
|
||||
yellow: i.scope === 'ServiceInstance',
|
||||
}"
|
||||
>
|
||||
{{ t(i.scope.toLowerCase()) }}
|
||||
<div class="flex-h">
|
||||
<div
|
||||
class="timeline-table-i-scope"
|
||||
:class="{
|
||||
blue: i.scope === 'Service',
|
||||
green: i.scope === 'Endpoint',
|
||||
yellow: i.scope === 'ServiceInstance',
|
||||
}"
|
||||
>
|
||||
{{ t(i.scope.toLowerCase()) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="grey sm show-xs">
|
||||
{{ dateFormat(parseInt(i.startTime)) }}
|
||||
@@ -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();
|
||||
@@ -132,7 +141,7 @@ limitations under the License. -->
|
||||
currentDetail.value = item;
|
||||
currentEvents.value = item.events;
|
||||
alarmTags.value = currentDetail.value.tags.map((d: { key: string; value: string }) => {
|
||||
return `${d.key} = ${d.value}`;
|
||||
return `${d.key}=${d.value}`;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
@@ -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;`
|
||||
|
141
src/views/alarm/components/Line.vue
Normal file
141
src/views/alarm/components/Line.vue
Normal 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>
|
35
src/views/alarm/components/Snapshot.vue
Normal file
35
src/views/alarm/components/Snapshot.vue
Normal 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>
|
@@ -52,6 +52,14 @@ export const AlarmDetailCol = [
|
||||
label: "events",
|
||||
value: "eventDetail",
|
||||
},
|
||||
{
|
||||
label: "expression",
|
||||
value: "expression",
|
||||
},
|
||||
{
|
||||
label: "snapshot",
|
||||
value: "snapshot",
|
||||
},
|
||||
];
|
||||
|
||||
export const EventsDetailKeys = [
|
||||
|
@@ -21,15 +21,7 @@ limitations under the License. -->
|
||||
<span class="remove-icon" @click="removeTags(index)">×</span>
|
||||
</span>
|
||||
</span>
|
||||
<el-input
|
||||
v-if="type === 'ALARM'"
|
||||
size="small"
|
||||
v-model="tags"
|
||||
class="trace-new-tag"
|
||||
@change="addLabels"
|
||||
:placeholder="t('addTags')"
|
||||
/>
|
||||
<el-popover v-else trigger="click" :visible="visible" width="300px">
|
||||
<el-popover trigger="click" :visible="visible" width="300px">
|
||||
<template #reference>
|
||||
<el-input
|
||||
size="small"
|
||||
@@ -47,7 +39,7 @@ limitations under the License. -->
|
||||
</span>
|
||||
</div>
|
||||
</el-popover>
|
||||
<span class="tags-tip" :class="type !== 'ALARM' && tagArr.length ? 'link-tips' : ''">
|
||||
<span class="tags-tip" :class="tagArr.length ? 'link-tips' : ''">
|
||||
<a
|
||||
target="blank"
|
||||
href="https://github.com/apache/skywalking/blob/master/docs/en/setup/backend/configuration-vocabulary.md"
|
||||
@@ -68,6 +60,7 @@ limitations under the License. -->
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useTraceStore } from "@/store/modules/trace";
|
||||
import { useLogStore } from "@/store/modules/log";
|
||||
import { useAlarmStore } from "@/store/modules/alarm";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
|
||||
@@ -79,6 +72,7 @@ limitations under the License. -->
|
||||
const traceStore = useTraceStore();
|
||||
const logStore = useLogStore();
|
||||
const appStore = useAppStoreWithOut();
|
||||
const alarmStore = useAlarmStore();
|
||||
const { t } = useI18n();
|
||||
const tags = ref<string>("");
|
||||
const tagsList = ref<string[]>([]);
|
||||
@@ -121,10 +115,18 @@ limitations under the License. -->
|
||||
let resp: Recordable = {};
|
||||
if (props.type === "TRACE") {
|
||||
resp = await traceStore.getTagKeys();
|
||||
} else {
|
||||
}
|
||||
if (props.type === "LOG") {
|
||||
resp = await logStore.getLogTagKeys();
|
||||
}
|
||||
|
||||
if (props.type === "ALARM") {
|
||||
resp = await alarmStore.getAlarmTagKeys();
|
||||
}
|
||||
|
||||
if (!resp.data) {
|
||||
return;
|
||||
}
|
||||
if (resp.errors) {
|
||||
ElMessage.error(resp.errors);
|
||||
return;
|
||||
@@ -140,10 +142,17 @@ limitations under the License. -->
|
||||
let resp: Recordable = {};
|
||||
if (props.type === "TRACE") {
|
||||
resp = await traceStore.getTagValues(param);
|
||||
} else {
|
||||
}
|
||||
if (props.type === "LOG") {
|
||||
resp = await logStore.getLogTagValues(param);
|
||||
}
|
||||
if (props.type === "ALARM") {
|
||||
resp = await alarmStore.getAlarmTagValues(param);
|
||||
}
|
||||
|
||||
if (!resp.data) {
|
||||
return;
|
||||
}
|
||||
if (resp.errors) {
|
||||
ElMessage.error(resp.errors);
|
||||
return;
|
||||
|
@@ -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(
|
||||
|
@@ -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"
|
||||
/>
|
||||
@@ -53,7 +55,7 @@ limitations under the License. -->
|
||||
import { useRoute } from "vue-router";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useExpressionsQueryProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import { useDashboardQueryProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import graphs from "./graphs";
|
||||
import { EntityType } from "./data";
|
||||
import timeFormat from "@/utils/timeFormat";
|
||||
@@ -128,12 +130,16 @@ limitations under the License. -->
|
||||
}
|
||||
async function queryMetrics() {
|
||||
loading.value = true;
|
||||
const params = await useExpressionsQueryProcessor({
|
||||
metrics: config.value.expressions || [],
|
||||
metricConfig: config.value.metricConfig || [],
|
||||
subExpressions: config.value.subExpressions || [],
|
||||
});
|
||||
|
||||
const metrics: { [key: string]: { source: { [key: string]: unknown }; typesOfMQE: string[] } } =
|
||||
await useDashboardQueryProcessor([
|
||||
{
|
||||
metrics: config.value.expressions || [],
|
||||
metricConfig: config.value.metricConfig || [],
|
||||
subExpressions: config.value.subExpressions || [],
|
||||
id: config.value.i,
|
||||
},
|
||||
]);
|
||||
const params = metrics[config.value.i];
|
||||
loading.value = false;
|
||||
source.value = params.source || {};
|
||||
typesOfMQE.value = params.typesOfMQE;
|
||||
|
@@ -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);
|
||||
|
@@ -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"
|
||||
|
@@ -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 = {
|
||||
|
@@ -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();
|
||||
|
@@ -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();
|
||||
|
@@ -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,
|
||||
|
@@ -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>
|
@@ -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";
|
||||
@@ -110,7 +124,7 @@ limitations under the License. -->
|
||||
ExpressionResultType,
|
||||
} from "@/views/dashboard/data";
|
||||
import Icon from "@/components/Icon.vue";
|
||||
import { useExpressionsQueryProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import { useDashboardQueryProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import type { DashboardItem, MetricConfigOpt } from "@/types/dashboard";
|
||||
import Standard from "./Standard.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,
|
||||
@@ -240,12 +260,13 @@ limitations under the License. -->
|
||||
}
|
||||
|
||||
async function queryMetricsWithExpressions() {
|
||||
const { expressions, metricConfig } = dashboardStore.selectedGrid;
|
||||
const { expressions, metricConfig, i } = dashboardStore.selectedGrid;
|
||||
if (!(expressions && expressions[0])) {
|
||||
return emit("update", {});
|
||||
}
|
||||
|
||||
const params: Indexable = (await useExpressionsQueryProcessor({ ...states, metricConfig })) || {};
|
||||
const metrics: Indexable = (await useDashboardQueryProcessor([{ ...states, metricConfig, id: i }])) || {};
|
||||
const params = metrics[i];
|
||||
states.tips = params.tips || [];
|
||||
states.metricTypes = params.typesOfMQE || [];
|
||||
dashboardStore.selectWidget({
|
||||
@@ -253,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 {
|
||||
|
85
src/views/dashboard/controls/AsyncProfiling.vue
Normal file
85
src/views/dashboard/controls/AsyncProfiling.vue
Normal 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>
|
@@ -113,6 +113,7 @@ limitations under the License. -->
|
||||
:data="item"
|
||||
:activeIndex="`${data.i}-${activeTabIndex}-${item.i}`"
|
||||
:needQuery="needQuery"
|
||||
:metricsValues="metricsValues"
|
||||
/>
|
||||
</grid-item>
|
||||
</grid-layout>
|
||||
@@ -129,13 +130,17 @@ limitations under the License. -->
|
||||
import controls from "./tab";
|
||||
import { dragIgnoreFrom, WidgetType } from "../data";
|
||||
import copy from "@/utils/copy";
|
||||
import { useExpressionsQueryProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import { useDashboardQueryProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
|
||||
const props = {
|
||||
data: {
|
||||
type: Object as PropType<LayoutConfig>,
|
||||
default: () => ({ children: [] }),
|
||||
},
|
||||
metricsValues: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({}),
|
||||
},
|
||||
active: { type: Boolean, default: false },
|
||||
};
|
||||
export default defineComponent({
|
||||
@@ -259,8 +264,10 @@ limitations under the License. -->
|
||||
if (!metrics.length) {
|
||||
return;
|
||||
}
|
||||
const params: { [key: string]: any } = (await useExpressionsQueryProcessor({ metrics })) || {};
|
||||
const values: { [key: string]: any } =
|
||||
(await useDashboardQueryProcessor([{ metrics, id: props.data.i }])) || {};
|
||||
for (const child of tabsProps.children || []) {
|
||||
const params = values[props.data.i];
|
||||
if (params.source[child.expression || ""]) {
|
||||
child.enable =
|
||||
!!Number(params.source[child.expression || ""]) &&
|
||||
|
@@ -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 {
|
||||
|
@@ -61,6 +61,7 @@ limitations under the License. -->
|
||||
typesOfMQE: typesOfMQE || [],
|
||||
subExpressions: data.subExpressions || [],
|
||||
subTypesOfMQE: data.subTypesOfMQE || [],
|
||||
valueRelatedDashboard: data.valueRelatedDashboard,
|
||||
}"
|
||||
:needQuery="needQuery"
|
||||
@click="clickHandle"
|
||||
@@ -75,11 +76,10 @@ limitations under the License. -->
|
||||
import type { LayoutConfig } from "@/types/dashboard";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import graphs from "../graphs";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useExpressionsQueryProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import { EntityType, ListChartTypes } from "../data";
|
||||
import { useDashboardQueryProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import { ListChartTypes } from "../data";
|
||||
import type { EventParams } from "@/types/dashboard";
|
||||
import getDashboard from "@/hooks/useDashboardsSession";
|
||||
|
||||
@@ -88,6 +88,10 @@ limitations under the License. -->
|
||||
type: Object as PropType<LayoutConfig>,
|
||||
default: () => ({ widget: {}, graph: {} }),
|
||||
},
|
||||
metricsValues: {
|
||||
type: Object as PropType<{ [key: string]: { source: { [key: string]: unknown }; typesOfMQE: string[] } }>,
|
||||
default: () => ({}),
|
||||
},
|
||||
activeIndex: { type: String, default: "" },
|
||||
needQuery: { type: Boolean, default: false },
|
||||
};
|
||||
@@ -105,23 +109,20 @@ limitations under the License. -->
|
||||
const { data } = toRefs(props);
|
||||
const appStore = useAppStoreWithOut();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const selectorStore = useSelectorStore();
|
||||
const graph = computed(() => props.data.graph || {});
|
||||
const widget = computed(() => props.data.widget || {});
|
||||
const isList = computed(() => ListChartTypes.includes((props.data.graph && props.data.graph.type) || ""));
|
||||
const typesOfMQE = ref<string[]>([]);
|
||||
|
||||
if ((props.needQuery || !dashboardStore.currentDashboard.id) && !isList.value) {
|
||||
queryMetrics();
|
||||
}
|
||||
|
||||
async function queryMetrics() {
|
||||
loading.value = true;
|
||||
const e = {
|
||||
const config = {
|
||||
metrics: props.data.expressions || [],
|
||||
metricConfig: props.data.metricConfig || [],
|
||||
id: props.data.i,
|
||||
};
|
||||
const params = (await useExpressionsQueryProcessor(e)) || {};
|
||||
const metrics: { [key: string]: { source: { [key: string]: unknown }; typesOfMQE: string[] } } =
|
||||
(await useDashboardQueryProcessor([config])) || {};
|
||||
const params = metrics[data.value.i];
|
||||
loading.value = false;
|
||||
state.source = params.source || {};
|
||||
typesOfMQE.value = params.typesOfMQE;
|
||||
@@ -160,7 +161,7 @@ limitations under the License. -->
|
||||
dashboardStore.selectWidget(props.data);
|
||||
}
|
||||
watch(
|
||||
() => props.data.expressions,
|
||||
() => props.data,
|
||||
() => {
|
||||
if (!dashboardStore.selectedGrid) {
|
||||
return;
|
||||
@@ -169,54 +170,19 @@ limitations under the License. -->
|
||||
return;
|
||||
}
|
||||
const chart = dashboardStore.selectedGrid.graph || {};
|
||||
if (ListChartTypes.includes(chart.type) || isList.value) {
|
||||
if (ListChartTypes.includes(chart.type)) {
|
||||
return;
|
||||
}
|
||||
queryMetrics();
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => [selectorStore.currentService, selectorStore.currentDestService],
|
||||
() => props.metricsValues,
|
||||
() => {
|
||||
if (isList.value) {
|
||||
return;
|
||||
}
|
||||
if ([EntityType[0].value, EntityType[4].value].includes(dashboardStore.entity)) {
|
||||
queryMetrics();
|
||||
}
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => [selectorStore.currentPod, selectorStore.currentDestPod],
|
||||
() => {
|
||||
if ([EntityType[0].value, EntityType[7].value, EntityType[8].value].includes(dashboardStore.entity)) {
|
||||
return;
|
||||
}
|
||||
if (isList.value) {
|
||||
return;
|
||||
}
|
||||
queryMetrics();
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => [selectorStore.currentProcess, selectorStore.currentDestProcess],
|
||||
() => {
|
||||
if (isList.value) {
|
||||
return;
|
||||
}
|
||||
if ([EntityType[7].value, EntityType[8].value].includes(dashboardStore.entity)) {
|
||||
queryMetrics();
|
||||
}
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => appStore.durationTime,
|
||||
() => {
|
||||
if (isList.value) {
|
||||
return;
|
||||
}
|
||||
if (dashboardStore.entity === EntityType[1].value) {
|
||||
queryMetrics();
|
||||
const params = props.metricsValues[data.value.i];
|
||||
if (params) {
|
||||
state.source = params.source || {};
|
||||
typesOfMQE.value = params.typesOfMQE;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
};
|
||||
|
@@ -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" },
|
||||
|
@@ -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,
|
||||
|
@@ -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 {
|
||||
|
@@ -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 || []),
|
||||
|
@@ -67,9 +67,10 @@ limitations under the License. -->
|
||||
<el-pagination
|
||||
class="pagination flex-h"
|
||||
layout="prev, pager, next"
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:total="pods.length"
|
||||
@current-change="changePage"
|
||||
:total="searchText ? pods.filter((d: any) => d.label.includes(searchText)).length : pods.length"
|
||||
@current-change="handleCurrentChange"
|
||||
@prev-click="changePage"
|
||||
@next-click="changePage"
|
||||
/>
|
||||
@@ -122,6 +123,7 @@ limitations under the License. -->
|
||||
const dashboardStore = useDashboardStore();
|
||||
const chartLoading = ref<boolean>(false);
|
||||
const instances = ref<Instance[]>([]); // current instances
|
||||
const currentPage = ref<number>(1);
|
||||
const pageSize = 10;
|
||||
const searchText = ref<string>("");
|
||||
const colMetrics = ref<string[]>([]);
|
||||
@@ -212,19 +214,27 @@ limitations under the License. -->
|
||||
);
|
||||
}
|
||||
|
||||
function changePage(pageIndex: number) {
|
||||
instances.value = pods.value.filter((d: unknown, index: number) => {
|
||||
if (index >= (pageIndex - 1) * pageSize && index < pageIndex * pageSize) {
|
||||
return d;
|
||||
}
|
||||
});
|
||||
function changePage() {
|
||||
let podList = pods.value;
|
||||
if (searchText.value) {
|
||||
podList = pods.value.filter((d: { label: string }) => d.label.includes(searchText.value));
|
||||
}
|
||||
instances.value = podList.filter(
|
||||
(_, index: number) => index >= (currentPage.value - 1) * pageSize && index < currentPage.value * pageSize,
|
||||
);
|
||||
queryInstanceMetrics(instances.value);
|
||||
}
|
||||
|
||||
function handleCurrentChange(val: number) {
|
||||
currentPage.value = val;
|
||||
changePage();
|
||||
}
|
||||
|
||||
function searchList() {
|
||||
const searchInstances = pods.value.filter((d: { label: string }) => d.label.includes(searchText.value));
|
||||
instances.value = searchInstances.filter((d: unknown, index: number) => index < pageSize);
|
||||
instances.value = searchInstances.filter((_, index: number) => index < pageSize);
|
||||
queryInstanceMetrics(instances.value);
|
||||
currentPage.value = 1;
|
||||
}
|
||||
|
||||
watch(
|
||||
|
@@ -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",
|
||||
|
@@ -60,9 +60,10 @@ limitations under the License. -->
|
||||
<el-pagination
|
||||
class="pagination flex-h"
|
||||
layout="prev, pager, next"
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:total="selectorStore.services.length"
|
||||
@current-change="changePage"
|
||||
:total="searchText ? sortServices.filter((d: any) => d.label.includes(searchText)).length : sortServices.length"
|
||||
@current-change="handleCurrentChange"
|
||||
@prev-click="changePage"
|
||||
@next-click="changePage"
|
||||
/>
|
||||
@@ -112,6 +113,7 @@ limitations under the License. -->
|
||||
const dashboardStore = useDashboardStore();
|
||||
const appStore = useAppStoreWithOut();
|
||||
const chartLoading = ref<boolean>(false);
|
||||
const currentPage = ref<number>(1);
|
||||
const pageSize = 10;
|
||||
const services = ref<Service[]>([]);
|
||||
const colMetrics = ref<string[]>([]);
|
||||
@@ -247,19 +249,28 @@ limitations under the License. -->
|
||||
}
|
||||
return { rowspan: groups.value[param.row.group], colspan: 1 };
|
||||
}
|
||||
function changePage(pageIndex: number) {
|
||||
const arr = sortServices.value.filter((d: Service, index: number) => {
|
||||
if (index >= (pageIndex - 1) * pageSize && index < pageSize * pageIndex) {
|
||||
function changePage() {
|
||||
let services = sortServices.value;
|
||||
if (searchText.value) {
|
||||
services = sortServices.value.filter((d: { label: string }) => d.label.includes(searchText.value));
|
||||
}
|
||||
const arr = services.filter((d: Service, index: number) => {
|
||||
if (index >= (currentPage.value - 1) * pageSize && index < pageSize * currentPage.value) {
|
||||
return d;
|
||||
}
|
||||
});
|
||||
|
||||
setServices(arr);
|
||||
}
|
||||
function handleCurrentChange(val: number) {
|
||||
currentPage.value = val;
|
||||
changePage();
|
||||
}
|
||||
function searchList() {
|
||||
const searchServices = sortServices.value.filter((d: { label: string }) => d.label.includes(searchText.value));
|
||||
const services = searchServices.filter((d: unknown, index: number) => index < pageSize);
|
||||
const services = searchServices.filter((_, index: number) => index < pageSize);
|
||||
setServices(services);
|
||||
currentPage.value = 1;
|
||||
}
|
||||
|
||||
watch(
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -53,5 +53,5 @@
|
||||
}
|
||||
|
||||
.inputs {
|
||||
width: 300px;
|
||||
width: 400px;
|
||||
}
|
||||
|
@@ -14,12 +14,15 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License. -->
|
||||
<template>
|
||||
<grid-layout
|
||||
v-if="dashboardStore.layout.length"
|
||||
v-model:layout="dashboardStore.layout"
|
||||
:col-num="24"
|
||||
:row-height="10"
|
||||
:is-draggable="dashboardStore.editMode"
|
||||
:is-resizable="dashboardStore.editMode"
|
||||
v-if="dashboardStore.layout.length"
|
||||
v-loading.fullscreen.lock="loading"
|
||||
element-loading-text="Loading..."
|
||||
element-loading-background="rgba(122, 122, 122, 0.8)"
|
||||
>
|
||||
<grid-item
|
||||
v-for="item in dashboardStore.layout"
|
||||
@@ -33,33 +36,39 @@ limitations under the License. -->
|
||||
:class="{ active: dashboardStore.activedGridItem === item.i }"
|
||||
:drag-ignore-from="dragIgnoreFrom"
|
||||
>
|
||||
<component :is="item.type" :data="item" />
|
||||
<component :is="item.type" :data="item" :metricsValues="metricsValues" />
|
||||
</grid-item>
|
||||
</grid-layout>
|
||||
<div class="no-data-tips" v-else>{{ t("noWidget") }}</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, onBeforeUnmount } from "vue";
|
||||
import { defineComponent, onBeforeUnmount, watch, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import type { LayoutConfig } from "@/types/dashboard";
|
||||
import controls from "../controls/index";
|
||||
import { dragIgnoreFrom } from "../data";
|
||||
import { dragIgnoreFrom, ListChartTypes, WidgetType } from "../data";
|
||||
import { useDashboardQueryProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import { EntityType } from "../data";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Layout",
|
||||
components: { ...controls },
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStoreWithOut();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const selectorStore = useSelectorStore();
|
||||
const metricsValues = ref();
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
function clickGrid(item: LayoutConfig, event: Event) {
|
||||
dashboardStore.activeGridItem(item.i);
|
||||
dashboardStore.selectWidget(item);
|
||||
if (
|
||||
item.type === "Tab" &&
|
||||
item.type === WidgetType.Tab &&
|
||||
!["operations", "tab-layout"].includes((event.target as HTMLDivElement)?.className) &&
|
||||
(event.target as HTMLDivElement)?.classList[2] !== "icon-tool" &&
|
||||
(event.target as HTMLDivElement)?.nodeName !== "use"
|
||||
@@ -67,6 +76,53 @@ limitations under the License. -->
|
||||
dashboardStore.setActiveTabIndex(0);
|
||||
}
|
||||
}
|
||||
async function queryMetrics() {
|
||||
const widgets: LayoutConfig[] = [];
|
||||
for (const item of dashboardStore.layout) {
|
||||
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.filter(
|
||||
(d: LayoutConfig) => d.type === WidgetType.Widget && !ListChartTypes.includes(d.graph?.type || ""),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
const configList = widgets.map((d: LayoutConfig) => ({
|
||||
metrics: d.expressions || [],
|
||||
metricConfig: d.metricConfig || [],
|
||||
id: d.i,
|
||||
}));
|
||||
if (!widgets.length) {
|
||||
return {};
|
||||
}
|
||||
loading.value = true;
|
||||
metricsValues.value = (await useDashboardQueryProcessor(configList)) || {};
|
||||
loading.value = false;
|
||||
}
|
||||
async function queryTabsMetrics() {
|
||||
const configList = dashboardStore.currentTabItems
|
||||
.filter(
|
||||
(item: LayoutConfig) => item.type === WidgetType.Widget && !ListChartTypes.includes(item.graph?.type || ""),
|
||||
)
|
||||
.map((d: LayoutConfig) => ({
|
||||
metrics: d.expressions || [],
|
||||
metricConfig: d.metricConfig || [],
|
||||
id: d.i,
|
||||
}));
|
||||
if (!configList.length) {
|
||||
return {};
|
||||
}
|
||||
loading.value = true;
|
||||
metricsValues.value = (await useDashboardQueryProcessor(configList)) || {};
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
dashboardStore.setLayout([]);
|
||||
selectorStore.setCurrentService(null);
|
||||
@@ -74,11 +130,53 @@ limitations under the License. -->
|
||||
dashboardStore.setEntity("");
|
||||
dashboardStore.setConfigPanel(false);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => [selectorStore.currentService, selectorStore.currentDestService],
|
||||
() => {
|
||||
if ([EntityType[0].value, EntityType[4].value].includes(dashboardStore.entity)) {
|
||||
queryMetrics();
|
||||
}
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => [selectorStore.currentPod, selectorStore.currentDestPod],
|
||||
() => {
|
||||
if ([EntityType[0].value, EntityType[7].value, EntityType[8].value].includes(dashboardStore.entity)) {
|
||||
return;
|
||||
}
|
||||
queryMetrics();
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => [selectorStore.currentProcess, selectorStore.currentDestProcess],
|
||||
() => {
|
||||
if ([EntityType[7].value, EntityType[8].value].includes(dashboardStore.entity)) {
|
||||
queryMetrics();
|
||||
}
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => [appStore.durationTime, dashboardStore.layout],
|
||||
() => {
|
||||
if (dashboardStore.entity === EntityType[1].value) {
|
||||
queryMetrics();
|
||||
}
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => dashboardStore.currentTabItems,
|
||||
() => {
|
||||
queryTabsMetrics();
|
||||
},
|
||||
);
|
||||
return {
|
||||
dashboardStore,
|
||||
clickGrid,
|
||||
t,
|
||||
dragIgnoreFrom,
|
||||
metricsValues,
|
||||
loading,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@@ -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">
|
||||
@@ -520,7 +520,7 @@ limitations under the License. -->
|
||||
instanceId: selectorStore.currentPod.id,
|
||||
});
|
||||
if (setPod) {
|
||||
const process = params.processId || selectorStore.processes[0].id;
|
||||
const process = selectorStore.currentProcess?.id || params.processId || selectorStore.processes[0].id;
|
||||
const currentProcess = selectorStore.processes.find((d: { id: string }) => d.id === process);
|
||||
if (currentProcess) {
|
||||
selectorStore.setCurrentProcess(currentProcess);
|
||||
@@ -538,7 +538,8 @@ limitations under the License. -->
|
||||
isRelation: true,
|
||||
});
|
||||
if (setPod) {
|
||||
const destProcess = params.destProcessId || selectorStore.destProcesses[0].id;
|
||||
const destProcess =
|
||||
selectorStore.currentDestProcess?.id || params.destProcessId || selectorStore.destProcesses[0].id;
|
||||
const currentDestProcess = selectorStore.destProcesses.find((d: { id: string }) => d.id === destProcess);
|
||||
if (!currentDestProcess) {
|
||||
states.currentDestProcess = "";
|
||||
@@ -570,7 +571,7 @@ limitations under the License. -->
|
||||
states.currentDestPod = "";
|
||||
return;
|
||||
}
|
||||
const destPod = params.destPodId || selectorStore.destPods[0].id;
|
||||
const destPod = selectorStore.currentDestPod?.id || params.destPodId || selectorStore.destPods[0].id;
|
||||
const currentDestPod = selectorStore.destPods.find((d: { id: string }) => d.id === destPod);
|
||||
if (!currentDestPod) {
|
||||
states.currentDestPod = "";
|
||||
@@ -602,7 +603,7 @@ limitations under the License. -->
|
||||
states.currentPod = "";
|
||||
return;
|
||||
}
|
||||
const pod = params.podId || selectorStore.pods[0].id;
|
||||
const pod = selectorStore.currentPod?.id || params.podId || selectorStore.pods[0].id;
|
||||
const currentPod = selectorStore.pods.find((d: { id: string }) => d.id === pod);
|
||||
if (!currentPod) {
|
||||
selectorStore.setCurrentPod(null);
|
||||
@@ -692,10 +693,6 @@ limitations under the License. -->
|
||||
padding: 4px 2px;
|
||||
}
|
||||
|
||||
.tool-icons {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.tools {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
@@ -703,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;
|
||||
@@ -733,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;
|
||||
|
69
src/views/dashboard/related/async-profiling/Content.vue
Normal file
69
src/views/dashboard/related/async-profiling/Content.vue
Normal 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>
|
50
src/views/dashboard/related/async-profiling/Header.vue
Normal file
50
src/views/dashboard/related/async-profiling/Header.vue
Normal 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>
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const DurationOptions = [
|
||||
{ value: "5", label: "5 min" },
|
||||
{ value: "10", label: "10 min" },
|
||||
{ value: "15", label: "15 min" },
|
||||
];
|
||||
|
||||
export const ProfilingEvents = ["CPU", "ALLOC", "LOCK", "WALL", "CTIMER", "ITIMER"];
|
||||
|
||||
export enum EventsMap {
|
||||
CPU = "EXECUTION_SAMPLE",
|
||||
WALL = "EXECUTION_SAMPLE",
|
||||
CTIMER = "EXECUTION_SAMPLE",
|
||||
ITIMER = "EXECUTION_SAMPLE",
|
||||
LOCK = "LOCK",
|
||||
OBJECT_ALLOCATION_IN_NEW_TLAB = "OBJECT_ALLOCATION_IN_NEW_TLAB",
|
||||
OBJECT_ALLOCATION_OUTSIDE_TLAB = "OBJECT_ALLOCATION_OUTSIDE_TLAB",
|
||||
}
|
||||
|
||||
export enum JFREventType {
|
||||
EXECUTION_SAMPLE = "EXECUTION_SAMPLE",
|
||||
LOCK = "LOCK",
|
||||
OBJECT_ALLOCATION_IN_NEW_TLAB = "OBJECT_ALLOCATION_IN_NEW_TLAB",
|
||||
OBJECT_ALLOCATION_OUTSIDE_TLAB = "OBJECT_ALLOCATION_OUTSIDE_TLAB",
|
||||
PROFILER_LIVE_OBJECT = "PROFILER_LIVE_OBJECT",
|
||||
}
|
||||
|
||||
export const ComponentType = "ASYNC_PROFILING";
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user