mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-05-01 18:43:40 +00:00
feat: implement MQE
on topology widget (#312)
This commit is contained in:
parent
8c1ddb109c
commit
60a4232759
@ -44,6 +44,7 @@ module.exports = {
|
|||||||
"workflow",
|
"workflow",
|
||||||
"types",
|
"types",
|
||||||
"release",
|
"release",
|
||||||
|
"merge",
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
87
src/components/Tags.vue
Normal file
87
src/components/Tags.vue
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<!-- 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>
|
||||||
|
<span :class="vertical ? 'vertical' : 'horizontal'" v-for="tag in dynamicTags" :key="tag">
|
||||||
|
<el-tag closable :disable-transitions="false" @close="handleClose(tag)">
|
||||||
|
{{ tag }}
|
||||||
|
</el-tag>
|
||||||
|
</span>
|
||||||
|
<el-input
|
||||||
|
v-if="inputVisible"
|
||||||
|
ref="InputRef"
|
||||||
|
v-model="inputValue"
|
||||||
|
class="ml-5 input-name"
|
||||||
|
size="small"
|
||||||
|
@keyup.enter="handleInputConfirm"
|
||||||
|
@blur="handleInputConfirm"
|
||||||
|
/>
|
||||||
|
<el-button v-else size="small" @click="showInput"> + {{ text }} </el-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { nextTick, ref } from "vue";
|
||||||
|
import type { PropType } from "vue";
|
||||||
|
import { ElInput } from "element-plus";
|
||||||
|
|
||||||
|
/*global defineProps, defineEmits*/
|
||||||
|
const emits = defineEmits(["change"]);
|
||||||
|
const props = defineProps({
|
||||||
|
tags: {
|
||||||
|
type: Array as PropType<string[]>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
text: { type: String, default: "" },
|
||||||
|
vertical: { type: Boolean, default: false },
|
||||||
|
});
|
||||||
|
const inputValue = ref("");
|
||||||
|
const dynamicTags = ref<string[]>(props.tags || []);
|
||||||
|
const inputVisible = ref(false);
|
||||||
|
const InputRef = ref<InstanceType<typeof ElInput>>();
|
||||||
|
|
||||||
|
const handleClose = (tag: string) => {
|
||||||
|
dynamicTags.value.splice(dynamicTags.value.indexOf(tag), 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showInput = () => {
|
||||||
|
inputVisible.value = true;
|
||||||
|
nextTick(() => {
|
||||||
|
InputRef.value!.input!.focus();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputConfirm = () => {
|
||||||
|
if (inputValue.value) {
|
||||||
|
dynamicTags.value.push(inputValue.value);
|
||||||
|
}
|
||||||
|
inputVisible.value = false;
|
||||||
|
inputValue.value = "";
|
||||||
|
emits("change", dynamicTags.value);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.input-name {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -21,6 +21,7 @@ import Selector from "./Selector.vue";
|
|||||||
import Graph from "./Graph.vue";
|
import Graph from "./Graph.vue";
|
||||||
import Radio from "./Radio.vue";
|
import Radio from "./Radio.vue";
|
||||||
import SelectSingle from "./SelectSingle.vue";
|
import SelectSingle from "./SelectSingle.vue";
|
||||||
|
import Tags from "./Tags.vue";
|
||||||
import VueGridLayout from "vue-grid-layout";
|
import VueGridLayout from "vue-grid-layout";
|
||||||
|
|
||||||
const components: Indexable = {
|
const components: Indexable = {
|
||||||
@ -31,6 +32,7 @@ const components: Indexable = {
|
|||||||
Graph,
|
Graph,
|
||||||
Radio,
|
Radio,
|
||||||
SelectSingle,
|
SelectSingle,
|
||||||
|
Tags,
|
||||||
};
|
};
|
||||||
const componentsName: string[] = Object.keys(components);
|
const componentsName: string[] = Object.keys(components);
|
||||||
|
|
||||||
|
@ -15,13 +15,14 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { RespFields } from "./data";
|
import { RespFields } from "./data";
|
||||||
import { ExpressionResultType } from "@/views/dashboard/data";
|
import { EntityType, ExpressionResultType } from "@/views/dashboard/data";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
import { useSelectorStore } from "@/store/modules/selectors";
|
import { useSelectorStore } from "@/store/modules/selectors";
|
||||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||||
import type { MetricConfigOpt } from "@/types/dashboard";
|
import type { MetricConfigOpt } from "@/types/dashboard";
|
||||||
import type { Instance, Endpoint, Service } from "@/types/selector";
|
import type { Instance, Endpoint, Service } from "@/types/selector";
|
||||||
|
import type { Node, Call } from "@/types/topology";
|
||||||
|
|
||||||
export async function useExpressionsQueryProcessor(config: Indexable) {
|
export async function useExpressionsQueryProcessor(config: Indexable) {
|
||||||
function expressionsGraphqlPods() {
|
function expressionsGraphqlPods() {
|
||||||
@ -312,3 +313,88 @@ export async function useExpressionsQueryPodsMetrics(
|
|||||||
|
|
||||||
return expressionParams;
|
return expressionParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useQueryTopologyExpressionsProcessor(metrics: string[], instances: (Call | Node)[]) {
|
||||||
|
const appStore = useAppStoreWithOut();
|
||||||
|
const dashboardStore = useDashboardStore();
|
||||||
|
|
||||||
|
function getExpressionQuery() {
|
||||||
|
const conditions: { [key: string]: unknown } = {
|
||||||
|
duration: appStore.durationTime,
|
||||||
|
};
|
||||||
|
const variables: string[] = [`$duration: Duration!`];
|
||||||
|
const fragmentList = instances.map((d: any, index: number) => {
|
||||||
|
let serviceName;
|
||||||
|
let destServiceName;
|
||||||
|
let endpointName;
|
||||||
|
let serviceInstanceName;
|
||||||
|
let destServiceInstanceName;
|
||||||
|
let destEndpointName;
|
||||||
|
if (d.sourceObj && d.targetObj) {
|
||||||
|
// instances = Calls
|
||||||
|
serviceName = d.sourceObj.serviceName || d.sourceObj.name;
|
||||||
|
destServiceName = d.targetObj.serviceName || d.targetObj.name;
|
||||||
|
if (EntityType[4].value === dashboardStore.entity) {
|
||||||
|
serviceInstanceName = d.sourceObj.name;
|
||||||
|
destServiceInstanceName = d.targetObj.name;
|
||||||
|
}
|
||||||
|
if (EntityType[2].value === dashboardStore.entity) {
|
||||||
|
endpointName = d.sourceObj.name;
|
||||||
|
destEndpointName = d.targetObj.name;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// instances = Nodes
|
||||||
|
serviceName = d.serviceName || d.name;
|
||||||
|
if (EntityType[4].value === dashboardStore.entity) {
|
||||||
|
serviceInstanceName = d.name;
|
||||||
|
}
|
||||||
|
if (EntityType[2].value === dashboardStore.entity) {
|
||||||
|
endpointName = d.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const entity = {
|
||||||
|
serviceName,
|
||||||
|
normal: true,
|
||||||
|
serviceInstanceName,
|
||||||
|
endpointName,
|
||||||
|
destServiceName,
|
||||||
|
destNormal: destServiceName ? true : undefined,
|
||||||
|
destServiceInstanceName,
|
||||||
|
destEndpointName,
|
||||||
|
};
|
||||||
|
variables.push(`$entity${index}: Entity!`);
|
||||||
|
conditions[`entity${index}`] = entity;
|
||||||
|
const f = metrics.map((name: string, idx: number) => {
|
||||||
|
if (index === 0) {
|
||||||
|
variables.push(`$expression${idx}: String!`);
|
||||||
|
conditions[`expression${idx}`] = name;
|
||||||
|
}
|
||||||
|
return `expression${index}${idx}: execExpression(expression: $expression${idx}, entity: $entity${index}, duration: $duration)${RespFields.execExpression}`;
|
||||||
|
});
|
||||||
|
return f;
|
||||||
|
});
|
||||||
|
const fragment = fragmentList.flat(1).join(" ");
|
||||||
|
const queryStr = `query queryData(${variables}) {${fragment}}`;
|
||||||
|
|
||||||
|
return { queryStr, conditions };
|
||||||
|
}
|
||||||
|
function handleExpressionValues(resp: { [key: string]: any }) {
|
||||||
|
const obj: any = {};
|
||||||
|
for (let idx = 0; idx < instances.length; idx++) {
|
||||||
|
for (let index = 0; index < metrics.length; index++) {
|
||||||
|
const k = "expression" + idx + index;
|
||||||
|
if (metrics[index]) {
|
||||||
|
if (!obj[metrics[index]]) {
|
||||||
|
obj[metrics[index]] = {
|
||||||
|
values: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
obj[metrics[index]].values.push({ value: resp[k].results[0].values[0].value, id: instances[idx].id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { getExpressionQuery, handleExpressionValues };
|
||||||
|
}
|
||||||
|
@ -378,5 +378,9 @@ const msg = {
|
|||||||
menus: "Menus",
|
menus: "Menus",
|
||||||
saveReload: "Save and reload the page",
|
saveReload: "Save and reload the page",
|
||||||
document: "Documentation",
|
document: "Documentation",
|
||||||
|
metricMode: "Metric Mode",
|
||||||
|
addExpressions: "Add Expressions",
|
||||||
|
expressions: "Expression",
|
||||||
|
unhealthyExpression: "Unhealthy Expression",
|
||||||
};
|
};
|
||||||
export default msg;
|
export default msg;
|
||||||
|
@ -378,5 +378,9 @@ const msg = {
|
|||||||
menus: "Menus",
|
menus: "Menus",
|
||||||
saveReload: "Save and reload the page",
|
saveReload: "Save and reload the page",
|
||||||
document: "Documentation",
|
document: "Documentation",
|
||||||
|
metricMode: "Metric Mode",
|
||||||
|
addExpressions: "Add Expressions",
|
||||||
|
expressions: "Expression",
|
||||||
|
unhealthyExpression: "Unhealthy Expression",
|
||||||
};
|
};
|
||||||
export default msg;
|
export default msg;
|
||||||
|
@ -376,5 +376,9 @@ const msg = {
|
|||||||
menusManagement: "菜单",
|
menusManagement: "菜单",
|
||||||
saveReload: "保存并重新加载页面",
|
saveReload: "保存并重新加载页面",
|
||||||
document: "文档",
|
document: "文档",
|
||||||
|
metricMode: "指标模式",
|
||||||
|
addExpressions: "添加表达式",
|
||||||
|
expressions: "表达式",
|
||||||
|
unhealthyExpression: "非健康表达式",
|
||||||
};
|
};
|
||||||
export default msg;
|
export default msg;
|
||||||
|
@ -57,7 +57,7 @@ export const eventStore = defineStore({
|
|||||||
this.instances = [{ value: "", label: "All" }, ...res.data.data.pods] || [{ value: "", label: "All" }];
|
this.instances = [{ value: "", label: "All" }, ...res.data.data.pods] || [{ value: "", label: "All" }];
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
async getEndpoints(keyword?: string) {
|
async getEndpoints(keyword: string) {
|
||||||
const serviceId = useSelectorStore().currentService ? useSelectorStore().currentService.id : "";
|
const serviceId = useSelectorStore().currentService ? useSelectorStore().currentService.id : "";
|
||||||
if (!serviceId) {
|
if (!serviceId) {
|
||||||
return;
|
return;
|
||||||
|
@ -24,6 +24,7 @@ import { useAppStoreWithOut } from "@/store/modules/app";
|
|||||||
import type { AxiosResponse } from "axios";
|
import type { AxiosResponse } from "axios";
|
||||||
import query from "@/graphql/fetch";
|
import query from "@/graphql/fetch";
|
||||||
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
|
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
|
||||||
|
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
|
|
||||||
interface MetricVal {
|
interface MetricVal {
|
||||||
@ -114,6 +115,16 @@ export const topologyStore = defineStore({
|
|||||||
setLinkClientMetrics(m: MetricVal) {
|
setLinkClientMetrics(m: MetricVal) {
|
||||||
this.linkClientMetrics = m;
|
this.linkClientMetrics = m;
|
||||||
},
|
},
|
||||||
|
setLegendValues(expressions: string, data: { [key: string]: any }) {
|
||||||
|
for (let idx = 0; idx < this.nodes.length; idx++) {
|
||||||
|
for (let index = 0; index < expressions.length; index++) {
|
||||||
|
const k = "expression" + idx + index;
|
||||||
|
if (expressions[index]) {
|
||||||
|
this.nodes[idx][expressions[index]] = Number(data[k].results[0].values[0].value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
async getDepthServiceTopology(serviceIds: string[], depth: number) {
|
async getDepthServiceTopology(serviceIds: string[], depth: number) {
|
||||||
const res = await this.getServicesTopology(serviceIds);
|
const res = await this.getServicesTopology(serviceIds);
|
||||||
if (depth > 1) {
|
if (depth > 1) {
|
||||||
@ -321,6 +332,15 @@ export const topologyStore = defineStore({
|
|||||||
this.setNodeMetricValue(res.data.data);
|
this.setNodeMetricValue(res.data.data);
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
|
async getNodeExpressionValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
|
||||||
|
const res: AxiosResponse = await query(param);
|
||||||
|
|
||||||
|
if (res.data.errors) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
async getLinkClientMetrics(linkClientMetrics: string[]) {
|
async getLinkClientMetrics(linkClientMetrics: string[]) {
|
||||||
if (!linkClientMetrics.length) {
|
if (!linkClientMetrics.length) {
|
||||||
this.setLinkClientMetrics({});
|
this.setLinkClientMetrics({});
|
||||||
@ -353,6 +373,29 @@ export const topologyStore = defineStore({
|
|||||||
ElMessage.error(res.errors);
|
ElMessage.error(res.errors);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async getLinkExpressions(expressions: string[], type: string) {
|
||||||
|
if (!expressions.length) {
|
||||||
|
this.setLinkServerMetrics({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const calls = this.calls.filter((i: Call) => i.detectPoints.includes(type));
|
||||||
|
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);
|
||||||
|
if (type === "SERVER") {
|
||||||
|
this.setLinkServerMetrics(metrics);
|
||||||
|
} else {
|
||||||
|
this.setLinkClientMetrics(metrics);
|
||||||
|
}
|
||||||
|
},
|
||||||
async queryNodeMetrics(nodeMetrics: string[]) {
|
async queryNodeMetrics(nodeMetrics: string[]) {
|
||||||
if (!nodeMetrics.length) {
|
if (!nodeMetrics.length) {
|
||||||
this.setNodeMetricValue({});
|
this.setNodeMetricValue({});
|
||||||
@ -369,6 +412,28 @@ export const topologyStore = defineStore({
|
|||||||
ElMessage.error(res.errors);
|
ElMessage.error(res.errors);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async queryNodeExpressions(expressions: string[]) {
|
||||||
|
if (!expressions.length) {
|
||||||
|
this.setNodeMetricValue({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.nodes.length) {
|
||||||
|
this.setNodeMetricValue({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { getExpressionQuery, handleExpressionValues } = useQueryTopologyExpressionsProcessor(
|
||||||
|
expressions,
|
||||||
|
this.nodes,
|
||||||
|
);
|
||||||
|
const param = getExpressionQuery();
|
||||||
|
const res = await this.getNodeExpressionValue(param);
|
||||||
|
if (res.errors) {
|
||||||
|
ElMessage.error(res.errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const metrics = handleExpressionValues(res.data);
|
||||||
|
this.setNodeMetricValue(metrics);
|
||||||
|
},
|
||||||
async getLegendMetrics(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
|
async getLegendMetrics(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
|
||||||
const res: AxiosResponse = await query(param);
|
const res: AxiosResponse = await query(param);
|
||||||
|
|
||||||
|
2
src/types/components.d.ts
vendored
2
src/types/components.d.ts
vendored
@ -36,6 +36,7 @@ declare module '@vue/runtime-core' {
|
|||||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||||
ElTable: typeof import('element-plus/es')['ElTable']
|
ElTable: typeof import('element-plus/es')['ElTable']
|
||||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||||
|
ElTag: typeof import('element-plus/es')['ElTag']
|
||||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||||
Graph: typeof import('./../components/Graph.vue')['default']
|
Graph: typeof import('./../components/Graph.vue')['default']
|
||||||
Icon: typeof import('./../components/Icon.vue')['default']
|
Icon: typeof import('./../components/Icon.vue')['default']
|
||||||
@ -45,6 +46,7 @@ declare module '@vue/runtime-core' {
|
|||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
Selector: typeof import('./../components/Selector.vue')['default']
|
Selector: typeof import('./../components/Selector.vue')['default']
|
||||||
SelectSingle: typeof import('./../components/SelectSingle.vue')['default']
|
SelectSingle: typeof import('./../components/SelectSingle.vue')['default']
|
||||||
|
Tags: typeof import('./../components/Tags.vue')['default']
|
||||||
TimePicker: typeof import('./../components/TimePicker.vue')['default']
|
TimePicker: typeof import('./../components/TimePicker.vue')['default']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,7 +160,7 @@ limitations under the License. -->
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const dashboardStore = useDashboardStore();
|
const dashboardStore = useDashboardStore();
|
||||||
const isExpression = ref<boolean>(dashboardStore.selectedGrid.metricMode === MetricModes.Expression ? true : false);
|
const isExpression = ref<boolean>(dashboardStore.selectedGrid.metricMode === MetricModes.Expression);
|
||||||
const metrics = computed(
|
const metrics = computed(
|
||||||
() => (isExpression.value ? dashboardStore.selectedGrid.expressions : dashboardStore.selectedGrid.metrics) || [],
|
() => (isExpression.value ? dashboardStore.selectedGrid.expressions : dashboardStore.selectedGrid.metrics) || [],
|
||||||
);
|
);
|
||||||
|
@ -139,7 +139,7 @@ limitations under the License. -->
|
|||||||
import { useSelectorStore } from "@/store/modules/selectors";
|
import { useSelectorStore } from "@/store/modules/selectors";
|
||||||
import { useTopologyStore } from "@/store/modules/topology";
|
import { useTopologyStore } from "@/store/modules/topology";
|
||||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
import { EntityType, DepthList } from "../../../data";
|
import { EntityType, DepthList, MetricModes } from "../../../data";
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import Settings from "./Settings.vue";
|
import Settings from "./Settings.vue";
|
||||||
@ -153,6 +153,7 @@ limitations under the License. -->
|
|||||||
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
|
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
|
||||||
import { layout, circleIntersection, computeCallPos } from "./utils/layout";
|
import { layout, circleIntersection, computeCallPos } from "./utils/layout";
|
||||||
import zoom from "../../components/utils/zoom";
|
import zoom from "../../components/utils/zoom";
|
||||||
|
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
|
||||||
|
|
||||||
/*global Nullable, defineProps */
|
/*global Nullable, defineProps */
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -220,19 +221,27 @@ limitations under the License. -->
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function update() {
|
async function update() {
|
||||||
topologyStore.queryNodeMetrics(settings.value.nodeMetrics || []);
|
if (settings.value.metricMode === MetricModes.Expression) {
|
||||||
topologyStore.getLinkClientMetrics(settings.value.linkClientMetrics || []);
|
topologyStore.queryNodeExpressions(settings.value.nodeExpressions || []);
|
||||||
topologyStore.getLinkServerMetrics(settings.value.linkServerMetrics || []);
|
topologyStore.getLinkExpressions(settings.value.linkClientExpressions || []);
|
||||||
|
topologyStore.getLinkExpressions(settings.value.linkServerExpressions || []);
|
||||||
|
} else {
|
||||||
|
topologyStore.queryNodeMetrics(settings.value.nodeMetrics || []);
|
||||||
|
topologyStore.getLinkClientMetrics(settings.value.linkClientMetrics || []);
|
||||||
|
topologyStore.getLinkServerMetrics(settings.value.linkServerMetrics || []);
|
||||||
|
}
|
||||||
|
|
||||||
window.addEventListener("resize", resize);
|
window.addEventListener("resize", resize);
|
||||||
await initLegendMetrics();
|
await initLegendMetrics();
|
||||||
draw();
|
draw();
|
||||||
tooltip.value = d3.select("#tooltip");
|
tooltip.value = d3.select("#tooltip");
|
||||||
setNodeTools(settings.value.nodeDashboard);
|
setNodeTools(settings.value.nodeDashboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw() {
|
function draw() {
|
||||||
const node = findMostFrequent(topologyStore.calls);
|
const node = findMostFrequent(topologyStore.calls);
|
||||||
const levels = [];
|
const levels = [];
|
||||||
const nodes = topologyStore.nodes.sort((a: Node, b: Node) => {
|
const nodes = JSON.parse(JSON.stringify(topologyStore.nodes)).sort((a: Node, b: Node) => {
|
||||||
if (a.name.toLowerCase() < b.name.toLowerCase()) {
|
if (a.name.toLowerCase() < b.name.toLowerCase()) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -352,18 +361,49 @@ limitations under the License. -->
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function initLegendMetrics() {
|
async function initLegendMetrics() {
|
||||||
const ids = topologyStore.nodes.map((d: Node) => d.id);
|
if (!topologyStore.nodes.length) {
|
||||||
const names = props.config.legend.map((d: any) => d.name);
|
return;
|
||||||
if (names.length && ids.length) {
|
}
|
||||||
const param = await useQueryTopologyMetrics(names, ids);
|
if (settings.value.metricMode === MetricModes.Expression) {
|
||||||
const res = await topologyStore.getLegendMetrics(param);
|
const expression = props.config.legendMQE && props.config.legendMQE.expression;
|
||||||
|
if (!expression) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { getExpressionQuery } = useQueryTopologyExpressionsProcessor([expression], topologyStore.nodes);
|
||||||
|
const param = getExpressionQuery();
|
||||||
|
const res = await topologyStore.getNodeExpressionValue(param);
|
||||||
if (res.errors) {
|
if (res.errors) {
|
||||||
ElMessage.error(res.errors);
|
ElMessage.error(res.errors);
|
||||||
|
} else {
|
||||||
|
topologyStore.setLegendValues([expression], res.data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const names = props.config.legend.map((d: any) => d.name);
|
||||||
|
if (!names.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ids = topologyStore.nodes.map((d: Node) => d.id);
|
||||||
|
if (ids.length) {
|
||||||
|
const param = await useQueryTopologyMetrics(names, ids);
|
||||||
|
const res = await topologyStore.getLegendMetrics(param);
|
||||||
|
if (res.errors) {
|
||||||
|
ElMessage.error(res.errors);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNodeStatus(d: any) {
|
function getNodeStatus(d: any) {
|
||||||
const legend = settings.value.legend;
|
const { legend, legendMQE } = settings.value;
|
||||||
|
if (settings.value.metricMode === MetricModes.Expression) {
|
||||||
|
if (!legendMQE) {
|
||||||
|
return icons.CUBE;
|
||||||
|
}
|
||||||
|
if (!legendMQE.expression) {
|
||||||
|
return icons.CUBE;
|
||||||
|
}
|
||||||
|
return Number(d[legendMQE.expression]) && d.isReal ? icons.CUBEERROR : icons.CUBE;
|
||||||
|
}
|
||||||
if (!legend) {
|
if (!legend) {
|
||||||
return icons.CUBE;
|
return icons.CUBE;
|
||||||
}
|
}
|
||||||
@ -381,7 +421,10 @@ limitations under the License. -->
|
|||||||
return c && d.isReal ? icons.CUBEERROR : icons.CUBE;
|
return c && d.isReal ? icons.CUBEERROR : icons.CUBE;
|
||||||
}
|
}
|
||||||
function showNodeTip(event: MouseEvent, data: Node) {
|
function showNodeTip(event: MouseEvent, data: Node) {
|
||||||
const nodeMetrics: string[] = settings.value.nodeMetrics || [];
|
const nodeMetrics: string[] =
|
||||||
|
(settings.value.metricMode === MetricModes.Expression
|
||||||
|
? settings.value.nodeExpressions
|
||||||
|
: settings.value.nodeMetrics) || [];
|
||||||
const nodeMetricConfig = settings.value.nodeMetricConfig || [];
|
const nodeMetricConfig = settings.value.nodeMetricConfig || [];
|
||||||
const html = nodeMetrics.map((m, index) => {
|
const html = nodeMetrics.map((m, index) => {
|
||||||
const metric =
|
const metric =
|
||||||
@ -404,10 +447,16 @@ limitations under the License. -->
|
|||||||
.html(tipHtml);
|
.html(tipHtml);
|
||||||
}
|
}
|
||||||
function showLinkTip(event: MouseEvent, data: Call) {
|
function showLinkTip(event: MouseEvent, data: Call) {
|
||||||
const linkClientMetrics: string[] = settings.value.linkClientMetrics || [];
|
const linkClientMetrics: string[] =
|
||||||
|
settings.value.metricMode === MetricModes.Expression
|
||||||
|
? settings.value.linkClientExpressions
|
||||||
|
: settings.value.linkClientMetrics || [];
|
||||||
const linkServerMetricConfig: MetricConfigOpt[] = settings.value.linkServerMetricConfig || [];
|
const linkServerMetricConfig: MetricConfigOpt[] = settings.value.linkServerMetricConfig || [];
|
||||||
const linkClientMetricConfig: MetricConfigOpt[] = settings.value.linkClientMetricConfig || [];
|
const linkClientMetricConfig: MetricConfigOpt[] = settings.value.linkClientMetricConfig || [];
|
||||||
const linkServerMetrics: string[] = settings.value.linkServerMetrics || [];
|
const linkServerMetrics: string[] =
|
||||||
|
settings.value.metricMode === MetricModes.Expression
|
||||||
|
? settings.value.linkServerExpressions
|
||||||
|
: settings.value.linkServerMetrics || [];
|
||||||
const htmlServer = linkServerMetrics.map((m, index) => {
|
const htmlServer = linkServerMetrics.map((m, index) => {
|
||||||
const metric = topologyStore.linkServerMetrics[m].values.find(
|
const metric = topologyStore.linkServerMetrics[m].values.find(
|
||||||
(val: { id: string; value: unknown }) => val.id === data.id,
|
(val: { id: string; value: unknown }) => val.id === data.id,
|
||||||
@ -667,7 +716,7 @@ limitations under the License. -->
|
|||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
color: $disabled-color;
|
color: $disabled-color;
|
||||||
border: 1px solid $disabled-color;
|
border: 1px solid #eee;
|
||||||
background-color: $theme-background;
|
background-color: $theme-background;
|
||||||
box-shadow: #eee 1px 2px 10px;
|
box-shadow: #eee 1px 2px 10px;
|
||||||
transition: all 0.5ms linear;
|
transition: all 0.5ms linear;
|
||||||
|
@ -15,7 +15,9 @@ limitations under the License. -->
|
|||||||
<template>
|
<template>
|
||||||
<div class="config-panel">
|
<div class="config-panel">
|
||||||
<div class="item mb-10">
|
<div class="item mb-10">
|
||||||
<span class="label">{{ t("metrics") }}</span>
|
<span class="label">{{
|
||||||
|
t(dashboardStore.selectedGrid.metricMode === MetricModes.General ? "metrics" : "expressions")
|
||||||
|
}}</span>
|
||||||
<SelectSingle :value="currentMetric" :options="metrics" @change="changeMetric" class="selectors" />
|
<SelectSingle :value="currentMetric" :options="metrics" @change="changeMetric" class="selectors" />
|
||||||
</div>
|
</div>
|
||||||
<div class="item mb-10">
|
<div class="item mb-10">
|
||||||
@ -38,7 +40,7 @@ limitations under the License. -->
|
|||||||
@change="changeConfigs({ label: currentConfig.label })"
|
@change="changeConfigs({ label: currentConfig.label })"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="item mb-10">
|
<div class="item mb-10" v-if="dashboardStore.selectedGrid.metricMode === MetricModes.General">
|
||||||
<span class="label">{{ t("aggregation") }}</span>
|
<span class="label">{{ t("aggregation") }}</span>
|
||||||
<SelectSingle
|
<SelectSingle
|
||||||
:value="currentConfig.calculation"
|
:value="currentConfig.calculation"
|
||||||
@ -54,17 +56,12 @@ limitations under the License. -->
|
|||||||
import { ref, computed, watch } from "vue";
|
import { ref, computed, watch } from "vue";
|
||||||
import type { PropType } from "vue";
|
import type { PropType } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { CalculationOpts } from "../../../data";
|
import { CalculationOpts, MetricModes } from "../../../data";
|
||||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
import type { MetricConfigOpt } from "@/types/dashboard";
|
|
||||||
import type { Option } from "element-plus/es/components/select-v2/src/select.types";
|
import type { Option } from "element-plus/es/components/select-v2/src/select.types";
|
||||||
|
|
||||||
/*global defineEmits, defineProps */
|
/*global defineEmits, defineProps */
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
currentMetricConfig: {
|
|
||||||
type: Object as PropType<MetricConfigOpt>,
|
|
||||||
default: () => ({ unit: "" }),
|
|
||||||
},
|
|
||||||
type: { type: String, default: "" },
|
type: { type: String, default: "" },
|
||||||
metrics: { type: Array as PropType<string[]>, default: () => [] },
|
metrics: { type: Array as PropType<string[]>, default: () => [] },
|
||||||
});
|
});
|
||||||
|
@ -22,6 +22,7 @@ limitations under the License. -->
|
|||||||
:options="DepthList"
|
:options="DepthList"
|
||||||
placeholder="Select a option"
|
placeholder="Select a option"
|
||||||
@change="changeDepth"
|
@change="changeDepth"
|
||||||
|
size="small"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span class="switch-icon ml-5" title="Settings" @click="setConfig" v-if="dashboardStore.editMode">
|
<span class="switch-icon ml-5" title="Settings" @click="setConfig" v-if="dashboardStore.editMode">
|
||||||
@ -68,7 +69,7 @@ limitations under the License. -->
|
|||||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
import { useSelectorStore } from "@/store/modules/selectors";
|
import { useSelectorStore } from "@/store/modules/selectors";
|
||||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||||
import { EntityType, DepthList } from "../../../data";
|
import { EntityType, DepthList, MetricModes } from "../../../data";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import Sankey from "./Sankey.vue";
|
import Sankey from "./Sankey.vue";
|
||||||
import Settings from "./Settings.vue";
|
import Settings from "./Settings.vue";
|
||||||
@ -118,9 +119,15 @@ limitations under the License. -->
|
|||||||
};
|
};
|
||||||
height.value = dom.height - 70;
|
height.value = dom.height - 70;
|
||||||
width.value = dom.width - 5;
|
width.value = dom.width - 5;
|
||||||
topologyStore.getLinkClientMetrics(settings.value.linkClientMetrics || []);
|
if (settings.value.metricMode === MetricModes.Expression) {
|
||||||
topologyStore.getLinkServerMetrics(settings.value.linkServerMetrics || []);
|
topologyStore.queryNodeExpressions(settings.value.nodeExpressions || []);
|
||||||
topologyStore.queryNodeMetrics(settings.value.nodeMetrics || []);
|
topologyStore.getLinkExpressions(settings.value.linkClientExpressions || []);
|
||||||
|
topologyStore.getLinkExpressions(settings.value.linkServerExpressions || []);
|
||||||
|
} else {
|
||||||
|
topologyStore.getLinkClientMetrics(settings.value.linkClientMetrics || []);
|
||||||
|
topologyStore.getLinkServerMetrics(settings.value.linkServerMetrics || []);
|
||||||
|
topologyStore.queryNodeMetrics(settings.value.nodeMetrics || []);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resize() {
|
function resize() {
|
||||||
@ -265,7 +272,6 @@ limitations under the License. -->
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.sankey {
|
.sankey {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
background-color: #333840 !important;
|
|
||||||
color: #ddd;
|
color: #ddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,7 +281,8 @@ limitations under the License. -->
|
|||||||
right: 10px;
|
right: 10px;
|
||||||
width: 400px;
|
width: 400px;
|
||||||
height: 600px;
|
height: 600px;
|
||||||
background-color: #2b3037;
|
border: 1px solid #eee;
|
||||||
|
background-color: $theme-background;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
@ -283,6 +290,7 @@ limitations under the License. -->
|
|||||||
transition: all 0.5ms linear;
|
transition: all 0.5ms linear;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
box-shadow: #eee 1px 2px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool {
|
.tool {
|
||||||
@ -299,8 +307,8 @@ limitations under the License. -->
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.5ms linear;
|
transition: all 0.5ms linear;
|
||||||
background-color: #252a2f99;
|
background: rgb(0 0 0 / 30%);
|
||||||
color: #ddd;
|
color: $text-color;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ limitations under the License. -->
|
|||||||
import type { Node, Call } from "@/types/topology";
|
import type { Node, Call } from "@/types/topology";
|
||||||
import type { MetricConfigOpt } from "@/types/dashboard";
|
import type { MetricConfigOpt } from "@/types/dashboard";
|
||||||
import { aggregation } from "@/hooks/useMetricsProcessor";
|
import { aggregation } from "@/hooks/useMetricsProcessor";
|
||||||
|
import { MetricModes } from "../../../data";
|
||||||
|
|
||||||
/*global defineEmits, defineProps */
|
/*global defineEmits, defineProps */
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -51,16 +52,16 @@ limitations under the License. -->
|
|||||||
data: topologyStore.nodes,
|
data: topologyStore.nodes,
|
||||||
links: topologyStore.calls,
|
links: topologyStore.calls,
|
||||||
label: {
|
label: {
|
||||||
color: "#fff",
|
color: "#666",
|
||||||
formatter: (param: any) => param.data.name,
|
formatter: (param: any) => param.data.name,
|
||||||
},
|
},
|
||||||
color: ["#3fe1da", "#6be6c1", "#3fcfdc", "#626c91", "#3fbcde", "#a0a7e6", "#3fa9e1", "#96dee8", "#bf99f8"],
|
color: ["#6be6c1", "#3fcfdc", "#626c91", "#3fbcde", "#a0a7e6", "#3fa9e1", "#96dee8", "#bf99f8"],
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
},
|
},
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: "source",
|
color: "source",
|
||||||
opacity: 0.12,
|
opacity: 0.3,
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
position: "bottom",
|
position: "bottom",
|
||||||
@ -75,8 +76,14 @@ limitations under the License. -->
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
function linkTooltip(data: Call) {
|
function linkTooltip(data: Call) {
|
||||||
const clientMetrics: string[] = Object.keys(topologyStore.linkClientMetrics);
|
const clientMetrics: string[] =
|
||||||
const serverMetrics: string[] = Object.keys(topologyStore.linkServerMetrics);
|
props.settings.metricMode === MetricModes.Expression
|
||||||
|
? props.settings.linkClientExpressions
|
||||||
|
: props.settings.linkClientMetrics;
|
||||||
|
const serverMetrics: string[] =
|
||||||
|
props.settings.metricMode === MetricModes.Expression
|
||||||
|
? props.settings.linkServerExpressions
|
||||||
|
: props.settings.linkServerMetrics;
|
||||||
const linkServerMetricConfig: MetricConfigOpt[] = props.settings.linkServerMetricConfig || [];
|
const linkServerMetricConfig: MetricConfigOpt[] = props.settings.linkServerMetricConfig || [];
|
||||||
const linkClientMetricConfig: MetricConfigOpt[] = props.settings.linkClientMetricConfig || [];
|
const linkClientMetricConfig: MetricConfigOpt[] = props.settings.linkClientMetricConfig || [];
|
||||||
|
|
||||||
@ -108,7 +115,10 @@ limitations under the License. -->
|
|||||||
}
|
}
|
||||||
|
|
||||||
function nodeTooltip(data: Node) {
|
function nodeTooltip(data: Node) {
|
||||||
const nodeMetrics: string[] = Object.keys(topologyStore.nodeMetricValue);
|
const nodeMetrics: string[] =
|
||||||
|
props.settings.metricMode === MetricModes.Expression
|
||||||
|
? props.settings.nodeExpressions
|
||||||
|
: props.settings.nodeMetrics;
|
||||||
const nodeMetricConfig = props.settings.nodeMetricConfig || [];
|
const nodeMetricConfig = props.settings.nodeMetricConfig || [];
|
||||||
const html = nodeMetrics.map((m, index) => {
|
const html = nodeMetrics.map((m, index) => {
|
||||||
const metric =
|
const metric =
|
||||||
|
@ -13,6 +13,17 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License. -->
|
limitations under the License. -->
|
||||||
<template>
|
<template>
|
||||||
|
<div class="mt-20">
|
||||||
|
<h5 class="title">{{ t("metricMode") }}</h5>
|
||||||
|
<el-switch
|
||||||
|
v-model="isExpression"
|
||||||
|
class="mt-5"
|
||||||
|
active-text="Expressions"
|
||||||
|
inactive-text="General"
|
||||||
|
size="small"
|
||||||
|
@change="changeMetricMode"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="link-settings">
|
<div class="link-settings">
|
||||||
<h5 class="title">{{ t("callSettings") }}</h5>
|
<h5 class="title">{{ t("callSettings") }}</h5>
|
||||||
<div class="label">{{ t("linkDashboard") }}</div>
|
<div class="label">{{ t("linkDashboard") }}</div>
|
||||||
@ -27,16 +38,34 @@ limitations under the License. -->
|
|||||||
/>
|
/>
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span>{{ t("linkServerMetrics") }}</span>
|
<span>{{ t("linkServerMetrics") }}</span>
|
||||||
<el-popover placement="left" :width="400" trigger="click" v-if="states.linkServerMetrics.length">
|
<el-popover
|
||||||
|
placement="left"
|
||||||
|
:width="400"
|
||||||
|
trigger="click"
|
||||||
|
v-if="isExpression ? states.linkServerExpressions.length : states.linkServerMetrics.length"
|
||||||
|
>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<span @click="setConfigType('linkServerMetricConfig')">
|
<span @click="setConfigType('linkServerMetricConfig')">
|
||||||
<Icon class="cp ml-5" iconName="mode_edit" size="middle" />
|
<Icon class="cp ml-5" iconName="mode_edit" size="middle" />
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<Metrics :type="configType" :metrics="states.linkServerMetrics" @update="changeLinkServerMetrics" />
|
<Metrics
|
||||||
|
:type="configType"
|
||||||
|
:metrics="isExpression ? states.linkServerExpressions : states.linkServerMetrics"
|
||||||
|
@update="updateSettings"
|
||||||
|
/>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="isExpression">
|
||||||
|
<Tags
|
||||||
|
:tags="states.linkServerExpressions"
|
||||||
|
:vertical="true"
|
||||||
|
:text="t('addExpressions')"
|
||||||
|
@change="(param) => changeLinkServerExpressions(param)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<Selector
|
<Selector
|
||||||
|
v-else
|
||||||
class="inputs"
|
class="inputs"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
:value="states.linkServerMetrics"
|
:value="states.linkServerMetrics"
|
||||||
@ -48,16 +77,34 @@ limitations under the License. -->
|
|||||||
<span v-show="dashboardStore.entity !== EntityType[2].value">
|
<span v-show="dashboardStore.entity !== EntityType[2].value">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span>{{ t("linkClientMetrics") }}</span>
|
<span>{{ t("linkClientMetrics") }}</span>
|
||||||
<el-popover placement="left" :width="400" trigger="click" v-if="states.linkClientMetrics.length">
|
<el-popover
|
||||||
|
placement="left"
|
||||||
|
:width="400"
|
||||||
|
trigger="click"
|
||||||
|
v-if="isExpression ? states.linkClientExpressions.length : states.linkClientMetrics.length"
|
||||||
|
>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<span @click="setConfigType('linkClientMetricConfig')">
|
<span @click="setConfigType('linkClientMetricConfig')">
|
||||||
<Icon class="cp ml-5" iconName="mode_edit" size="middle" />
|
<Icon class="cp ml-5" iconName="mode_edit" size="middle" />
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<Metrics :type="configType" :metrics="states.linkClientMetrics" @update="changeLinkClientMetrics" />
|
<Metrics
|
||||||
|
:type="configType"
|
||||||
|
:metrics="isExpression ? states.linkClientExpressions : states.linkClientMetrics"
|
||||||
|
@update="updateSettings"
|
||||||
|
/>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="isExpression">
|
||||||
|
<Tags
|
||||||
|
:tags="states.linkClientExpressions"
|
||||||
|
:vertical="true"
|
||||||
|
:text="t('addExpressions')"
|
||||||
|
@change="(param) => changeLinkClientExpressions(param)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<Selector
|
<Selector
|
||||||
|
v-else
|
||||||
class="inputs"
|
class="inputs"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
:value="states.linkClientMetrics"
|
:value="states.linkClientMetrics"
|
||||||
@ -110,16 +157,34 @@ limitations under the License. -->
|
|||||||
</div>
|
</div>
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span>{{ t("nodeMetrics") }}</span>
|
<span>{{ t("nodeMetrics") }}</span>
|
||||||
<el-popover placement="left" :width="400" trigger="click" v-if="states.nodeMetrics.length">
|
<el-popover
|
||||||
|
placement="left"
|
||||||
|
:width="400"
|
||||||
|
trigger="click"
|
||||||
|
v-if="isExpression ? states.nodeExpressions.length : states.nodeMetrics.length"
|
||||||
|
>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<span @click="setConfigType('nodeMetricConfig')">
|
<span @click="setConfigType('nodeMetricConfig')">
|
||||||
<Icon class="cp ml-5" iconName="mode_edit" size="middle" />
|
<Icon class="cp ml-5" iconName="mode_edit" size="middle" />
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<Metrics :type="configType" :metrics="states.nodeMetrics" @update="changeNodeMetrics" />
|
<Metrics
|
||||||
|
:type="configType"
|
||||||
|
:metrics="isExpression ? states.nodeExpressions : states.nodeMetrics"
|
||||||
|
@update="updateSettings"
|
||||||
|
/>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="isExpression">
|
||||||
|
<Tags
|
||||||
|
:tags="states.nodeExpressions"
|
||||||
|
:vertical="true"
|
||||||
|
:text="t('addExpressions')"
|
||||||
|
@change="(param) => changeNodeExpressions(param)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<Selector
|
<Selector
|
||||||
|
v-else
|
||||||
class="inputs"
|
class="inputs"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
:value="states.nodeMetrics"
|
:value="states.nodeMetrics"
|
||||||
@ -131,8 +196,26 @@ limitations under the License. -->
|
|||||||
</div>
|
</div>
|
||||||
<div class="legend-settings" v-show="isService">
|
<div class="legend-settings" v-show="isService">
|
||||||
<h5 class="title">{{ t("legendSettings") }}</h5>
|
<h5 class="title">{{ t("legendSettings") }}</h5>
|
||||||
<div class="label">{{ t("conditions") }}</div>
|
<span v-if="isExpression">
|
||||||
<div v-for="(metric, index) of legend.metric" :key="metric.name + index">
|
<div class="label">Healthy Description</div>
|
||||||
|
<el-input v-model="description.healthy" placeholder="Please input description" size="small" class="mt-5" />
|
||||||
|
</span>
|
||||||
|
<div class="label">
|
||||||
|
<span>{{ t(isExpression ? "unhealthyExpression" : "conditions") }}</span>
|
||||||
|
<el-tooltip
|
||||||
|
class="cp"
|
||||||
|
v-if="isExpression"
|
||||||
|
content="The node would be red to indicate unhealthy status when the expression return greater than 0"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<Icon class="icon-help ml-5" iconName="help" size="small" />
|
||||||
|
</span>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div v-if="isExpression">
|
||||||
|
<el-input v-model="legendMQE.expression" placeholder="Please input a expression" size="small" class="inputs" />
|
||||||
|
</div>
|
||||||
|
<div v-for="(metric, index) of legend" :key="index" v-else>
|
||||||
<Selector
|
<Selector
|
||||||
class="item"
|
class="item"
|
||||||
:value="metric.name"
|
:value="metric.name"
|
||||||
@ -163,14 +246,16 @@ limitations under the License. -->
|
|||||||
class="cp"
|
class="cp"
|
||||||
iconName="add_circle_outlinecontrol_point"
|
iconName="add_circle_outlinecontrol_point"
|
||||||
size="middle"
|
size="middle"
|
||||||
v-show="index === legend.metric.length - 1 && legend.metric.length < 5"
|
v-show="index === legend.length - 1 && legend.length < 5"
|
||||||
@click="addMetric"
|
@click="addMetric"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<div v-show="index !== legend.metric.length - 1">&&</div>
|
<div v-show="index !== legend.length - 1">&&</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="label">Healthy Description</div>
|
<span v-if="!isExpression">
|
||||||
<el-input v-model="description.healthy" placeholder="Please input description" size="small" class="mt-5" />
|
<div class="label">Healthy Description</div>
|
||||||
|
<el-input v-model="description.healthy" placeholder="Please input description" size="small" class="mt-5" />
|
||||||
|
</span>
|
||||||
<div class="label">Unhealthy Description</div>
|
<div class="label">Unhealthy Description</div>
|
||||||
<el-input v-model="description.unhealthy" placeholder="Please input description" size="small" class="mt-5" />
|
<el-input v-model="description.unhealthy" placeholder="Please input description" size="small" class="mt-5" />
|
||||||
<el-button @click="setLegend" class="legend-btn" size="small" type="primary">
|
<el-button @click="setLegend" class="legend-btn" size="small" type="primary">
|
||||||
@ -184,12 +269,20 @@ limitations under the License. -->
|
|||||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||||
import { useTopologyStore } from "@/store/modules/topology";
|
import { useTopologyStore } from "@/store/modules/topology";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import { MetricCatalog, ScopeType, MetricConditions } from "../../../data";
|
import {
|
||||||
|
MetricCatalog,
|
||||||
|
ScopeType,
|
||||||
|
MetricConditions,
|
||||||
|
EntityType,
|
||||||
|
LegendOpt,
|
||||||
|
MetricsType,
|
||||||
|
MetricModes,
|
||||||
|
} from "../../../data";
|
||||||
import type { Option } from "@/types/app";
|
import type { Option } from "@/types/app";
|
||||||
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
|
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
|
||||||
|
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
|
||||||
import type { Node } from "@/types/topology";
|
import type { Node } from "@/types/topology";
|
||||||
import type { DashboardItem, MetricConfigOpt } from "@/types/dashboard";
|
import type { DashboardItem, MetricConfigOpt } from "@/types/dashboard";
|
||||||
import { EntityType, LegendOpt, MetricsType } from "../../../data";
|
|
||||||
import Metrics from "./Metrics.vue";
|
import Metrics from "./Metrics.vue";
|
||||||
|
|
||||||
/*global defineEmits */
|
/*global defineEmits */
|
||||||
@ -198,6 +291,7 @@ limitations under the License. -->
|
|||||||
const dashboardStore = useDashboardStore();
|
const dashboardStore = useDashboardStore();
|
||||||
const topologyStore = useTopologyStore();
|
const topologyStore = useTopologyStore();
|
||||||
const { selectedGrid } = dashboardStore;
|
const { selectedGrid } = dashboardStore;
|
||||||
|
const isExpression = ref<boolean>(dashboardStore.selectedGrid.metricMode === MetricModes.Expression);
|
||||||
const nodeDashboard =
|
const nodeDashboard =
|
||||||
selectedGrid.nodeDashboard && selectedGrid.nodeDashboard.length ? selectedGrid.nodeDashboard : "";
|
selectedGrid.nodeDashboard && selectedGrid.nodeDashboard.length ? selectedGrid.nodeDashboard : "";
|
||||||
const isService = [EntityType[0].value, EntityType[1].value].includes(dashboardStore.entity);
|
const isService = [EntityType[0].value, EntityType[1].value].includes(dashboardStore.entity);
|
||||||
@ -220,6 +314,9 @@ limitations under the License. -->
|
|||||||
linkMetricList: Option[];
|
linkMetricList: Option[];
|
||||||
linkDashboards: (DashboardItem & { label: string; value: string })[];
|
linkDashboards: (DashboardItem & { label: string; value: string })[];
|
||||||
nodeDashboards: (DashboardItem & { label: string; value: string })[];
|
nodeDashboards: (DashboardItem & { label: string; value: string })[];
|
||||||
|
linkServerExpressions: string[];
|
||||||
|
linkClientExpressions: string[];
|
||||||
|
nodeExpressions: string[];
|
||||||
}>({
|
}>({
|
||||||
linkDashboard: selectedGrid.linkDashboard || "",
|
linkDashboard: selectedGrid.linkDashboard || "",
|
||||||
nodeDashboard: selectedGrid.nodeDashboard || [],
|
nodeDashboard: selectedGrid.nodeDashboard || [],
|
||||||
@ -230,13 +327,15 @@ limitations under the License. -->
|
|||||||
linkMetricList: [],
|
linkMetricList: [],
|
||||||
linkDashboards: [],
|
linkDashboards: [],
|
||||||
nodeDashboards: [],
|
nodeDashboards: [],
|
||||||
|
linkServerExpressions: selectedGrid.linkServerExpressions || [],
|
||||||
|
linkClientExpressions: selectedGrid.linkClientExpressions || [],
|
||||||
|
nodeExpressions: selectedGrid.nodeExpressions || [],
|
||||||
});
|
});
|
||||||
const l = selectedGrid.legend && selectedGrid.legend.length;
|
const l = selectedGrid.legend && selectedGrid.legend.length;
|
||||||
const legend = reactive<{
|
const legend = ref<{ name: string; condition: string; value: string }[]>(
|
||||||
metric: { name: string; condition: string; value: string }[];
|
l ? selectedGrid.legend : [{ name: "", condition: "", value: "" }],
|
||||||
}>({
|
);
|
||||||
metric: l ? selectedGrid.legend : [{ name: "", condition: "", value: "" }],
|
const legendMQE = ref<{ expression: string }>(selectedGrid.legendMQE || { expression: "" });
|
||||||
});
|
|
||||||
const configType = ref<string>("");
|
const configType = ref<string>("");
|
||||||
const description = reactive<any>(selectedGrid.description || {});
|
const description = reactive<any>(selectedGrid.description || {});
|
||||||
|
|
||||||
@ -285,18 +384,35 @@ limitations under the License. -->
|
|||||||
}
|
}
|
||||||
async function setLegend() {
|
async function setLegend() {
|
||||||
updateSettings();
|
updateSettings();
|
||||||
const ids = topologyStore.nodes.map((d: Node) => d.id);
|
if (isExpression.value) {
|
||||||
const names = dashboardStore.selectedGrid.legend.map((d: any) => d.name);
|
const expression = dashboardStore.selectedGrid.legendMQE && dashboardStore.selectedGrid.legendMQE.expression;
|
||||||
if (!names.length) {
|
if (!expression) {
|
||||||
emit("updateNodes");
|
emit("updateNodes");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const param = await useQueryTopologyMetrics(names, ids);
|
const { getExpressionQuery } = useQueryTopologyExpressionsProcessor([expression], topologyStore.nodes);
|
||||||
const res = await topologyStore.getLegendMetrics(param);
|
const param = getExpressionQuery();
|
||||||
|
const res = await topologyStore.getNodeExpressionValue(param);
|
||||||
|
if (res.errors) {
|
||||||
|
ElMessage.error(res.errors);
|
||||||
|
} else {
|
||||||
|
topologyStore.setLegendValues([expression], res.data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const names = dashboardStore.selectedGrid.legend.map((d: any) => d.name && d.condition && d.value);
|
||||||
|
if (!names.length) {
|
||||||
|
emit("updateNodes");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ids = topologyStore.nodes.map((d: Node) => d.id);
|
||||||
|
const param = await useQueryTopologyMetrics(names, ids);
|
||||||
|
const res = await topologyStore.getLegendMetrics(param);
|
||||||
|
|
||||||
if (res.errors) {
|
if (res.errors) {
|
||||||
ElMessage.error(res.errors);
|
ElMessage.error(res.errors);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emit("updateNodes");
|
emit("updateNodes");
|
||||||
}
|
}
|
||||||
function changeNodeDashboard(opt: any) {
|
function changeNodeDashboard(opt: any) {
|
||||||
@ -308,7 +424,7 @@ limitations under the License. -->
|
|||||||
updateSettings();
|
updateSettings();
|
||||||
}
|
}
|
||||||
function changeLegend(type: string, opt: any, index: number) {
|
function changeLegend(type: string, opt: any, index: number) {
|
||||||
(legend.metric[index] as any)[type] = opt[0].value || opt;
|
(legend.value[index] as any)[type] = opt[0].value || opt;
|
||||||
}
|
}
|
||||||
function changeScope(index: number, opt: Option[] | any) {
|
function changeScope(index: number, opt: Option[] | any) {
|
||||||
items[index].scope = opt[0].value;
|
items[index].scope = opt[0].value;
|
||||||
@ -340,7 +456,13 @@ limitations under the License. -->
|
|||||||
updateSettings();
|
updateSettings();
|
||||||
}
|
}
|
||||||
function updateSettings(metricConfig?: { [key: string]: MetricConfigOpt[] }) {
|
function updateSettings(metricConfig?: { [key: string]: MetricConfigOpt[] }) {
|
||||||
const metrics = legend.metric.filter((d: any) => d.name && d.value && d.condition);
|
let metrics = [];
|
||||||
|
if (isExpression.value) {
|
||||||
|
metrics = legend.value.filter((d: any) => d.name);
|
||||||
|
} else {
|
||||||
|
metrics = legend.value.filter((d: any) => d.name && d.value && d.condition);
|
||||||
|
}
|
||||||
|
|
||||||
const param = {
|
const param = {
|
||||||
...dashboardStore.selectedGrid,
|
...dashboardStore.selectedGrid,
|
||||||
linkDashboard: states.linkDashboard,
|
linkDashboard: states.linkDashboard,
|
||||||
@ -350,7 +472,12 @@ limitations under the License. -->
|
|||||||
linkServerMetrics: states.linkServerMetrics,
|
linkServerMetrics: states.linkServerMetrics,
|
||||||
linkClientMetrics: states.linkClientMetrics,
|
linkClientMetrics: states.linkClientMetrics,
|
||||||
nodeMetrics: states.nodeMetrics,
|
nodeMetrics: states.nodeMetrics,
|
||||||
|
linkServerExpressions: states.linkServerExpressions,
|
||||||
|
linkClientExpressions: states.linkClientExpressions,
|
||||||
|
nodeExpressions: states.nodeExpressions,
|
||||||
|
metricMode: isExpression.value ? MetricModes.Expression : MetricModes.General,
|
||||||
legend: metrics,
|
legend: metrics,
|
||||||
|
legendMQE: legendMQE.value,
|
||||||
...metricConfig,
|
...metricConfig,
|
||||||
description,
|
description,
|
||||||
};
|
};
|
||||||
@ -378,6 +505,30 @@ limitations under the License. -->
|
|||||||
}
|
}
|
||||||
topologyStore.getLinkServerMetrics(states.linkServerMetrics);
|
topologyStore.getLinkServerMetrics(states.linkServerMetrics);
|
||||||
}
|
}
|
||||||
|
function changeLinkServerExpressions(param: string[]) {
|
||||||
|
if (!isExpression.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
states.linkServerExpressions = param;
|
||||||
|
updateSettings();
|
||||||
|
if (!states.linkServerExpressions.length) {
|
||||||
|
topologyStore.setLinkServerMetrics({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
topologyStore.getLinkExpressions(states.linkServerExpressions, "SERVER");
|
||||||
|
}
|
||||||
|
function changeLinkClientExpressions(param: string[]) {
|
||||||
|
if (!isExpression.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
states.linkClientExpressions = param;
|
||||||
|
updateSettings();
|
||||||
|
if (!states.linkClientExpressions.length) {
|
||||||
|
topologyStore.changeLinkClientMetrics({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
topologyStore.getLinkExpressions(states.linkClientExpressions, "CLIENT");
|
||||||
|
}
|
||||||
function updateLinkClientMetrics(options: Option[] | any) {
|
function updateLinkClientMetrics(options: Option[] | any) {
|
||||||
const opt = options.map((d: Option) => d.value);
|
const opt = options.map((d: Option) => d.value);
|
||||||
const index = states.linkClientMetrics.findIndex((d: any) => !opt.includes(d));
|
const index = states.linkClientMetrics.findIndex((d: any) => !opt.includes(d));
|
||||||
@ -419,18 +570,49 @@ limitations under the License. -->
|
|||||||
topologyStore.queryNodeMetrics(states.nodeMetrics);
|
topologyStore.queryNodeMetrics(states.nodeMetrics);
|
||||||
}
|
}
|
||||||
function deleteMetric(index: number) {
|
function deleteMetric(index: number) {
|
||||||
if (legend.metric.length === 1) {
|
if (legend.value.length === 1) {
|
||||||
legend.metric = [{ name: "", condition: "", value: "" }];
|
legend.value = [{ name: "", condition: "", value: "" }];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
legend.metric.splice(index, 1);
|
legend.value.splice(index, 1);
|
||||||
}
|
}
|
||||||
function addMetric() {
|
function addMetric() {
|
||||||
legend.metric.push({ name: "", condition: "", value: "" });
|
legend.value.push({ name: "", condition: "", value: "" });
|
||||||
}
|
}
|
||||||
function setConfigType(type: string) {
|
function setConfigType(type: string) {
|
||||||
configType.value = type;
|
configType.value = type;
|
||||||
}
|
}
|
||||||
|
function changeNodeExpressions(param: string[]) {
|
||||||
|
if (!isExpression.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
states.nodeExpressions = param;
|
||||||
|
updateSettings();
|
||||||
|
if (!states.nodeExpressions.length) {
|
||||||
|
topologyStore.setNodeMetricValue({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
topologyStore.queryNodeExpressions(states.nodeExpressions);
|
||||||
|
}
|
||||||
|
function changeMetricMode() {
|
||||||
|
legend.value = [{ name: "", condition: "", value: "" }];
|
||||||
|
const config = {
|
||||||
|
linkServerMetricConfig: [],
|
||||||
|
linkClientMetricConfig: [],
|
||||||
|
nodeMetricConfig: [],
|
||||||
|
};
|
||||||
|
if (isExpression.value) {
|
||||||
|
states.linkServerMetrics = [];
|
||||||
|
states.linkClientMetrics = [];
|
||||||
|
states.nodeMetrics = [];
|
||||||
|
} else {
|
||||||
|
states.linkServerExpressions = [];
|
||||||
|
states.linkClientExpressions = [];
|
||||||
|
states.nodeExpressions = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSettings(config);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.link-settings {
|
.link-settings {
|
||||||
@ -442,6 +624,11 @@ limitations under the License. -->
|
|||||||
width: 355px;
|
width: 355px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.legend-inputs {
|
||||||
|
margin-top: 8px;
|
||||||
|
width: 310px;
|
||||||
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
width: 130px;
|
width: 130px;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
@ -453,13 +640,14 @@ limitations under the License. -->
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin-bottom: 0;
|
|
||||||
color: #666;
|
color: #666;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
font-size: $font-size-smaller;
|
font-size: $font-size-smaller;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-btn {
|
.legend-btn {
|
||||||
|
Loading…
Reference in New Issue
Block a user