mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-05-12 15:52:57 +00:00
Merge branch 'main' into feature-kafka-monitoring
This commit is contained in:
commit
fd6880ce9d
@ -44,6 +44,7 @@ module.exports = {
|
||||
"workflow",
|
||||
"types",
|
||||
"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 Radio from "./Radio.vue";
|
||||
import SelectSingle from "./SelectSingle.vue";
|
||||
import Tags from "./Tags.vue";
|
||||
import VueGridLayout from "vue-grid-layout";
|
||||
|
||||
const components: Indexable = {
|
||||
@ -31,6 +32,7 @@ const components: Indexable = {
|
||||
Graph,
|
||||
Radio,
|
||||
SelectSingle,
|
||||
Tags,
|
||||
};
|
||||
const componentsName: string[] = Object.keys(components);
|
||||
|
||||
|
@ -15,13 +15,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { RespFields } from "./data";
|
||||
import { ExpressionResultType } from "@/views/dashboard/data";
|
||||
import { EntityType, ExpressionResultType } from "@/views/dashboard/data";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
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() {
|
||||
@ -312,3 +313,88 @@ export async function useExpressionsQueryPodsMetrics(
|
||||
|
||||
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 };
|
||||
}
|
||||
|
@ -14,7 +14,31 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License. -->
|
||||
<template>
|
||||
<div class="nav-bar flex-h">
|
||||
<div class="title">{{ route.name === "ViewWidget" ? "" : appStore.pageTitle || pageName }}</div>
|
||||
<el-breadcrumb
|
||||
:separator-icon="ArrowRight"
|
||||
class="title flex-h"
|
||||
v-if="pathNames.length"
|
||||
:style="{ '--el-text-color-placeholder': '#666' }"
|
||||
>
|
||||
<el-breadcrumb-item
|
||||
v-for="(path, index) in pathNames"
|
||||
:key="index"
|
||||
:replace="true"
|
||||
:to="{ path: getName(path).path || '' }"
|
||||
>
|
||||
<el-dropdown size="small" placement="bottom" :persistent="false">
|
||||
<span class="cp name">{{ getName(path).name }}</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="setName(p)" v-for="(p, index) in path" :key="index">
|
||||
<span>{{ p.name }}</span>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
<div class="title" v-else>{{ pageTitle }}</div>
|
||||
<div class="app-config">
|
||||
<span class="red" v-show="timeRange">{{ t("timeTips") }}</span>
|
||||
<TimePicker
|
||||
@ -45,20 +69,47 @@ limitations under the License. -->
|
||||
import { useI18n } from "vue-i18n";
|
||||
import timeFormat from "@/utils/timeFormat";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { MetricCatalog } from "@/views/dashboard/data";
|
||||
import type { DashboardItem } from "@/types/dashboard";
|
||||
import router from "@/router";
|
||||
import { ArrowRight } from "@element-plus/icons-vue";
|
||||
|
||||
/*global Indexable */
|
||||
const { t } = useI18n();
|
||||
const { t, te } = useI18n();
|
||||
const appStore = useAppStoreWithOut();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const route = useRoute();
|
||||
const pageName = ref<string>("");
|
||||
const pathNames = ref<{ path?: string; name: string; selected: boolean }[][]>([]);
|
||||
const timeRange = ref<number>(0);
|
||||
const pageTitle = ref<string>("");
|
||||
|
||||
resetDuration();
|
||||
getVersion();
|
||||
const setConfig = (value: string) => {
|
||||
pageName.value = value || "";
|
||||
};
|
||||
getNavPaths();
|
||||
|
||||
function getName(list: any[]) {
|
||||
return list.find((d: any) => d.selected) || {};
|
||||
}
|
||||
|
||||
function setName(item: any) {
|
||||
pathNames.value = pathNames.value.map((list: { path?: string; name: string; selected: boolean }[]) => {
|
||||
const p = list.find((i: any) => i.entity === item.entity && item.layer === i.layer && i.name === item.name);
|
||||
if (p) {
|
||||
list = list.map((d: any) => {
|
||||
d.selected = false;
|
||||
if (d.entity === item.entity && item.layer === d.layer && d.name === item.name) {
|
||||
d.selected = true;
|
||||
}
|
||||
return d;
|
||||
});
|
||||
}
|
||||
|
||||
return list;
|
||||
});
|
||||
item.path && router.push(item.path);
|
||||
}
|
||||
|
||||
function handleReload() {
|
||||
const gap = appStore.duration.end.getTime() - appStore.duration.start.getTime();
|
||||
@ -73,19 +124,138 @@ limitations under the License. -->
|
||||
}
|
||||
appStore.setDuration(timeFormat(val));
|
||||
}
|
||||
setConfig(String(route.meta.title));
|
||||
watch(
|
||||
() => route.meta.title,
|
||||
(title: unknown) => {
|
||||
setConfig(String(title));
|
||||
},
|
||||
);
|
||||
|
||||
function getNavPaths() {
|
||||
pathNames.value = [];
|
||||
pageTitle.value = "";
|
||||
const dashboard = dashboardStore.currentDashboard;
|
||||
|
||||
if (!(dashboard && dashboard.name)) {
|
||||
updateNavTitle();
|
||||
return;
|
||||
}
|
||||
const root =
|
||||
dashboardStore.dashboards.filter((d: DashboardItem) => d.isRoot && dashboard.layer === d.layer)[0] || {};
|
||||
for (const item of appStore.allMenus) {
|
||||
if (item.subItems && item.subItems.length) {
|
||||
for (const subItem of item.subItems) {
|
||||
if (subItem.layer === root.layer) {
|
||||
root.path = subItem.path;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (item.layer === root.layer) {
|
||||
root.path = item.path;
|
||||
}
|
||||
}
|
||||
}
|
||||
pathNames.value.push([{ ...root, selected: true }]);
|
||||
if (dashboard.entity === MetricCatalog.ALL) {
|
||||
return;
|
||||
}
|
||||
if (dashboard.entity === MetricCatalog.SERVICE) {
|
||||
pathNames.value.push([
|
||||
{
|
||||
name: dashboard.name,
|
||||
selected: true,
|
||||
},
|
||||
]);
|
||||
return;
|
||||
}
|
||||
const serviceDashboards = dashboardStore.dashboards.filter(
|
||||
(d: DashboardItem) => MetricCatalog.SERVICE === d.entity && dashboard.layer === d.layer,
|
||||
);
|
||||
if (!serviceDashboards.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const serviceId = route.params.serviceId;
|
||||
const list = serviceDashboards.map((d: { path: string } & DashboardItem, index: number) => {
|
||||
let path = `/dashboard/${d.layer}/${d.entity}/${d.name}`;
|
||||
if (serviceId) {
|
||||
path = `/dashboard/${d.layer}/${d.entity}/${serviceId}/${d.name}`;
|
||||
}
|
||||
const selected = index === 0;
|
||||
return {
|
||||
...d,
|
||||
path,
|
||||
selected,
|
||||
};
|
||||
});
|
||||
pathNames.value.push(list);
|
||||
const podId = route.params.podId;
|
||||
if (dashboard.entity === MetricCatalog.ENDPOINT_RELATION) {
|
||||
const endpointDashboards = dashboardStore.dashboards.filter(
|
||||
(d: DashboardItem) => MetricCatalog.ENDPOINT === d.entity && dashboard.layer === d.layer,
|
||||
);
|
||||
const list = endpointDashboards.map((d: { path: string } & DashboardItem, index: number) => {
|
||||
let path = `/dashboard/${d.layer}/${d.entity}/${d.name}`;
|
||||
if (podId) {
|
||||
path = `/dashboard/${d.layer}/${d.entity}/${serviceId}/${podId}/${d.name}`;
|
||||
}
|
||||
const selected = index === 0;
|
||||
return {
|
||||
...d,
|
||||
path,
|
||||
selected,
|
||||
};
|
||||
});
|
||||
|
||||
pathNames.value.push(list);
|
||||
}
|
||||
const destServiceId = route.params.destServiceId;
|
||||
if (dashboard.entity === MetricCatalog.SERVICE_INSTANCE_RELATION) {
|
||||
const serviceRelationDashboards = dashboardStore.dashboards.filter(
|
||||
(d: DashboardItem) => MetricCatalog.SERVICE_RELATION === d.entity && dashboard.layer === d.layer,
|
||||
);
|
||||
const list = serviceRelationDashboards.map((d: { path: string } & DashboardItem, index: number) => {
|
||||
let path = `/dashboard/${d.layer}/${d.entity}/${d.name}`;
|
||||
if (destServiceId) {
|
||||
path = `/dashboard/related/${d.layer}/${d.entity}/${serviceId}/${destServiceId}/${d.name}`;
|
||||
}
|
||||
const selected = index === 0;
|
||||
return {
|
||||
...d,
|
||||
path,
|
||||
selected,
|
||||
};
|
||||
});
|
||||
pathNames.value.push(list);
|
||||
}
|
||||
if ([MetricCatalog.Process, MetricCatalog.PROCESS_RELATION].includes(dashboard.entity)) {
|
||||
const InstanceDashboards = dashboardStore.dashboards.filter(
|
||||
(d: DashboardItem) => MetricCatalog.SERVICE_INSTANCE === d.entity && dashboard.layer === d.layer,
|
||||
);
|
||||
const list = InstanceDashboards.map((d: { path: string } & DashboardItem, index: number) => {
|
||||
let path = `/dashboard/${d.layer}/${d.entity}/${d.name}`;
|
||||
if (podId) {
|
||||
path = `/dashboard/${d.layer}/${d.entity}/${serviceId}/${podId}/${d.name}`;
|
||||
}
|
||||
const selected = index === 0;
|
||||
return {
|
||||
...d,
|
||||
path,
|
||||
selected,
|
||||
};
|
||||
});
|
||||
|
||||
pathNames.value.push(list);
|
||||
}
|
||||
pathNames.value.push([
|
||||
{
|
||||
name: dashboard.name,
|
||||
selected: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
async function getVersion() {
|
||||
const res = await appStore.fetchVersion();
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
}
|
||||
}
|
||||
|
||||
function resetDuration() {
|
||||
const { duration }: Indexable = route.params;
|
||||
if (duration) {
|
||||
@ -99,10 +269,22 @@ limitations under the License. -->
|
||||
appStore.updateUTC(d.utc);
|
||||
}
|
||||
}
|
||||
|
||||
function updateNavTitle() {
|
||||
const key = String(route.meta.i18nKey);
|
||||
pageTitle.value = te(key) ? t(key) : String(route.meta.title);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [dashboardStore.currentDashboard, route.name],
|
||||
() => {
|
||||
getNavPaths();
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.nav-bar {
|
||||
padding: 5px 10px;
|
||||
padding: 5px;
|
||||
text-align: left;
|
||||
justify-content: space-between;
|
||||
background-color: #fafbfc;
|
||||
@ -120,11 +302,17 @@ limitations under the License. -->
|
||||
.title {
|
||||
font-size: $font-size-normal;
|
||||
font-weight: 500;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.nav-tabs {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.name {
|
||||
display: inline-block;
|
||||
max-width: 250px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
@ -378,5 +378,9 @@ const msg = {
|
||||
menus: "Menus",
|
||||
saveReload: "Save and reload the page",
|
||||
document: "Documentation",
|
||||
metricMode: "Metric Mode",
|
||||
addExpressions: "Add Expressions",
|
||||
expressions: "Expression",
|
||||
unhealthyExpression: "Unhealthy Expression",
|
||||
};
|
||||
export default msg;
|
||||
|
@ -378,5 +378,9 @@ const msg = {
|
||||
menus: "Menus",
|
||||
saveReload: "Save and reload the page",
|
||||
document: "Documentation",
|
||||
metricMode: "Metric Mode",
|
||||
addExpressions: "Add Expressions",
|
||||
expressions: "Expression",
|
||||
unhealthyExpression: "Unhealthy Expression",
|
||||
};
|
||||
export default msg;
|
||||
|
@ -376,5 +376,9 @@ const msg = {
|
||||
menusManagement: "菜单",
|
||||
saveReload: "保存并重新加载页面",
|
||||
document: "文档",
|
||||
metricMode: "指标模式",
|
||||
addExpressions: "添加表达式",
|
||||
expressions: "表达式",
|
||||
unhealthyExpression: "非健康表达式",
|
||||
};
|
||||
export default msg;
|
||||
|
@ -32,7 +32,6 @@ interface AppState {
|
||||
eventStack: (() => unknown)[];
|
||||
timer: Nullable<TimeoutHandle>;
|
||||
autoRefresh: boolean;
|
||||
pageTitle: string;
|
||||
version: string;
|
||||
isMobile: boolean;
|
||||
reloadTimer: Nullable<IntervalHandle>;
|
||||
@ -53,7 +52,6 @@ export const appStore = defineStore({
|
||||
eventStack: [],
|
||||
timer: null,
|
||||
autoRefresh: false,
|
||||
pageTitle: "",
|
||||
version: "",
|
||||
isMobile: false,
|
||||
reloadTimer: null,
|
||||
@ -146,9 +144,6 @@ export const appStore = defineStore({
|
||||
setAutoRefresh(auto: boolean) {
|
||||
this.autoRefresh = auto;
|
||||
},
|
||||
setPageTitle(title: string) {
|
||||
this.pageTitle = title;
|
||||
},
|
||||
runEventStack() {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
|
@ -57,7 +57,7 @@ export const eventStore = defineStore({
|
||||
this.instances = [{ value: "", label: "All" }, ...res.data.data.pods] || [{ value: "", label: "All" }];
|
||||
return res.data;
|
||||
},
|
||||
async getEndpoints() {
|
||||
async getEndpoints(keyword: string) {
|
||||
const serviceId = useSelectorStore().currentService ? useSelectorStore().currentService.id : "";
|
||||
if (!serviceId) {
|
||||
return;
|
||||
@ -65,7 +65,7 @@ export const eventStore = defineStore({
|
||||
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
|
||||
serviceId,
|
||||
duration: useAppStoreWithOut().durationTime,
|
||||
keyword: "",
|
||||
keyword: keyword || "",
|
||||
});
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
|
@ -184,7 +184,7 @@ export const selectorStore = defineStore({
|
||||
if (isRelation) {
|
||||
this.currentDestPod = res.data.data.instance || null;
|
||||
this.destPods = [res.data.data.instance];
|
||||
return;
|
||||
return res.data;
|
||||
}
|
||||
this.currentPod = res.data.data.instance || null;
|
||||
this.pods = [res.data.data.instance];
|
||||
@ -199,16 +199,16 @@ export const selectorStore = defineStore({
|
||||
const res: AxiosResponse = await graphql.query("queryEndpoint").params({
|
||||
endpointId,
|
||||
});
|
||||
if (!res.data.errors) {
|
||||
if (isRelation) {
|
||||
this.currentDestPod = res.data.data.endpoint || null;
|
||||
this.destPods = [res.data.data.endpoint];
|
||||
return;
|
||||
}
|
||||
this.currentPod = res.data.data.endpoint || null;
|
||||
this.pods = [res.data.data.endpoint];
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
|
||||
if (isRelation) {
|
||||
this.currentDestPod = res.data.data.endpoint || null;
|
||||
this.destPods = [res.data.data.endpoint];
|
||||
return res.data;
|
||||
}
|
||||
this.currentPod = res.data.data.endpoint || null;
|
||||
this.pods = [res.data.data.endpoint];
|
||||
return res.data;
|
||||
},
|
||||
async getProcess(processId: string, isRelation?: boolean) {
|
||||
@ -222,7 +222,7 @@ export const selectorStore = defineStore({
|
||||
if (isRelation) {
|
||||
this.currentDestProcess = res.data.data.process || null;
|
||||
this.destProcesses = [res.data.data.process];
|
||||
return;
|
||||
return res.data;
|
||||
}
|
||||
this.currentProcess = res.data.data.process || null;
|
||||
this.processes = [res.data.data.process];
|
||||
|
@ -24,6 +24,7 @@ import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import type { AxiosResponse } from "axios";
|
||||
import query from "@/graphql/fetch";
|
||||
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
|
||||
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
interface MetricVal {
|
||||
@ -114,6 +115,16 @@ export const topologyStore = defineStore({
|
||||
setLinkClientMetrics(m: MetricVal) {
|
||||
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) {
|
||||
const res = await this.getServicesTopology(serviceIds);
|
||||
if (depth > 1) {
|
||||
@ -321,6 +332,15 @@ export const topologyStore = defineStore({
|
||||
this.setNodeMetricValue(res.data.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[]) {
|
||||
if (!linkClientMetrics.length) {
|
||||
this.setLinkClientMetrics({});
|
||||
@ -353,6 +373,29 @@ export const topologyStore = defineStore({
|
||||
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[]) {
|
||||
if (!nodeMetrics.length) {
|
||||
this.setNodeMetricValue({});
|
||||
@ -369,6 +412,28 @@ export const topologyStore = defineStore({
|
||||
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 } }) {
|
||||
const res: AxiosResponse = await query(param);
|
||||
|
||||
|
@ -212,6 +212,7 @@ div.vis-tooltip {
|
||||
div:has(> a.menu-title) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-input-number .el-input__inner {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
2
src/types/app.d.ts
vendored
2
src/types/app.d.ts
vendored
@ -44,6 +44,8 @@ export type EventParams = {
|
||||
dataType: string;
|
||||
value: number | any[];
|
||||
color: string;
|
||||
event: Record<string, T>;
|
||||
dataIndex: number;
|
||||
event: any;
|
||||
};
|
||||
|
||||
|
4
src/types/components.d.ts
vendored
4
src/types/components.d.ts
vendored
@ -6,6 +6,8 @@ import '@vue/runtime-core'
|
||||
declare module '@vue/runtime-core' {
|
||||
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']
|
||||
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||
@ -36,6 +38,7 @@ declare module '@vue/runtime-core' {
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
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']
|
||||
Icon: typeof import('./../components/Icon.vue')['default']
|
||||
@ -45,6 +48,7 @@ declare module '@vue/runtime-core' {
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
Selector: typeof import('./../components/Selector.vue')['default']
|
||||
SelectSingle: typeof import('./../components/SelectSingle.vue')['default']
|
||||
Tags: typeof import('./../components/Tags.vue')['default']
|
||||
TimePicker: typeof import('./../components/TimePicker.vue')['default']
|
||||
}
|
||||
}
|
||||
|
4
src/types/profile.d.ts
vendored
4
src/types/profile.d.ts
vendored
@ -55,8 +55,8 @@ export interface SegmentSpan {
|
||||
component: string;
|
||||
isError: boolean;
|
||||
layer: string;
|
||||
tags: any[];
|
||||
logs: any[];
|
||||
tags: Recordable[];
|
||||
logs: Recordable[];
|
||||
}
|
||||
|
||||
export interface ProfileTaskCreationRequest {
|
||||
|
31
src/utils/arrayAlgorithm.ts
Normal file
31
src/utils/arrayAlgorithm.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.
|
||||
*/
|
||||
|
||||
export function deduplication(arr: any, labels: string[]) {
|
||||
const map = new Map();
|
||||
for (const i of arr) {
|
||||
const key = labels
|
||||
.map((d: string) => {
|
||||
return i[d];
|
||||
})
|
||||
.join("");
|
||||
if (!map.has(i[key])) {
|
||||
map.set(i[key], i);
|
||||
}
|
||||
}
|
||||
return [...map.values()];
|
||||
}
|
@ -19,12 +19,8 @@ limitations under the License. -->
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import Header from "./alarm/Header.vue";
|
||||
import Content from "./alarm/Content.vue";
|
||||
|
||||
const appStore = useAppStoreWithOut();
|
||||
appStore.setPageTitle("Alerting");
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.alarm {
|
||||
|
@ -19,13 +19,8 @@ limitations under the License. -->
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import Header from "./event/Header.vue";
|
||||
import Content from "./event/Content.vue";
|
||||
|
||||
const appStore = useAppStoreWithOut();
|
||||
|
||||
appStore.setPageTitle("Events");
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.event {
|
||||
|
@ -24,11 +24,9 @@ limitations under the License. -->
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import Dashboard from "./dashboard/Edit.vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStoreWithOut();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const layer = ref<string>("GENERAL");
|
||||
|
||||
@ -44,7 +42,6 @@ limitations under the License. -->
|
||||
d.layer === dashboardStore.layerId && [EntityType[0].value, EntityType[1].value].includes(d.entity) && d.isRoot,
|
||||
);
|
||||
if (!item) {
|
||||
appStore.setPageTitle(dashboardStore.layer);
|
||||
dashboardStore.setCurrentDashboard(null);
|
||||
dashboardStore.setEntity(EntityType[1].value);
|
||||
return;
|
||||
|
@ -63,8 +63,6 @@ limitations under the License. -->
|
||||
function handleItems(item: MenuOptions) {
|
||||
currentItems.value = item;
|
||||
}
|
||||
|
||||
appStore.setPageTitle("Marketplace");
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.menus {
|
||||
|
@ -66,7 +66,6 @@ limitations under the License. -->
|
||||
const utcHour = ref<number>(appStore.utcHour);
|
||||
const utcMin = ref<number>(appStore.utcMin);
|
||||
|
||||
appStore.setPageTitle("Setting");
|
||||
const handleReload = () => {
|
||||
const gap = appStore.duration.end.getTime() - appStore.duration.start.getTime();
|
||||
const dates: Date[] = [
|
||||
|
@ -142,7 +142,79 @@ limitations under the License. -->
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import url("../components/style.scss");
|
||||
.timeline-table {
|
||||
padding: 30px 20px 20px 40px;
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.time-line {
|
||||
padding: 14px 30px;
|
||||
min-height: 63px;
|
||||
max-width: 132px;
|
||||
}
|
||||
|
||||
.timeline-table-i {
|
||||
padding: 10px 15px;
|
||||
border-left: 4px solid #eee;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
left: -23px;
|
||||
top: 25px;
|
||||
border-radius: 4px;
|
||||
background-color: #448dfe;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: calc(100% + 11px);
|
||||
top: 0;
|
||||
left: -20px;
|
||||
border-radius: 5px;
|
||||
background-color: #448dfe99;
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-table-i-scope {
|
||||
display: inline-block;
|
||||
padding: 0 8px;
|
||||
border: 1px solid;
|
||||
margin-top: -1px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
cursor: pointer;
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
|
||||
.keys {
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.source > span {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.source > div {
|
||||
padding-left: 120px;
|
||||
}
|
||||
|
||||
.uuid {
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
.tips {
|
||||
width: 100%;
|
||||
@ -150,4 +222,29 @@ limitations under the License. -->
|
||||
text-align: center;
|
||||
font-size: $font-size-normal;
|
||||
}
|
||||
|
||||
.alarm-detail {
|
||||
max-height: 600px;
|
||||
overflow: auto;
|
||||
|
||||
ul {
|
||||
min-height: 100px;
|
||||
overflow: auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
|
||||
> span {
|
||||
width: 160px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
border-bottom: 1px solid $disabled-color;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -71,7 +71,7 @@ limitations under the License. -->
|
||||
import { ElMessage } from "element-plus";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
|
||||
/*global defineEmits, defineProps */
|
||||
/*global defineEmits, defineProps, Recordable */
|
||||
const emit = defineEmits(["update"]);
|
||||
const props = defineProps({
|
||||
type: { type: String, default: "TRACE" },
|
||||
@ -118,7 +118,7 @@ limitations under the License. -->
|
||||
emit("update", { tagsMap, tagsList: tagsList.value });
|
||||
}
|
||||
async function fetchTagKeys() {
|
||||
let resp: any = {};
|
||||
let resp: Recordable = {};
|
||||
if (props.type === "TRACE") {
|
||||
resp = await traceStore.getTagKeys();
|
||||
} else {
|
||||
@ -137,7 +137,7 @@ limitations under the License. -->
|
||||
|
||||
async function fetchTagValues() {
|
||||
const param = tags.value.split("=")[0];
|
||||
let resp: any = {};
|
||||
let resp: Recordable = {};
|
||||
if (props.type === "TRACE") {
|
||||
resp = await traceStore.getTagValues(param);
|
||||
} else {
|
||||
|
@ -1,115 +0,0 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.timeline-table {
|
||||
padding: 30px 20px 20px 40px;
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.time-line {
|
||||
padding: 14px 30px;
|
||||
min-height: 63px;
|
||||
max-width: 132px;
|
||||
}
|
||||
|
||||
.timeline-table-i {
|
||||
padding: 10px 15px;
|
||||
border-left: 4px solid #eee;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
left: -23px;
|
||||
top: 25px;
|
||||
border-radius: 4px;
|
||||
background-color: #448dfe;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: calc(100% + 11px);
|
||||
top: 0;
|
||||
left: -20px;
|
||||
border-radius: 5px;
|
||||
background-color: #448dfe99;
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-table-i-scope {
|
||||
display: inline-block;
|
||||
padding: 0 8px;
|
||||
border: 1px solid;
|
||||
margin-top: -1px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
cursor: pointer;
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
|
||||
.alarm-detail {
|
||||
max-height: 600px;
|
||||
overflow: auto;
|
||||
|
||||
ul {
|
||||
min-height: 100px;
|
||||
overflow: auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
|
||||
> span {
|
||||
width: 160px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
border-bottom: 1px solid $disabled-color;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.keys {
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.source > span {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.source > div {
|
||||
padding-left: 120px;
|
||||
}
|
||||
|
||||
.uuid {
|
||||
width: 280px;
|
||||
}
|
@ -41,13 +41,12 @@ limitations under the License. -->
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ref, defineComponent } from "vue";
|
||||
import { ref, defineComponent, onUnmounted } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRoute } from "vue-router";
|
||||
import GridLayout from "./panel/Layout.vue";
|
||||
import Tool from "./panel/Tool.vue";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import Configuration from "./configuration";
|
||||
import type { LayoutConfig } from "@/types/dashboard";
|
||||
import WidgetLink from "./components/WidgetLink.vue";
|
||||
@ -57,7 +56,6 @@ limitations under the License. -->
|
||||
components: { ...Configuration, GridLayout, Tool, WidgetLink },
|
||||
setup() {
|
||||
const dashboardStore = useDashboardStore();
|
||||
const appStore = useAppStoreWithOut();
|
||||
const { t } = useI18n();
|
||||
const p = useRoute().params;
|
||||
const layoutKey = ref<string>(`${p.layerId}_${p.entity}_${p.name}`);
|
||||
@ -77,7 +75,6 @@ limitations under the License. -->
|
||||
const layout: any = c.configuration || {};
|
||||
|
||||
dashboardStore.setLayout(setWidgetsID(layout.children || []));
|
||||
appStore.setPageTitle(layout.name);
|
||||
if (p.entity) {
|
||||
dashboardStore.setCurrentDashboard({
|
||||
layer: p.layerId,
|
||||
@ -114,6 +111,10 @@ limitations under the License. -->
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
dashboardStore.setCurrentDashboard({});
|
||||
});
|
||||
|
||||
return {
|
||||
t,
|
||||
handleClick,
|
||||
|
@ -135,7 +135,6 @@ limitations under the License. -->
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { ElMessageBox, ElMessage } from "element-plus";
|
||||
import { ElTable } from "element-plus";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import router from "@/router";
|
||||
import type { DashboardItem, LayoutConfig } from "@/types/dashboard";
|
||||
@ -145,7 +144,6 @@ limitations under the License. -->
|
||||
|
||||
/*global Nullable*/
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStoreWithOut();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const pageSize = 20;
|
||||
const dashboards = ref<DashboardItem[]>([]);
|
||||
@ -157,7 +155,6 @@ limitations under the License. -->
|
||||
const multipleSelection = ref<DashboardItem[]>([]);
|
||||
const dashboardFile = ref<Nullable<HTMLDivElement>>(null);
|
||||
|
||||
appStore.setPageTitle("Dashboard List");
|
||||
const handleSelectionChange = (val: DashboardItem[]) => {
|
||||
multipleSelection.value = val;
|
||||
};
|
||||
|
@ -53,12 +53,9 @@ limitations under the License. -->
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import { EntityType } from "./data";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
|
||||
const appStore = useAppStoreWithOut();
|
||||
const dashboardStore = useDashboardStore();
|
||||
appStore.setPageTitle("Dashboard New");
|
||||
const { t } = useI18n();
|
||||
const selectorStore = useSelectorStore();
|
||||
const states = reactive({
|
||||
|
@ -160,7 +160,7 @@ limitations under the License. -->
|
||||
},
|
||||
});
|
||||
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(
|
||||
() => (isExpression.value ? dashboardStore.selectedGrid.expressions : dashboardStore.selectedGrid.metrics) || [],
|
||||
);
|
||||
|
@ -161,6 +161,7 @@ export enum MetricCatalog {
|
||||
SERVICE_RELATION = "ServiceRelation",
|
||||
SERVICE_INSTANCE_RELATION = "ServiceInstanceRelation",
|
||||
ENDPOINT_RELATION = "EndpointRelation",
|
||||
Process = "Process",
|
||||
PROCESS_RELATION = "ProcessRelation",
|
||||
}
|
||||
export const EntityType = [
|
||||
|
@ -294,4 +294,8 @@ limitations under the License. -->
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: $active-color;
|
||||
}
|
||||
</style>
|
||||
|
@ -216,7 +216,7 @@ limitations under the License. -->
|
||||
width: 330px;
|
||||
height: calc(100% - 10px);
|
||||
overflow: auto;
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-right: 1px solid rgb(0 0 0 / 10%);
|
||||
}
|
||||
|
||||
.item span {
|
||||
@ -225,7 +225,7 @@ limitations under the License. -->
|
||||
|
||||
.profile-td {
|
||||
padding: 10px 5px 10px 10px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
|
||||
border-bottom: 1px solid rgb(0 0 0 / 7%);
|
||||
|
||||
&.selected {
|
||||
background-color: #ededed;
|
||||
@ -253,13 +253,13 @@ limitations under the License. -->
|
||||
|
||||
.profile-tr {
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.04);
|
||||
background-color: rgb(0 0 0 / 4%);
|
||||
}
|
||||
}
|
||||
|
||||
.profile-t-tool {
|
||||
padding: 10px 5px 10px 10px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
|
||||
border-bottom: 1px solid rgb(0 0 0 / 7%);
|
||||
background: #f3f4f9;
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ limitations under the License. -->
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import { useTopologyStore } from "@/store/modules/topology";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { EntityType, DepthList } from "../../../data";
|
||||
import { EntityType, DepthList, MetricModes } from "../../../data";
|
||||
import router from "@/router";
|
||||
import { ElMessage } from "element-plus";
|
||||
import Settings from "./Settings.vue";
|
||||
@ -153,6 +153,7 @@ limitations under the License. -->
|
||||
import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
|
||||
import { layout, circleIntersection, computeCallPos } from "./utils/layout";
|
||||
import zoom from "../../components/utils/zoom";
|
||||
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
|
||||
/*global Nullable, defineProps */
|
||||
const props = defineProps({
|
||||
@ -220,19 +221,27 @@ limitations under the License. -->
|
||||
}
|
||||
|
||||
async function update() {
|
||||
topologyStore.queryNodeMetrics(settings.value.nodeMetrics || []);
|
||||
topologyStore.getLinkClientMetrics(settings.value.linkClientMetrics || []);
|
||||
topologyStore.getLinkServerMetrics(settings.value.linkServerMetrics || []);
|
||||
if (settings.value.metricMode === MetricModes.Expression) {
|
||||
topologyStore.queryNodeExpressions(settings.value.nodeExpressions || []);
|
||||
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);
|
||||
await initLegendMetrics();
|
||||
draw();
|
||||
tooltip.value = d3.select("#tooltip");
|
||||
setNodeTools(settings.value.nodeDashboard);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
const node = findMostFrequent(topologyStore.calls);
|
||||
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()) {
|
||||
return -1;
|
||||
}
|
||||
@ -352,18 +361,49 @@ limitations under the License. -->
|
||||
}
|
||||
|
||||
async function initLegendMetrics() {
|
||||
const ids = topologyStore.nodes.map((d: Node) => d.id);
|
||||
const names = props.config.legend.map((d: any) => d.name);
|
||||
if (names.length && ids.length) {
|
||||
const param = await useQueryTopologyMetrics(names, ids);
|
||||
const res = await topologyStore.getLegendMetrics(param);
|
||||
if (!topologyStore.nodes.length) {
|
||||
return;
|
||||
}
|
||||
if (settings.value.metricMode === MetricModes.Expression) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
return icons.CUBE;
|
||||
}
|
||||
@ -381,7 +421,10 @@ limitations under the License. -->
|
||||
return c && d.isReal ? icons.CUBEERROR : icons.CUBE;
|
||||
}
|
||||
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 html = nodeMetrics.map((m, index) => {
|
||||
const metric =
|
||||
@ -404,10 +447,16 @@ limitations under the License. -->
|
||||
.html(tipHtml);
|
||||
}
|
||||
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 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 metric = topologyStore.linkServerMetrics[m].values.find(
|
||||
(val: { id: string; value: unknown }) => val.id === data.id,
|
||||
@ -667,7 +716,7 @@ limitations under the License. -->
|
||||
padding: 0 15px;
|
||||
border-radius: 3px;
|
||||
color: $disabled-color;
|
||||
border: 1px solid $disabled-color;
|
||||
border: 1px solid #eee;
|
||||
background-color: $theme-background;
|
||||
box-shadow: #eee 1px 2px 10px;
|
||||
transition: all 0.5ms linear;
|
||||
|
@ -15,8 +15,10 @@ limitations under the License. -->
|
||||
<template>
|
||||
<div class="config-panel">
|
||||
<div class="item mb-10">
|
||||
<span class="label">{{ t("metrics") }}</span>
|
||||
<SelectSingle :value="currentMetric" :options="metrics" @change="changeMetric" class="selectors" />
|
||||
<span class="label">{{
|
||||
t(dashboardStore.selectedGrid.metricMode === MetricModes.General ? "metrics" : "expressions")
|
||||
}}</span>
|
||||
<SelectSingle :value="currentMetric" :options="metricList" @change="changeMetric" class="selectors" />
|
||||
</div>
|
||||
<div class="item mb-10">
|
||||
<span class="label">{{ t("unit") }}</span>
|
||||
@ -38,7 +40,7 @@ limitations under the License. -->
|
||||
@change="changeConfigs({ label: currentConfig.label })"
|
||||
/>
|
||||
</div>
|
||||
<div class="item mb-10">
|
||||
<div class="item mb-10" v-if="dashboardStore.selectedGrid.metricMode === MetricModes.General">
|
||||
<span class="label">{{ t("aggregation") }}</span>
|
||||
<SelectSingle
|
||||
:value="currentConfig.calculation"
|
||||
@ -54,17 +56,12 @@ limitations under the License. -->
|
||||
import { ref, computed, watch } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { CalculationOpts } from "../../../data";
|
||||
import { CalculationOpts, MetricModes } from "../../../data";
|
||||
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";
|
||||
|
||||
/*global defineEmits, defineProps */
|
||||
const props = defineProps({
|
||||
currentMetricConfig: {
|
||||
type: Object as PropType<MetricConfigOpt>,
|
||||
default: () => ({ unit: "" }),
|
||||
},
|
||||
type: { type: String, default: "" },
|
||||
metrics: { type: Array as PropType<string[]>, default: () => [] },
|
||||
});
|
||||
@ -74,8 +71,8 @@ limitations under the License. -->
|
||||
const m = props.metrics.map((d: string) => {
|
||||
return { label: d, value: d };
|
||||
});
|
||||
const metrics = ref<Option[]>(m.length ? m : [{ label: "", value: "" }]);
|
||||
const currentMetric = ref<string>(metrics.value[0].value);
|
||||
const metricList = ref<Option[]>(m.length ? m : [{ label: "", value: "" }]);
|
||||
const currentMetric = ref<string>(metricList.value[0].value);
|
||||
const currentConfig = ref<{ unit: string; calculation: string; label: string }>({
|
||||
unit: "",
|
||||
calculation: "",
|
||||
@ -109,7 +106,7 @@ limitations under the License. -->
|
||||
}
|
||||
function changeMetric(val: string) {
|
||||
currentMetric.value = val;
|
||||
const index = metrics.value.findIndex((d: Option) => d.value === val);
|
||||
const index = metricList.value.findIndex((d: Option) => d.value === val);
|
||||
currentIndex.value = index || 0;
|
||||
const config = getMetricConfig.value || [];
|
||||
|
||||
@ -126,8 +123,8 @@ limitations under the License. -->
|
||||
const m = props.metrics.map((d: string) => {
|
||||
return { label: d, value: d };
|
||||
});
|
||||
metrics.value = m.length ? m : [{ label: "", value: "" }];
|
||||
currentMetric.value = metrics.value[0].value;
|
||||
metricList.value = m.length ? m : [{ label: "", value: "" }];
|
||||
currentMetric.value = metricList.value[0].value;
|
||||
const config = getMetricConfig.value || [];
|
||||
currentIndex.value = 0;
|
||||
currentConfig.value = {
|
||||
|
@ -22,6 +22,7 @@ limitations under the License. -->
|
||||
:options="DepthList"
|
||||
placeholder="Select a option"
|
||||
@change="changeDepth"
|
||||
size="small"
|
||||
/>
|
||||
</span>
|
||||
<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 { useSelectorStore } from "@/store/modules/selectors";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import { EntityType, DepthList } from "../../../data";
|
||||
import { EntityType, DepthList, MetricModes } from "../../../data";
|
||||
import { ElMessage } from "element-plus";
|
||||
import Sankey from "./Sankey.vue";
|
||||
import Settings from "./Settings.vue";
|
||||
@ -118,9 +119,15 @@ limitations under the License. -->
|
||||
};
|
||||
height.value = dom.height - 70;
|
||||
width.value = dom.width - 5;
|
||||
topologyStore.getLinkClientMetrics(settings.value.linkClientMetrics || []);
|
||||
topologyStore.getLinkServerMetrics(settings.value.linkServerMetrics || []);
|
||||
topologyStore.queryNodeMetrics(settings.value.nodeMetrics || []);
|
||||
if (settings.value.metricMode === MetricModes.Expression) {
|
||||
topologyStore.queryNodeExpressions(settings.value.nodeExpressions || []);
|
||||
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() {
|
||||
@ -265,7 +272,6 @@ limitations under the License. -->
|
||||
<style lang="scss" scoped>
|
||||
.sankey {
|
||||
margin-top: 10px;
|
||||
background-color: #333840 !important;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
@ -275,7 +281,8 @@ limitations under the License. -->
|
||||
right: 10px;
|
||||
width: 400px;
|
||||
height: 600px;
|
||||
background-color: #2b3037;
|
||||
border: 1px solid #eee;
|
||||
background-color: $theme-background;
|
||||
overflow: auto;
|
||||
padding: 10px 15px;
|
||||
border-radius: 3px;
|
||||
@ -283,6 +290,7 @@ limitations under the License. -->
|
||||
transition: all 0.5ms linear;
|
||||
z-index: 99;
|
||||
text-align: left;
|
||||
box-shadow: #eee 1px 2px 10px;
|
||||
}
|
||||
|
||||
.tool {
|
||||
@ -299,8 +307,8 @@ limitations under the License. -->
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.5ms linear;
|
||||
background-color: #252a2f99;
|
||||
color: #ddd;
|
||||
background: rgb(0 0 0 / 30%);
|
||||
color: $text-color;
|
||||
display: inline-block;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ limitations under the License. -->
|
||||
import type { Node, Call } from "@/types/topology";
|
||||
import type { MetricConfigOpt } from "@/types/dashboard";
|
||||
import { aggregation } from "@/hooks/useMetricsProcessor";
|
||||
import { MetricModes } from "../../../data";
|
||||
|
||||
/*global defineEmits, defineProps */
|
||||
const props = defineProps({
|
||||
@ -51,16 +52,16 @@ limitations under the License. -->
|
||||
data: topologyStore.nodes,
|
||||
links: topologyStore.calls,
|
||||
label: {
|
||||
color: "#fff",
|
||||
color: "#666",
|
||||
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: {
|
||||
borderWidth: 0,
|
||||
},
|
||||
lineStyle: {
|
||||
color: "source",
|
||||
opacity: 0.12,
|
||||
opacity: 0.3,
|
||||
},
|
||||
tooltip: {
|
||||
position: "bottom",
|
||||
@ -75,8 +76,14 @@ limitations under the License. -->
|
||||
};
|
||||
}
|
||||
function linkTooltip(data: Call) {
|
||||
const clientMetrics: string[] = Object.keys(topologyStore.linkClientMetrics);
|
||||
const serverMetrics: string[] = Object.keys(topologyStore.linkServerMetrics);
|
||||
const clientMetrics: string[] =
|
||||
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 linkClientMetricConfig: MetricConfigOpt[] = props.settings.linkClientMetricConfig || [];
|
||||
|
||||
@ -108,7 +115,10 @@ limitations under the License. -->
|
||||
}
|
||||
|
||||
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 html = nodeMetrics.map((m, index) => {
|
||||
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
|
||||
limitations under the License. -->
|
||||
<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">
|
||||
<h5 class="title">{{ t("callSettings") }}</h5>
|
||||
<div class="label">{{ t("linkDashboard") }}</div>
|
||||
@ -27,16 +38,34 @@ limitations under the License. -->
|
||||
/>
|
||||
<div class="label">
|
||||
<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>
|
||||
<span @click="setConfigType('linkServerMetricConfig')">
|
||||
<Icon class="cp ml-5" iconName="mode_edit" size="middle" />
|
||||
</span>
|
||||
</template>
|
||||
<Metrics :type="configType" :metrics="states.linkServerMetrics" @update="changeLinkServerMetrics" />
|
||||
<Metrics
|
||||
:type="configType"
|
||||
:metrics="isExpression ? states.linkServerExpressions : states.linkServerMetrics"
|
||||
@update="updateSettings"
|
||||
/>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div v-if="isExpression">
|
||||
<Tags
|
||||
:tags="states.linkServerExpressions"
|
||||
:vertical="true"
|
||||
:text="t('addExpressions')"
|
||||
@change="(param) => changeLinkServerExpressions(param)"
|
||||
/>
|
||||
</div>
|
||||
<Selector
|
||||
v-else
|
||||
class="inputs"
|
||||
:multiple="true"
|
||||
:value="states.linkServerMetrics"
|
||||
@ -48,16 +77,34 @@ limitations under the License. -->
|
||||
<span v-show="dashboardStore.entity !== EntityType[2].value">
|
||||
<div class="label">
|
||||
<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>
|
||||
<span @click="setConfigType('linkClientMetricConfig')">
|
||||
<Icon class="cp ml-5" iconName="mode_edit" size="middle" />
|
||||
</span>
|
||||
</template>
|
||||
<Metrics :type="configType" :metrics="states.linkClientMetrics" @update="changeLinkClientMetrics" />
|
||||
<Metrics
|
||||
:type="configType"
|
||||
:metrics="isExpression ? states.linkClientExpressions : states.linkClientMetrics"
|
||||
@update="updateSettings"
|
||||
/>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div v-if="isExpression">
|
||||
<Tags
|
||||
:tags="states.linkClientExpressions"
|
||||
:vertical="true"
|
||||
:text="t('addExpressions')"
|
||||
@change="(param) => changeLinkClientExpressions(param)"
|
||||
/>
|
||||
</div>
|
||||
<Selector
|
||||
v-else
|
||||
class="inputs"
|
||||
:multiple="true"
|
||||
:value="states.linkClientMetrics"
|
||||
@ -110,16 +157,34 @@ limitations under the License. -->
|
||||
</div>
|
||||
<div class="label">
|
||||
<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>
|
||||
<span @click="setConfigType('nodeMetricConfig')">
|
||||
<Icon class="cp ml-5" iconName="mode_edit" size="middle" />
|
||||
</span>
|
||||
</template>
|
||||
<Metrics :type="configType" :metrics="states.nodeMetrics" @update="changeNodeMetrics" />
|
||||
<Metrics
|
||||
:type="configType"
|
||||
:metrics="isExpression ? states.nodeExpressions : states.nodeMetrics"
|
||||
@update="updateSettings"
|
||||
/>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div v-if="isExpression">
|
||||
<Tags
|
||||
:tags="states.nodeExpressions"
|
||||
:vertical="true"
|
||||
:text="t('addExpressions')"
|
||||
@change="(param) => changeNodeExpressions(param)"
|
||||
/>
|
||||
</div>
|
||||
<Selector
|
||||
v-else
|
||||
class="inputs"
|
||||
:multiple="true"
|
||||
:value="states.nodeMetrics"
|
||||
@ -131,8 +196,26 @@ limitations under the License. -->
|
||||
</div>
|
||||
<div class="legend-settings" v-show="isService">
|
||||
<h5 class="title">{{ t("legendSettings") }}</h5>
|
||||
<div class="label">{{ t("conditions") }}</div>
|
||||
<div v-for="(metric, index) of legend.metric" :key="metric.name + index">
|
||||
<span v-if="isExpression">
|
||||
<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
|
||||
class="item"
|
||||
:value="metric.name"
|
||||
@ -163,14 +246,16 @@ limitations under the License. -->
|
||||
class="cp"
|
||||
iconName="add_circle_outlinecontrol_point"
|
||||
size="middle"
|
||||
v-show="index === legend.metric.length - 1 && legend.metric.length < 5"
|
||||
v-show="index === legend.length - 1 && legend.length < 5"
|
||||
@click="addMetric"
|
||||
/>
|
||||
</span>
|
||||
<div v-show="index !== legend.metric.length - 1">&&</div>
|
||||
<div v-show="index !== legend.length - 1">&&</div>
|
||||
</div>
|
||||
<div class="label">Healthy Description</div>
|
||||
<el-input v-model="description.healthy" placeholder="Please input description" size="small" class="mt-5" />
|
||||
<span v-if="!isExpression">
|
||||
<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>
|
||||
<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">
|
||||
@ -184,12 +269,20 @@ limitations under the License. -->
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useTopologyStore } from "@/store/modules/topology";
|
||||
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 { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
|
||||
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import type { Node } from "@/types/topology";
|
||||
import type { DashboardItem, MetricConfigOpt } from "@/types/dashboard";
|
||||
import { EntityType, LegendOpt, MetricsType } from "../../../data";
|
||||
import Metrics from "./Metrics.vue";
|
||||
|
||||
/*global defineEmits */
|
||||
@ -198,6 +291,7 @@ limitations under the License. -->
|
||||
const dashboardStore = useDashboardStore();
|
||||
const topologyStore = useTopologyStore();
|
||||
const { selectedGrid } = dashboardStore;
|
||||
const isExpression = ref<boolean>(dashboardStore.selectedGrid.metricMode === MetricModes.Expression);
|
||||
const nodeDashboard =
|
||||
selectedGrid.nodeDashboard && selectedGrid.nodeDashboard.length ? selectedGrid.nodeDashboard : "";
|
||||
const isService = [EntityType[0].value, EntityType[1].value].includes(dashboardStore.entity);
|
||||
@ -220,6 +314,9 @@ limitations under the License. -->
|
||||
linkMetricList: Option[];
|
||||
linkDashboards: (DashboardItem & { label: string; value: string })[];
|
||||
nodeDashboards: (DashboardItem & { label: string; value: string })[];
|
||||
linkServerExpressions: string[];
|
||||
linkClientExpressions: string[];
|
||||
nodeExpressions: string[];
|
||||
}>({
|
||||
linkDashboard: selectedGrid.linkDashboard || "",
|
||||
nodeDashboard: selectedGrid.nodeDashboard || [],
|
||||
@ -230,13 +327,15 @@ limitations under the License. -->
|
||||
linkMetricList: [],
|
||||
linkDashboards: [],
|
||||
nodeDashboards: [],
|
||||
linkServerExpressions: selectedGrid.linkServerExpressions || [],
|
||||
linkClientExpressions: selectedGrid.linkClientExpressions || [],
|
||||
nodeExpressions: selectedGrid.nodeExpressions || [],
|
||||
});
|
||||
const l = selectedGrid.legend && selectedGrid.legend.length;
|
||||
const legend = reactive<{
|
||||
metric: { name: string; condition: string; value: string }[];
|
||||
}>({
|
||||
metric: l ? selectedGrid.legend : [{ name: "", condition: "", value: "" }],
|
||||
});
|
||||
const legend = ref<{ name: string; condition: string; value: string }[]>(
|
||||
l ? selectedGrid.legend : [{ name: "", condition: "", value: "" }],
|
||||
);
|
||||
const legendMQE = ref<{ expression: string }>(selectedGrid.legendMQE || { expression: "" });
|
||||
const configType = ref<string>("");
|
||||
const description = reactive<any>(selectedGrid.description || {});
|
||||
|
||||
@ -285,18 +384,35 @@ limitations under the License. -->
|
||||
}
|
||||
async function setLegend() {
|
||||
updateSettings();
|
||||
const ids = topologyStore.nodes.map((d: Node) => d.id);
|
||||
const names = dashboardStore.selectedGrid.legend.map((d: any) => d.name);
|
||||
if (!names.length) {
|
||||
emit("updateNodes");
|
||||
return;
|
||||
}
|
||||
const param = await useQueryTopologyMetrics(names, ids);
|
||||
const res = await topologyStore.getLegendMetrics(param);
|
||||
if (isExpression.value) {
|
||||
const expression = dashboardStore.selectedGrid.legendMQE && dashboardStore.selectedGrid.legendMQE.expression;
|
||||
if (!expression) {
|
||||
emit("updateNodes");
|
||||
return;
|
||||
}
|
||||
const { getExpressionQuery } = useQueryTopologyExpressionsProcessor([expression], topologyStore.nodes);
|
||||
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) {
|
||||
ElMessage.error(res.errors);
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
}
|
||||
}
|
||||
|
||||
emit("updateNodes");
|
||||
}
|
||||
function changeNodeDashboard(opt: any) {
|
||||
@ -308,7 +424,7 @@ limitations under the License. -->
|
||||
updateSettings();
|
||||
}
|
||||
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) {
|
||||
items[index].scope = opt[0].value;
|
||||
@ -340,7 +456,13 @@ limitations under the License. -->
|
||||
updateSettings();
|
||||
}
|
||||
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 = {
|
||||
...dashboardStore.selectedGrid,
|
||||
linkDashboard: states.linkDashboard,
|
||||
@ -350,7 +472,12 @@ limitations under the License. -->
|
||||
linkServerMetrics: states.linkServerMetrics,
|
||||
linkClientMetrics: states.linkClientMetrics,
|
||||
nodeMetrics: states.nodeMetrics,
|
||||
linkServerExpressions: states.linkServerExpressions,
|
||||
linkClientExpressions: states.linkClientExpressions,
|
||||
nodeExpressions: states.nodeExpressions,
|
||||
metricMode: isExpression.value ? MetricModes.Expression : MetricModes.General,
|
||||
legend: metrics,
|
||||
legendMQE: legendMQE.value,
|
||||
...metricConfig,
|
||||
description,
|
||||
};
|
||||
@ -378,6 +505,30 @@ limitations under the License. -->
|
||||
}
|
||||
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) {
|
||||
const opt = options.map((d: Option) => d.value);
|
||||
const index = states.linkClientMetrics.findIndex((d: any) => !opt.includes(d));
|
||||
@ -419,18 +570,49 @@ limitations under the License. -->
|
||||
topologyStore.queryNodeMetrics(states.nodeMetrics);
|
||||
}
|
||||
function deleteMetric(index: number) {
|
||||
if (legend.metric.length === 1) {
|
||||
legend.metric = [{ name: "", condition: "", value: "" }];
|
||||
if (legend.value.length === 1) {
|
||||
legend.value = [{ name: "", condition: "", value: "" }];
|
||||
return;
|
||||
}
|
||||
legend.metric.splice(index, 1);
|
||||
legend.value.splice(index, 1);
|
||||
}
|
||||
function addMetric() {
|
||||
legend.metric.push({ name: "", condition: "", value: "" });
|
||||
legend.value.push({ name: "", condition: "", value: "" });
|
||||
}
|
||||
function setConfigType(type: string) {
|
||||
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>
|
||||
<style lang="scss" scoped>
|
||||
.link-settings {
|
||||
@ -442,6 +624,11 @@ limitations under the License. -->
|
||||
width: 355px;
|
||||
}
|
||||
|
||||
.legend-inputs {
|
||||
margin-top: 8px;
|
||||
width: 310px;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 130px;
|
||||
margin-top: 5px;
|
||||
@ -453,13 +640,14 @@ limitations under the License. -->
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 0;
|
||||
color: #666;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: $font-size-smaller;
|
||||
margin-top: 10px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.legend-btn {
|
||||
|
Loading…
Reference in New Issue
Block a user