-
{{ route.name === "ViewWidget" ? "" : appStore.pageTitle || pageName }}
+
+
+
+ {{ getName(path).name }}
+
+
+
+ {{ p.name }}
+
+
+
+
+
+
+
{{ pageTitle }}
{{ t("timeTips") }}
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("");
+ const pathNames = ref<{ path?: string; name: string; selected: boolean }[][]>([]);
const timeRange = ref(0);
+ const pageTitle = ref("");
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();
+ },
+ );
diff --git a/src/locales/lang/en.ts b/src/locales/lang/en.ts
index db2eb4ab..1d983f65 100644
--- a/src/locales/lang/en.ts
+++ b/src/locales/lang/en.ts
@@ -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;
diff --git a/src/locales/lang/es.ts b/src/locales/lang/es.ts
index aa7953e6..b92ba40b 100644
--- a/src/locales/lang/es.ts
+++ b/src/locales/lang/es.ts
@@ -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;
diff --git a/src/locales/lang/zh.ts b/src/locales/lang/zh.ts
index 430755e5..edc9a665 100644
--- a/src/locales/lang/zh.ts
+++ b/src/locales/lang/zh.ts
@@ -376,5 +376,9 @@ const msg = {
menusManagement: "菜单",
saveReload: "保存并重新加载页面",
document: "文档",
+ metricMode: "指标模式",
+ addExpressions: "添加表达式",
+ expressions: "表达式",
+ unhealthyExpression: "非健康表达式",
};
export default msg;
diff --git a/src/store/modules/app.ts b/src/store/modules/app.ts
index ede73ea5..994e83a5 100644
--- a/src/store/modules/app.ts
+++ b/src/store/modules/app.ts
@@ -32,7 +32,6 @@ interface AppState {
eventStack: (() => unknown)[];
timer: Nullable;
autoRefresh: boolean;
- pageTitle: string;
version: string;
isMobile: boolean;
reloadTimer: Nullable;
@@ -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);
diff --git a/src/store/modules/event.ts b/src/store/modules/event.ts
index e1948cce..bb6ec794 100644
--- a/src/store/modules/event.ts
+++ b/src/store/modules/event.ts
@@ -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;
diff --git a/src/store/modules/selectors.ts b/src/store/modules/selectors.ts
index 74d62399..4101b850 100644
--- a/src/store/modules/selectors.ts
+++ b/src/store/modules/selectors.ts
@@ -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];
diff --git a/src/store/modules/topology.ts b/src/store/modules/topology.ts
index 50bae018..74bc7e15 100644
--- a/src/store/modules/topology.ts
+++ b/src/store/modules/topology.ts
@@ -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);
diff --git a/src/styles/reset.scss b/src/styles/reset.scss
index c447ec45..49262e50 100644
--- a/src/styles/reset.scss
+++ b/src/styles/reset.scss
@@ -212,6 +212,7 @@ div.vis-tooltip {
div:has(> a.menu-title) {
display: none;
}
+
.el-input-number .el-input__inner {
text-align: left !important;
}
diff --git a/src/types/app.d.ts b/src/types/app.d.ts
index c759cfef..e697ae07 100644
--- a/src/types/app.d.ts
+++ b/src/types/app.d.ts
@@ -44,6 +44,8 @@ export type EventParams = {
dataType: string;
value: number | any[];
color: string;
+ event: Record;
+ dataIndex: number;
event: any;
};
diff --git a/src/types/components.d.ts b/src/types/components.d.ts
index cae7912a..9f9c7086 100644
--- a/src/types/components.d.ts
+++ b/src/types/components.d.ts
@@ -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']
}
}
diff --git a/src/types/profile.d.ts b/src/types/profile.d.ts
index c9d64746..ac7fb79f 100644
--- a/src/types/profile.d.ts
+++ b/src/types/profile.d.ts
@@ -55,8 +55,8 @@ export interface SegmentSpan {
component: string;
isError: boolean;
layer: string;
- tags: any[];
- logs: any[];
+ tags: Recordable[];
+ logs: Recordable[];
}
export interface ProfileTaskCreationRequest {
diff --git a/src/utils/arrayAlgorithm.ts b/src/utils/arrayAlgorithm.ts
new file mode 100644
index 00000000..3baab512
--- /dev/null
+++ b/src/utils/arrayAlgorithm.ts
@@ -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()];
+}
diff --git a/src/views/Alarm.vue b/src/views/Alarm.vue
index 236e7e18..e541a8ed 100644
--- a/src/views/Alarm.vue
+++ b/src/views/Alarm.vue
@@ -19,12 +19,8 @@ limitations under the License. -->
diff --git a/src/views/components/ConditionTags.vue b/src/views/components/ConditionTags.vue
index f62629c2..9d33d104 100644
--- a/src/views/components/ConditionTags.vue
+++ b/src/views/components/ConditionTags.vue
@@ -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 {
diff --git a/src/views/components/style.scss b/src/views/components/style.scss
deleted file mode 100644
index 179e9fb1..00000000
--- a/src/views/components/style.scss
+++ /dev/null
@@ -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;
-}
diff --git a/src/views/dashboard/Edit.vue b/src/views/dashboard/Edit.vue
index 88cf5c8f..3d6d523a 100644
--- a/src/views/dashboard/Edit.vue
+++ b/src/views/dashboard/Edit.vue
@@ -41,13 +41,12 @@ limitations under the License. -->