Compare commits

...

11 Commits

Author SHA1 Message Date
dependabot[bot]
0c2cfa5630
build(deps-dev): bump vite from 6.2.6 to 6.3.4 (#468) 2025-05-01 16:42:57 +08:00
Fine0830
5e6e5aa737
Fix: correct the same labels for metrics (#467) 2025-04-24 11:58:29 +08:00
Fine0830
a4cd265d45
refactor: use the Fetch API to instead of Axios (#466) 2025-04-22 11:41:29 +08:00
peachisai
0ef6b57cae
Add the Flink icon and descriptions (#465) 2025-04-17 19:27:58 +08:00
Fine0830
687ae07bb0
feat: enhance the trace table graph for multiple refs (#464) 2025-04-14 21:12:48 +08:00
dependabot[bot]
0775bf0034
build(deps-dev): bump vite from 6.2.5 to 6.2.6 (#463) 2025-04-12 10:39:21 +08:00
Fine0830
5c322d960f
feat: enhance the trace tree to support displaying multiple refs of spans and distinguishing different parents. (#462) 2025-04-08 20:44:36 +08:00
dependabot[bot]
df2d07f508
build(deps-dev): bump vite from 6.2.4 to 6.2.5 (#461) 2025-04-07 09:44:10 +08:00
dependabot[bot]
105450071e
build(deps-dev): bump vite from 6.2.3 to 6.2.4 (#460)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.3 to 6.2.4.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.4/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.4/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 07:32:25 +08:00
Fine0830
39b4626317
feat: enhance trace list graph (#459) 2025-03-28 10:34:01 +08:00
dependabot[bot]
0ea8335fee
build(deps-dev): bump vite from 6.1.1 to 6.2.3 (#458) 2025-03-26 09:39:39 +08:00
48 changed files with 1425 additions and 1088 deletions

553
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,6 @@
"check-components-types": "if (! git diff --quiet -U0 ./src/types); then echo 'type files are not updated correctly'; git diff -U0 ./src/types; exit 1; fi" "check-components-types": "if (! git diff --quiet -U0 ./src/types); then echo 'type files are not updated correctly'; git diff -U0 ./src/types; exit 1; fi"
}, },
"dependencies": { "dependencies": {
"axios": "^1.8.2",
"d3": "^7.3.0", "d3": "^7.3.0",
"d3-flame-graph": "^4.1.3", "d3-flame-graph": "^4.1.3",
"d3-tip": "^0.9.1", "d3-tip": "^0.9.1",
@ -72,7 +71,7 @@
"typescript": "^5.7.3", "typescript": "^5.7.3",
"unplugin-auto-import": "^0.18.2", "unplugin-auto-import": "^0.18.2",
"unplugin-vue-components": "^0.27.3", "unplugin-vue-components": "^0.27.3",
"vite": "^6.1.0", "vite": "^6.3.4",
"vite-plugin-monaco-editor": "^1.1.0", "vite-plugin-monaco-editor": "^1.1.0",
"vite-plugin-svg-icons": "^2.0.1", "vite-plugin-svg-icons": "^2.0.1",
"vitest": "^3.0.5", "vitest": "^3.0.5",

View File

@ -0,0 +1,38 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<svg width="2400" height="2400" viewBox="0 0 200 100" version="1.1" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="arrowhead" markerWidth="8" markerHeight="8" refX="2" refY="2.5" orient="auto">
<polygon points="0 0, 3 2.5, 0 5" fill="white" />
</marker>
</defs>
<line x1="0" y1="20" x2="42" y2="20" stroke="white" stroke-width="7" marker-end="url(#arrowhead)" />
<line x1="0" y1="50" x2="42" y2="50" stroke="white" stroke-width="7" marker-end="url(#arrowhead)" />
<line x1="0" y1="80" x2="42" y2="80" stroke="white" stroke-width="7" marker-end="url(#arrowhead)" />
<line x1="49" y1="10" x2="139" y2="10" stroke="white" stroke-width="7" />
<line x1="49" y1="90" x2="139" y2="90" stroke="white" stroke-width="7" />
<line x1="49" y1="10" x2="50" y2="90" stroke="white" stroke-width="7" />
<ellipse cx="140" cy="50" rx="10" ry="40" fill="none" stroke="white" stroke-width="7" />
<line x1="147" y1="20" x2="190" y2="20" stroke="white" stroke-width="7" marker-end="url(#arrowhead)" />
<line x1="149" y1="50" x2="190" y2="50" stroke="white" stroke-width="7" marker-end="url(#arrowhead)" />
<line x1="147" y1="80" x2="190" y2="80" stroke="white" stroke-width="7" marker-end="url(#arrowhead)" />
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

75
src/graphql/base.ts Normal file
View File

@ -0,0 +1,75 @@
/*
* Licensed to Apache Software Foundation (ASF) under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Apache Software Foundation (ASF) licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
const Timeout = 2 * 60 * 1000;
export let globalAbortController = new AbortController();
export function abortRequestsAndUpdate() {
globalAbortController.abort(`Request timeout ${Timeout}ms`);
globalAbortController = new AbortController();
}
class HTTPError extends Error {
response;
constructor(response: Response, detailText = "") {
super(detailText || response.statusText);
this.name = "HTTPError";
this.response = response;
}
}
const BasePath = `/graphql`;
export async function httpQuery({
path = "",
method = "GET",
json,
headers = {},
}: {
path?: string;
method: string;
json: unknown;
headers: Recordable;
}) {
const timeoutId = setTimeout(() => {
abortRequestsAndUpdate();
}, Timeout);
const url = `${BasePath}${path}`;
const response: Response = await fetch(url, {
method,
headers: {
"Content-Type": "application/json",
accept: "application/json",
...headers,
},
body: JSON.stringify(json),
signal: globalAbortController.signal,
})
.catch((error) => {
throw new HTTPError(error);
})
.finally(() => {
clearTimeout(timeoutId);
});
if (response.ok) {
return response.json();
} else {
console.error(new HTTPError(response));
}
}

View File

@ -14,20 +14,18 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import type { AxiosResponse } from "axios"; import { httpQuery } from "./base";
import axios from "axios";
import { cancelToken } from "@/utils/cancelToken";
async function query(param: { queryStr: string; conditions: { [key: string]: unknown } }) { async function fetchQuery(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const res: AxiosResponse = await axios.post( const response = await httpQuery({
"/graphql", method: "post",
{ query: param.queryStr, variables: { ...param.conditions } }, json: { query: param.queryStr, variables: { ...param.conditions } },
{ cancelToken: cancelToken() }, headers: {},
); });
if (res.data.errors) { if (response.errors) {
res.data.errors = res.data.errors.map((e: { message: string }) => e.message).join(" "); response.errors = response.errors.map((e: { message: string }) => e.message).join(" ");
} }
return res; return response;
} }
export default query; export default fetchQuery;

View File

@ -14,22 +14,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
export const TypeOfMetrics = {
variable: "$name: String!",
query: `typeOfMetrics(name: $name)`,
};
export const listMetrics = {
variable: "$regex: String",
query: `
metrics: listMetrics(regex: $regex) {
value: name
label: name
type
catalog
}
`,
};
export const getAllTemplates = { export const getAllTemplates = {
query: ` query: `

View File

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

View File

@ -14,18 +14,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { import { getAllTemplates, addTemplate, changeTemplate, deleteTemplate } from "../fragments/dashboard";
TypeOfMetrics,
listMetrics,
getAllTemplates,
addTemplate,
changeTemplate,
deleteTemplate,
} from "../fragments/dashboard";
export const queryTypeOfMetrics = `query typeOfMetrics(${TypeOfMetrics.variable}) {${TypeOfMetrics.query}}`;
export const queryMetrics = `query queryData(${listMetrics.variable}) {${listMetrics.query}}`;
export const addNewTemplate = `mutation template(${addTemplate.variable}) {${addTemplate.query}}`; export const addNewTemplate = `mutation template(${addTemplate.variable}) {${addTemplate.query}}`;

View File

@ -109,12 +109,12 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
return { source: {}, tips: [], typesOfMQE: [] }; return { source: {}, tips: [], typesOfMQE: [] };
} }
const tips: string[] = []; const tips: string[] = [];
const source: { [key: string]: unknown } = {}; const source: Indexable<unknown> = {};
const keys = Object.keys(resp.data); const keys = Object.keys(resp.data);
const typesOfMQE: string[] = []; const typesOfMQE: string[] = [];
for (let i = 0; i < config.metrics.length; i++) { for (let i = 0; i < config.metrics.length; i++) {
const c: MetricConfigOpt = (config.metricConfig && config.metricConfig[i]) || {}; const metricConfig: MetricConfigOpt = (config.metricConfig && config.metricConfig[i]) || {};
const obj = resp.data[keys[i]] || {}; const obj = resp.data[keys[i]] || {};
const results = obj.results || []; const results = obj.results || [];
const name = config.metrics[i]; const name = config.metrics[i];
@ -125,15 +125,15 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
if (!obj.error) { if (!obj.error) {
if ([ExpressionResultType.SINGLE_VALUE, ExpressionResultType.TIME_SERIES_VALUES].includes(type)) { if ([ExpressionResultType.SINGLE_VALUE, ExpressionResultType.TIME_SERIES_VALUES].includes(type)) {
for (const item of results) { for (const item of results) {
const label = let label =
item.metric && item.metric &&
item.metric.labels.map((d: { key: string; value: string }) => `${d.key}=${d.value}`).join(","); item.metric.labels.map((d: { key: string; value: string }) => `${d.key}=${d.value}`).join(",");
const values = item.values.map((d: { value: unknown }) => d.value) || []; const values = item.values.map((d: { value: unknown }) => d.value) || [];
if (results.length === 1) { if (results.length === 1) {
source[label || c.label || name] = values; // If the metrics label does not exist, use the configuration label or expression
} else { label = label ? `${metricConfig.label || name}, ${label}` : metricConfig.label || name;
source[label] = values;
} }
source[label] = values;
} }
} }
if (([ExpressionResultType.RECORD_LIST, ExpressionResultType.SORTED_LIST] as string[]).includes(type)) { if (([ExpressionResultType.RECORD_LIST, ExpressionResultType.SORTED_LIST] as string[]).includes(type)) {
@ -148,7 +148,7 @@ export async function useDashboardQueryProcessor(configList: Indexable[]) {
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const variables: string[] = [`$duration: Duration!`]; const variables: string[] = [`$duration: Duration!`];
let fragments = ""; let fragments = "";
let conditions: Recordable = { let conditions: Recordable<unknown> = {
duration: appStore.durationTime, duration: appStore.durationTime,
}; };
for (let i = 0; i < configArr.length; i++) { for (let i = 0; i < configArr.length; i++) {
@ -465,7 +465,7 @@ export function useQueryTopologyExpressionsProcessor(metrics: string[], instance
return { queryStr, conditions }; return { queryStr, conditions };
} }
function handleExpressionValues(partMetrics: string[], resp: { [key: string]: any }) { function handleExpressionValues(partMetrics: string[], resp: { [key: string]: any }) {
const obj: any = {}; const obj: Indexable = {};
for (let idx = 0; idx < instances.length; idx++) { for (let idx = 0; idx < instances.length; idx++) {
for (let index = 0; index < partMetrics.length; index++) { for (let index = 0; index < partMetrics.length; index++) {
const k = "expression" + idx + index; const k = "expression" + idx + index;

View File

@ -136,6 +136,12 @@ const titles = {
"Cilium is a CNI plugin for Kubernetes that provides eBPF-based networking, security, and load balancing.", "Cilium is a CNI plugin for Kubernetes that provides eBPF-based networking, security, and load balancing.",
cilium_service: "Cilium Service", cilium_service: "Cilium Service",
cilium_service_desc: "Observe Service status and resources from Cilium Hubble.", cilium_service_desc: "Observe Service status and resources from Cilium Hubble.",
data_processing_engine: "Data Processing Engine",
data_processing_engine_desc:
"A data processing engine is a system designed to efficiently process, transform, and analyze large-scale data in real time or batch mode.",
data_processing_engine_flink: "Flink",
data_processing_engine_flink_desc:
"Apache Flink is a framework and distributed processing engine for stateful computations over unbounded and bounded data streams. Flink has been designed to run in all common cluster environments, perform computations at in-memory speed and at any scale.",
}; };
export default titles; export default titles;

View File

@ -137,6 +137,12 @@ const titles = {
"Cilium es un complemento CNI para Kubernetes que proporciona redes, seguridad y equilibrio de carga basados en eBPF.", "Cilium es un complemento CNI para Kubernetes que proporciona redes, seguridad y equilibrio de carga basados en eBPF.",
cilium_service: "Cilium Service", cilium_service: "Cilium Service",
cilium_service_desc: "Observe el estado del servicio y los recursos de Cilium Hubble.", cilium_service_desc: "Observe el estado del servicio y los recursos de Cilium Hubble.",
data_processing_engine: "Data Processing Engine",
data_processing_engine_desc:
"A data processing engine is a system designed to efficiently process, transform, and analyze large-scale data in real time or batch mode.",
data_processing_engine_flink: "Flink",
data_processing_engine_flink_desc:
"Apache Flink is a framework and distributed processing engine for stateful computations over unbounded and bounded data streams. Flink has been designed to run in all common cluster environments, perform computations at in-memory speed and at any scale.",
}; };
export default titles; export default titles;

View File

@ -119,6 +119,11 @@ const titles = {
cilium_desc: "Cilium是Kubernetes上的CNI插件提供基于eBPF的网络、安全和负载均衡。", cilium_desc: "Cilium是Kubernetes上的CNI插件提供基于eBPF的网络、安全和负载均衡。",
cilium_service: "Cilium服务", cilium_service: "Cilium服务",
cilium_service_desc: "通过Cilium Hubble收集的遥测数据观察服务。", cilium_service_desc: "通过Cilium Hubble收集的遥测数据观察服务。",
data_processing_engine: "数据处理引擎",
data_processing_engine_desc: "数据处理引擎是一个用于高效地在实时或批处理模式下处理、转换和分析大规模数据的系统。",
data_processing_engine_flink: "Flink",
data_processing_engine_flink_desc:
"Apache Flink 是一个框架和分布式处理引擎用于在无边界和有边界数据流上进行有状态的计算。Flink 能在所有常见集群环境中运行,并能以内存速度和任意规模进行计算。",
}; };
export default titles; export default titles;

View File

@ -37,16 +37,8 @@ const router = createRouter({
routes, routes,
}); });
(window as any).axiosCancel = []; router.beforeEach((to, _, next) => {
router.beforeEach((to, from, next) => {
// const token = window.localStorage.getItem("skywalking-authority"); // const token = window.localStorage.getItem("skywalking-authority");
if ((window as any).axiosCancel.length !== 0) {
for (const func of (window as any).axiosCancel) {
setTimeout(func(), 0);
}
(window as any).axiosCancel = [];
}
if (to.path === "/") { if (to.path === "/") {
let defaultPath = ""; let defaultPath = "";

View File

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

View File

@ -19,7 +19,6 @@ import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { Duration, DurationTime } from "@/types/app"; import type { Duration, DurationTime } from "@/types/app";
import getLocalTime from "@/utils/localtime"; import getLocalTime from "@/utils/localtime";
import type { AxiosResponse } from "axios";
import dateFormatStep, { dateFormatTime } from "@/utils/dateFormat"; import dateFormatStep, { dateFormatTime } from "@/utils/dateFormat";
import { TimeType } from "@/constants/data"; import { TimeType } from "@/constants/data";
import type { MenuOptions, SubItem } from "@/types/app"; import type { MenuOptions, SubItem } from "@/types/app";
@ -118,12 +117,6 @@ export const appStore = defineStore({
actions: { actions: {
setDuration(data: Duration): void { setDuration(data: Duration): void {
this.durationRow = data; this.durationRow = data;
if ((window as any).axiosCancel.length !== 0) {
for (const event of (window as any).axiosCancel) {
setTimeout(event(), 0);
}
(window as any).axiosCancel = [];
}
this.runEventStack(); this.runEventStack();
}, },
updateDurationRow(data: Duration) { updateDurationRow(data: Duration) {
@ -185,11 +178,11 @@ export const appStore = defineStore({
}); });
}, },
async queryOAPTimeInfo() { async queryOAPTimeInfo() {
const res: AxiosResponse = await graphql.query("queryOAPTimeInfo").params({}); const res = await graphql.query("queryOAPTimeInfo").params({});
if (res.data.errors) { if (res.errors) {
this.utc = -(new Date().getTimezoneOffset() / 60) + ":0"; this.utc = -(new Date().getTimezoneOffset() / 60) + ":0";
} else { } else {
this.utc = res.data.data.getTimeInfo.timezone / 100 + ":0"; this.utc = res.data.getTimeInfo.timezone / 100 + ":0";
} }
const utcArr = this.utc.split(":"); const utcArr = this.utc.split(":");
this.utcHour = isNaN(Number(utcArr[0])) ? 0 : Number(utcArr[0]); this.utcHour = isNaN(Number(utcArr[0])) ? 0 : Number(utcArr[0]);
@ -197,21 +190,21 @@ export const appStore = defineStore({
return res.data; return res.data;
}, },
async fetchVersion(): Promise<void> { async fetchVersion() {
const res: AxiosResponse = await graphql.query("queryOAPVersion").params({}); const res = await graphql.query("queryOAPVersion").params({});
if (res.data.errors) { if (res.errors) {
return res.data; return res;
} }
this.version = res.data.data.version; this.version = res.data.version;
return res.data; return res.data;
}, },
async queryMenuItems() { async queryMenuItems() {
const res: AxiosResponse = await graphql.query("queryMenuItems").params({}); const res = await graphql.query("queryMenuItems").params({});
if (res.data.errors) { if (res.errors) {
return res.data; return res;
} }
return res.data.data; return res.data;
}, },
setReloadTimer(timer: IntervalHandle) { setReloadTimer(timer: IntervalHandle) {
this.reloadTimer = timer; this.reloadTimer = timer;

View File

@ -25,7 +25,6 @@ import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import type { AxiosResponse } from "axios";
import type { Instance } from "@/types/selector"; import type { Instance } from "@/types/selector";
interface AsyncProfilingState { interface AsyncProfilingState {
@ -59,79 +58,77 @@ export const asyncProfilingStore = defineStore({
async getTaskList() { async getTaskList() {
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
this.loadingTasks = true; this.loadingTasks = true;
const res: AxiosResponse = await graphql.query("getAsyncTaskList").params({ const response = await graphql.query("getAsyncTaskList").params({
request: { request: {
serviceId: selectorStore.currentService.id, serviceId: selectorStore.currentService.id,
limit: 10000, limit: 10000,
}, },
}); });
this.loadingTasks = false; this.loadingTasks = false;
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.taskList = res.data.data.asyncTaskList.tasks || []; this.taskList = response.data.asyncTaskList.tasks || [];
this.selectedTask = this.taskList[0] || {}; this.selectedTask = this.taskList[0] || {};
this.setAnalyzeTrees([]); this.setAnalyzeTrees([]);
this.setSelectedTask(this.selectedTask); this.setSelectedTask(this.selectedTask);
if (!this.taskList.length) { if (!this.taskList.length) {
return res.data; return response;
} }
return res.data; return response;
}, },
async getTaskLogs(param: { taskID: string }) { async getTaskLogs(param: { taskID: string }) {
const res: AxiosResponse = await graphql.query("getAsyncProfileTaskProcess").params(param); const response = await graphql.query("getAsyncProfileTaskProcess").params(param);
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.taskProgress = res.data.data.taskProgress; this.taskProgress = response.data.taskProgress;
return res.data; return response;
}, },
async getServiceInstances(param: { serviceId: string; isRelation: boolean }): Promise<Nullable<AxiosResponse>> { async getServiceInstances(param: { serviceId: string; isRelation: boolean }) {
if (!param.serviceId) { if (!param.serviceId) {
return null; return null;
} }
const res: AxiosResponse = await graphql.query("queryInstances").params({ const response = await graphql.query("queryInstances").params({
serviceId: param.serviceId, serviceId: param.serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
}); });
if (!res.data.errors) { if (!response.errors) {
this.instances = (res.data.data.pods || []).map((d: Instance) => { this.instances = (response.data.pods || []).map((d: Instance) => {
d.value = d.id || ""; d.value = d.id || "";
return d; return d;
}); });
} }
return res.data; return response;
}, },
async createTask(param: AsyncProfileTaskCreationRequest) { async createTask(param: AsyncProfileTaskCreationRequest) {
const res: AxiosResponse = await graphql const response = await graphql.query("saveAsyncProfileTask").params({ asyncProfilerTaskCreationRequest: param });
.query("saveAsyncProfileTask")
.params({ asyncProfilerTaskCreationRequest: param });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.getTaskList(); this.getTaskList();
return res.data; return response;
}, },
async getAsyncProfilingAnalyze(params: { taskId: string; instanceIds: Array<string>; eventType: string }) { async getAsyncProfilingAnalyze(params: { taskId: string; instanceIds: Array<string>; eventType: string }) {
if (!params.instanceIds.length) { if (!params.instanceIds.length) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
this.loadingTree = true; this.loadingTree = true;
const res: AxiosResponse = await graphql.query("getAsyncProfileAnalyze").params({ request: params }); const response = await graphql.query("getAsyncProfileAnalyze").params({ request: params });
this.loadingTree = false; this.loadingTree = false;
if (res.data.errors) { if (response.errors) {
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
const { analysisResult } = res.data.data; const { analysisResult } = response.data;
if (!analysisResult) { if (!analysisResult) {
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
this.analyzeTrees = [analysisResult.tree]; this.analyzeTrees = [analysisResult.tree];
return res.data; return response;
}, },
}, },
}); });

View File

@ -21,7 +21,6 @@ import type { Instance } from "@/types/selector";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { MonitorInstance, MonitorProcess } from "@/types/continous-profiling"; import type { MonitorInstance, MonitorProcess } from "@/types/continous-profiling";
import type { AxiosResponse } from "axios";
import { dateFormat } from "@/utils/dateFormat"; import { dateFormat } from "@/utils/dateFormat";
interface ContinousProfilingState { interface ContinousProfilingState {
@ -84,37 +83,37 @@ export const continousProfilingStore = defineStore({
checkItems: CheckItems[]; checkItems: CheckItems[];
}[], }[],
) { ) {
const res: AxiosResponse = await graphql.query("editStrategy").params({ const response = await graphql.query("editStrategy").params({
request: { request: {
serviceId, serviceId,
targets, targets,
}, },
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
return res.data; return response;
}, },
async getStrategyList(params: { serviceId: string }) { async getStrategyList(params: { serviceId: string }) {
if (!params.serviceId) { if (!params.serviceId) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
this.policyLoading = true; this.policyLoading = true;
const res: AxiosResponse = await graphql.query("getStrategyList").params(params); const response = await graphql.query("getStrategyList").params(params);
this.policyLoading = false; this.policyLoading = false;
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
const list = res.data.data.strategyList || []; const list = response.data.strategyList || [];
if (!list.length) { if (!list.length) {
this.taskList = []; this.taskList = [];
this.instances = []; this.instances = [];
this.instance = null; this.instance = null;
} }
const arr = list.length ? res.data.data.strategyList : [{ type: "", checkItems: [{ type: "" }] }]; const arr = list.length ? response.data.strategyList : [{ type: "", checkItems: [{ type: "" }] }];
this.strategyList = arr.map((d: StrategyItem, index: number) => { this.strategyList = arr.map((d: StrategyItem, index: number) => {
return { return {
...d, ...d,
@ -123,25 +122,25 @@ export const continousProfilingStore = defineStore({
}); });
this.setSelectedStrategy(this.strategyList[0]); this.setSelectedStrategy(this.strategyList[0]);
if (!this.selectedStrategy.type) { if (!this.selectedStrategy.type) {
return res.data; return response;
} }
this.getMonitoringInstances(params.serviceId); this.getMonitoringInstances(params.serviceId);
return res.data; return response;
}, },
async getMonitoringInstances(serviceId: string): Promise<Nullable<AxiosResponse>> { async getMonitoringInstances(serviceId: string) {
this.instancesLoading = true; this.instancesLoading = true;
if (!serviceId) { if (!serviceId) {
return null; return null;
} }
const res: AxiosResponse = await graphql.query("getMonitoringInstances").params({ const response = await graphql.query("getMonitoringInstances").params({
serviceId, serviceId,
target: this.selectedStrategy.type, target: this.selectedStrategy.type,
}); });
this.instancesLoading = false; this.instancesLoading = false;
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.instances = (res.data.data.instances || []) this.instances = (response.data.instances || [])
.map((d: MonitorInstance) => { .map((d: MonitorInstance) => {
const processes = (d.processes || []) const processes = (d.processes || [])
.sort((c: MonitorProcess, d: MonitorProcess) => d.lastTriggerTimestamp - c.lastTriggerTimestamp) .sort((c: MonitorProcess, d: MonitorProcess) => d.lastTriggerTimestamp - c.lastTriggerTimestamp)
@ -161,7 +160,7 @@ export const continousProfilingStore = defineStore({
}) })
.sort((a: MonitorInstance, b: MonitorInstance) => b.lastTriggerTimestamp - a.lastTriggerTimestamp); .sort((a: MonitorInstance, b: MonitorInstance) => b.lastTriggerTimestamp - a.lastTriggerTimestamp);
this.instance = this.instances[0] || null; this.instance = this.instances[0] || null;
return res.data; return response;
}, },
}, },
}); });

View File

@ -18,11 +18,10 @@ import { defineStore } from "pinia";
import { store } from "@/store"; import { store } from "@/store";
import type { LayoutConfig } from "@/types/dashboard"; import type { LayoutConfig } from "@/types/dashboard";
import graphql from "@/graphql"; import graphql from "@/graphql";
import query from "@/graphql/fetch"; import fetchQuery from "@/graphql/fetch";
import type { DashboardItem } from "@/types/dashboard"; import type { DashboardItem } from "@/types/dashboard";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { NewControl, TextConfig, TimeRangeConfig, ControlsTypes } from "../data"; import { NewControl, TextConfig, TimeRangeConfig, ControlsTypes } from "../data";
import type { AxiosResponse } from "axios";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { EntityType, WidgetType } from "@/views/dashboard/data"; import { EntityType, WidgetType } from "@/views/dashboard/data";
interface DashboardState { interface DashboardState {
@ -299,37 +298,20 @@ export const dashboardStore = defineStore({
} }
} }
}, },
async fetchMetricType(item: string) {
const res: AxiosResponse = await graphql.query("queryTypeOfMetrics").params({ name: item });
return res.data;
},
async getTypeOfMQE(expression: string) {
const res: AxiosResponse = await graphql.query("getTypeOfMQE").params({ expression });
return res.data;
},
async fetchMetricList(regex: string) {
const res: AxiosResponse = await graphql.query("queryMetrics").params({ regex });
return res.data;
},
async fetchMetricValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) { async fetchMetricValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const res: AxiosResponse = await query(param); return await fetchQuery(param);
return res.data;
}, },
async fetchTemplates() { async fetchTemplates() {
const res: AxiosResponse = await graphql.query("getTemplates").params({}); const res = await graphql.query("getTemplates").params({});
if (res.data.errors) { if (res.errors) {
return res.data; return res;
} }
const data = res.data.data.getAllTemplates; const data = res.data.getAllTemplates;
let list = []; let list = [];
for (const t of data) { for (const t of data) {
const c = JSON.parse(t.configuration); const c = JSON.parse(t.configuration);
const key = [c.layer, c.entity, c.name].join("_"); const key = [c.layer, c.entity, c.name].join("_");
list.push({ list.push({
...c, ...c,
id: t.id, id: t.id,
@ -372,20 +354,20 @@ export const dashboardStore = defineStore({
this.dashboards = JSON.parse(sessionStorage.getItem("dashboards") || "[]"); this.dashboards = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
}, },
async updateDashboard(setting: { id: string; configuration: string }) { async updateDashboard(setting: { id: string; configuration: string }) {
const res: AxiosResponse = await graphql.query("updateTemplate").params({ const resp = await graphql.query("updateTemplate").params({
setting, setting,
}); });
if (res.data.errors) { if (resp.errors) {
ElMessage.error(res.data.errors); ElMessage.error(resp.errors);
return res.data; return resp;
} }
const json = res.data.data.changeTemplate; const json = resp.data.changeTemplate;
if (!json.status) { if (!json.status) {
ElMessage.error(json.message); ElMessage.error(json.message);
return res.data; return resp;
} }
ElMessage.success("Saved successfully"); ElMessage.success("Saved successfully");
return res.data; return resp;
}, },
async saveDashboard() { async saveDashboard() {
if (!this.currentDashboard?.name) { if (!this.currentDashboard?.name) {
@ -419,14 +401,14 @@ export const dashboardStore = defineStore({
} }
res = await graphql.query("addNewTemplate").params({ setting: { configuration: JSON.stringify(c) } }); res = await graphql.query("addNewTemplate").params({ setting: { configuration: JSON.stringify(c) } });
json = res.data.data.addTemplate; json = res.data.addTemplate;
if (!json.status) { if (!json.status) {
ElMessage.error(json.message); ElMessage.error(json.message);
} }
} }
if (res.data.errors || res.errors) { if (res.errors) {
ElMessage.error(res.data.errors); ElMessage.error(res.errors);
return res.data; return res;
} }
if (!json.status) { if (!json.status) {
return json; return json;
@ -448,16 +430,16 @@ export const dashboardStore = defineStore({
return json; return json;
}, },
async deleteDashboard() { async deleteDashboard() {
const res: AxiosResponse = await graphql.query("removeTemplate").params({ id: this.currentDashboard?.id }); const res = await graphql.query("removeTemplate").params({ id: this.currentDashboard?.id });
if (res.data.errors) { if (res.errors) {
ElMessage.error(res.data.errors); ElMessage.error(res.errors);
return res.data; return res;
} }
const json = res.data.data.disableTemplate; const json = res.data.disableTemplate;
if (!json.status) { if (!json.status) {
ElMessage.error(json.message); ElMessage.error(json.message);
return res.data; return res;
} }
this.dashboards = this.dashboards.filter((d: Recordable) => d.id !== this.currentDashboard?.id); this.dashboards = this.dashboards.filter((d: Recordable) => d.id !== this.currentDashboard?.id);
const key = [this.currentDashboard?.layer, this.currentDashboard?.entity, this.currentDashboard?.name].join("_"); const key = [this.currentDashboard?.layer, this.currentDashboard?.entity, this.currentDashboard?.name].join("_");

View File

@ -18,7 +18,6 @@ import { defineStore } from "pinia";
import type { Instance } from "@/types/selector"; import type { Instance } from "@/types/selector";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import type { Conditions, Log } from "@/types/demand-log"; import type { Conditions, Log } from "@/types/demand-log";
@ -60,16 +59,16 @@ export const demandLogStore = defineStore({
}, },
async getInstances(id: string) { async getInstances(id: string) {
const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id; const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id;
const res: AxiosResponse = await graphql.query("queryInstances").params({ const response = await graphql.query("queryInstances").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.instances = res.data.data.pods || []; this.instances = response.data.pods || [];
return res.data; return response;
}, },
async getContainers(serviceInstanceId: string) { async getContainers(serviceInstanceId: string) {
if (!serviceInstanceId) { if (!serviceInstanceId) {
@ -78,35 +77,35 @@ export const demandLogStore = defineStore({
const condition = { const condition = {
serviceInstanceId, serviceInstanceId,
}; };
const res: AxiosResponse = await graphql.query("fetchContainers").params({ condition }); const response = await graphql.query("fetchContainers").params({ condition });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
if (res.data.data.containers.errorReason) { if (response.data.containers.errorReason) {
this.containers = [{ label: "", value: "" }]; this.containers = [{ label: "", value: "" }];
return res.data; return response;
} }
this.containers = res.data.data.containers.containers.map((d: string) => { this.containers = response.data.containers.containers.map((d: string) => {
return { label: d, value: d }; return { label: d, value: d };
}); });
return res.data; return response;
}, },
async getDemandLogs() { async getDemandLogs() {
this.loadLogs = true; this.loadLogs = true;
const res: AxiosResponse = await graphql.query("fetchDemandPodLogs").params({ condition: this.conditions }); const response = await graphql.query("fetchDemandPodLogs").params({ condition: this.conditions });
this.loadLogs = false; this.loadLogs = false;
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
if (res.data.data.logs.errorReason) { if (response.data.logs.errorReason) {
this.setLogs([], res.data.data.logs.errorReason); this.setLogs([], response.data.logs.errorReason);
return res.data; return response;
} }
this.total = res.data.data.logs.logs.length; this.total = response.data.logs.logs.length;
const logs = res.data.data.logs.logs.map((d: Log) => d.content).join("\n"); const logs = response.data.logs.logs.map((d: Log) => d.content).join("\n");
this.setLogs(logs); this.setLogs(logs);
return res.data; return response;
}, },
}, },
}); });

View File

@ -19,7 +19,6 @@ import type { Option } from "@/types/app";
import type { EBPFTaskCreationRequest, EBPFProfilingSchedule, EBPFTaskList, AnalyzationTrees } from "@/types/ebpf"; import type { EBPFTaskCreationRequest, EBPFProfilingSchedule, EBPFTaskList, AnalyzationTrees } from "@/types/ebpf";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { EBPFProfilingTriggerType } from "../data"; import { EBPFProfilingTriggerType } from "../data";
interface EbpfState { interface EbpfState {
taskList: Array<Recordable<EBPFTaskList>>; taskList: Array<Recordable<EBPFTaskList>>;
@ -57,70 +56,70 @@ export const ebpfStore = defineStore({
this.analyzeTrees = tree; this.analyzeTrees = tree;
}, },
async getCreateTaskData(serviceId: string) { async getCreateTaskData(serviceId: string) {
const res: AxiosResponse = await graphql.query("getCreateTaskData").params({ serviceId }); const response = await graphql.query("getCreateTaskData").params({ serviceId });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
const json = res.data.data.createTaskData; const json = response.data.createTaskData;
this.couldProfiling = json.couldProfiling || false; this.couldProfiling = json.couldProfiling || false;
this.labels = json.processLabels.map((d: string) => { this.labels = json.processLabels.map((d: string) => {
return { label: d, value: d }; return { label: d, value: d };
}); });
return res.data; return response;
}, },
async createTask(param: EBPFTaskCreationRequest) { async createTask(param: EBPFTaskCreationRequest) {
const res: AxiosResponse = await graphql.query("saveEBPFTask").params({ request: param }); const response = await graphql.query("saveEBPFTask").params({ request: param });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.getTaskList({ this.getTaskList({
serviceId: param.serviceId, serviceId: param.serviceId,
targets: ["ON_CPU", "OFF_CPU"], targets: ["ON_CPU", "OFF_CPU"],
triggerType: EBPFProfilingTriggerType.FIXED_TIME, triggerType: EBPFProfilingTriggerType.FIXED_TIME,
}); });
return res.data; return response;
}, },
async getTaskList(params: { serviceId: string; targets: string[] }) { async getTaskList(params: { serviceId: string; targets: string[] }) {
if (!params.serviceId) { if (!params.serviceId) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql.query("getEBPFTasks").params(params); const response = await graphql.query("getEBPFTasks").params(params);
this.ebpfTips = ""; this.ebpfTips = "";
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.taskList = res.data.data.queryEBPFTasks || []; this.taskList = response.data.queryEBPFTasks || [];
this.selectedTask = this.taskList[0] || {}; this.selectedTask = this.taskList[0] || {};
this.setSelectedTask(this.selectedTask); this.setSelectedTask(this.selectedTask);
if (!this.taskList.length) { if (!this.taskList.length) {
return res.data; return response;
} }
this.getEBPFSchedules({ taskId: String(this.taskList[0].taskId) }); this.getEBPFSchedules({ taskId: String(this.taskList[0].taskId) });
return res.data; return response;
}, },
async getEBPFSchedules(params: { taskId: string }) { async getEBPFSchedules(params: { taskId: string }) {
if (!params.taskId) { if (!params.taskId) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql.query("getEBPFSchedules").params({ ...params }); const response = await graphql.query("getEBPFSchedules").params({ ...params });
if (res.data.errors) { if (response.errors) {
this.eBPFSchedules = []; this.eBPFSchedules = [];
return res.data; return response;
} }
this.ebpfTips = ""; this.ebpfTips = "";
const { eBPFSchedules } = res.data.data; const { eBPFSchedules } = response.data;
this.eBPFSchedules = eBPFSchedules; this.eBPFSchedules = eBPFSchedules;
if (!eBPFSchedules.length) { if (!eBPFSchedules.length) {
this.eBPFSchedules = []; this.eBPFSchedules = [];
this.analyzeTrees = []; this.analyzeTrees = [];
} }
return res.data; return response;
}, },
async getEBPFAnalyze(params: { async getEBPFAnalyze(params: {
scheduleIdList: string[]; scheduleIdList: string[];
@ -134,24 +133,24 @@ export const ebpfStore = defineStore({
if (!params.timeRanges.length) { if (!params.timeRanges.length) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql.query("getEBPFResult").params(params); const response = await graphql.query("getEBPFResult").params(params);
if (res.data.errors) { if (response.errors) {
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
const { analysisEBPFResult } = res.data.data; const { analysisEBPFResult } = response.data;
this.ebpfTips = analysisEBPFResult.tip; this.ebpfTips = analysisEBPFResult.tip;
if (!analysisEBPFResult) { if (!analysisEBPFResult) {
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
if (analysisEBPFResult.tip) { if (analysisEBPFResult.tip) {
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
this.analyzeTrees = analysisEBPFResult.trees; this.analyzeTrees = analysisEBPFResult.trees;
return res.data; return response;
}, },
}, },
}); });

View File

@ -17,7 +17,6 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import type { Event, QueryEventCondition } from "@/types/events"; import type { Event, QueryEventCondition } from "@/types/events";
import type { Instance, Endpoint } from "@/types/selector"; import type { Instance, Endpoint } from "@/types/selector";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
@ -47,48 +46,48 @@ export const eventStore = defineStore({
}, },
async getInstances() { async getInstances() {
const serviceId = useSelectorStore().currentService ? useSelectorStore().currentService.id : ""; const serviceId = useSelectorStore().currentService ? useSelectorStore().currentService.id : "";
const res: AxiosResponse = await graphql.query("queryInstances").params({ const response = await graphql.query("queryInstances").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.instances = [{ value: "", label: "All" }, ...res.data.data.pods]; this.instances = [{ value: "", label: "All" }, ...response.data.pods];
return res.data; return response;
}, },
async getEndpoints(keyword: string) { async getEndpoints(keyword: string) {
const serviceId = useSelectorStore().currentService ? useSelectorStore().currentService.id : ""; const serviceId = useSelectorStore().currentService ? useSelectorStore().currentService.id : "";
if (!serviceId) { if (!serviceId) {
return; return;
} }
const res: AxiosResponse = await graphql.query("queryEndpoints").params({ const response = await graphql.query("queryEndpoints").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
keyword: keyword || "", keyword: keyword || "",
limit: EndpointsTopNDefault, limit: EndpointsTopNDefault,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.endpoints = [{ value: "", label: "All" }, ...res.data.data.pods]; this.endpoints = [{ value: "", label: "All" }, ...response.data.pods];
return res.data; return response;
}, },
async getEvents() { async getEvents() {
this.loading = true; this.loading = true;
const res: AxiosResponse = await graphql.query("queryEvents").params({ const response = await graphql.query("queryEvents").params({
condition: { condition: {
...this.condition, ...this.condition,
time: useAppStoreWithOut().durationTime, time: useAppStoreWithOut().durationTime,
}, },
}); });
this.loading = false; this.loading = false;
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
if (res.data.data.fetchEvents) { if (response.data.fetchEvents) {
this.events = (res.data.data.fetchEvents.events || []).map((item: Event) => { this.events = (response.data.fetchEvents.events || []).map((item: Event) => {
let scope = "Service"; let scope = "Service";
if (item.source.serviceInstance) { if (item.source.serviceInstance) {
scope = "ServiceInstance"; scope = "ServiceInstance";
@ -103,7 +102,7 @@ export const eventStore = defineStore({
return item; return item;
}); });
} }
return res.data; return response;
}, },
}, },
}); });

View File

@ -18,7 +18,6 @@ import { defineStore } from "pinia";
import type { Instance, Endpoint, Service } from "@/types/selector"; import type { Instance, Endpoint, Service } from "@/types/selector";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
@ -62,51 +61,51 @@ export const logStore = defineStore({
}; };
}, },
async getServices(layer: string) { async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({ const response = await graphql.query("queryServices").params({
layer, layer,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.services = res.data.data.services; this.services = response.data.services;
return res.data; return response;
}, },
async getInstances(id: string) { async getInstances(id: string) {
const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id; const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id;
const res: AxiosResponse = await graphql.query("queryInstances").params({ const response = await graphql.query("queryInstances").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.instances = [{ value: "0", label: "All" }, ...res.data.data.pods]; this.instances = [{ value: "0", label: "All" }, ...response.data.pods];
return res.data; return response;
}, },
async getEndpoints(id: string, keyword?: string) { async getEndpoints(id: string, keyword?: string) {
const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id; const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id;
const res: AxiosResponse = await graphql.query("queryEndpoints").params({ const response = await graphql.query("queryEndpoints").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
keyword: keyword || "", keyword: keyword || "",
limit: EndpointsTopNDefault, limit: EndpointsTopNDefault,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.endpoints = [{ value: "0", label: "All" }, ...res.data.data.pods]; this.endpoints = [{ value: "0", label: "All" }, ...response.data.pods];
return res.data; return response;
}, },
async getLogsByKeywords() { async getLogsByKeywords() {
const res: AxiosResponse = await graphql.query("queryLogsByKeywords").params({}); const response = await graphql.query("queryLogsByKeywords").params({});
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.supportQueryLogsByKeywords = res.data.data.support; this.supportQueryLogsByKeywords = response.data.support;
return res.data; return response;
}, },
async getLogs() { async getLogs() {
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
@ -117,39 +116,31 @@ export const logStore = defineStore({
}, },
async getServiceLogs() { async getServiceLogs() {
this.loadLogs = true; this.loadLogs = true;
const res: AxiosResponse = await graphql.query("queryServiceLogs").params({ condition: this.conditions }); const response = await graphql.query("queryServiceLogs").params({ condition: this.conditions });
this.loadLogs = false; this.loadLogs = false;
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.logs = res.data.data.queryLogs.logs; this.logs = response.data.queryLogs.logs;
return res.data; return response;
}, },
async getBrowserLogs() { async getBrowserLogs() {
this.loadLogs = true; this.loadLogs = true;
const res: AxiosResponse = await graphql.query("queryBrowserErrorLogs").params({ condition: this.conditions }); const response = await graphql.query("queryBrowserErrorLogs").params({ condition: this.conditions });
this.loadLogs = false; this.loadLogs = false;
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.logs = res.data.data.queryBrowserErrorLogs.logs; this.logs = response.data.queryBrowserErrorLogs.logs;
return res.data; return response;
}, },
async getLogTagKeys() { async getLogTagKeys() {
const res: AxiosResponse = await graphql return await graphql.query("queryLogTagKeys").params({ duration: useAppStoreWithOut().durationTime });
.query("queryLogTagKeys")
.params({ duration: useAppStoreWithOut().durationTime });
return res.data;
}, },
async getLogTagValues(tagKey: string) { async getLogTagValues(tagKey: string) {
const res: AxiosResponse = await graphql return await graphql.query("queryLogTagValues").params({ tagKey, duration: useAppStoreWithOut().durationTime });
.query("queryLogTagValues")
.params({ tagKey, duration: useAppStoreWithOut().durationTime });
return res.data;
}, },
}, },
}); });

View File

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

View File

@ -26,7 +26,6 @@ import type {
import type { Trace } from "@/types/trace"; import type { Trace } from "@/types/trace";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { EndpointsTopNDefault } from "../data"; import { EndpointsTopNDefault } from "../data";
@ -94,38 +93,38 @@ export const profileStore = defineStore({
this.highlightTop = !this.highlightTop; this.highlightTop = !this.highlightTop;
}, },
async getEndpoints(serviceId: string, keyword?: string) { async getEndpoints(serviceId: string, keyword?: string) {
const res: AxiosResponse = await graphql.query("queryEndpoints").params({ const response = await graphql.query("queryEndpoints").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
keyword: keyword || "", keyword: keyword || "",
limit: EndpointsTopNDefault, limit: EndpointsTopNDefault,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.endpoints = res.data.data.pods || []; this.endpoints = response.data.pods || [];
return res.data; return response.data;
}, },
async getTaskEndpoints(serviceId: string, keyword?: string) { async getTaskEndpoints(serviceId: string, keyword?: string) {
const res: AxiosResponse = await graphql.query("queryEndpoints").params({ const response = await graphql.query("queryEndpoints").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
keyword: keyword || "", keyword: keyword || "",
limit: EndpointsTopNDefault, limit: EndpointsTopNDefault,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.taskEndpoints = [{ value: "", label: "All" }, ...res.data.data.pods]; this.taskEndpoints = [{ value: "", label: "All" }, ...response.data.pods];
return res.data; return response;
}, },
async getTaskList() { async getTaskList() {
const res: AxiosResponse = await graphql.query("getProfileTaskList").params(this.condition); const response = await graphql.query("getProfileTaskList").params(this.condition);
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
const list = res.data.data.taskList || []; const list = response.data.taskList || [];
this.taskList = list; this.taskList = list;
this.currentTask = list[0] || {}; this.currentTask = list[0] || {};
if (!list.length) { if (!list.length) {
@ -133,29 +132,29 @@ export const profileStore = defineStore({
this.segmentSpans = []; this.segmentSpans = [];
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
this.getSegmentList({ taskID: list[0].id }); this.getSegmentList({ taskID: list[0].id });
return res.data; return response;
}, },
async getSegmentList(params: { taskID: string }) { async getSegmentList(params: { taskID: string }) {
if (!params.taskID) { if (!params.taskID) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql.query("getProfileTaskSegmentList").params(params); const response = await graphql.query("getProfileTaskSegmentList").params(params);
if (res.data.errors) { if (response.errors) {
this.segmentList = []; this.segmentList = [];
return res.data; return response;
} }
const { segmentList } = res.data.data; const { segmentList } = response.data;
this.segmentList = segmentList || []; this.segmentList = segmentList || [];
if (!segmentList.length) { if (!segmentList.length) {
this.segmentSpans = []; this.segmentSpans = [];
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
if (segmentList[0]) { if (segmentList[0]) {
this.setCurrentSegment(segmentList[0]); this.setCurrentSegment(segmentList[0]);
@ -163,22 +162,22 @@ export const profileStore = defineStore({
} else { } else {
this.setCurrentSegment({}); this.setCurrentSegment({});
} }
return res.data; return response;
}, },
async getSegmentSpans(params: { segmentId: string }) { async getSegmentSpans(params: { segmentId: string }) {
if (!(params && params.segmentId)) { if (!(params && params.segmentId)) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql.query("queryProfileSegment").params(params); const response = await graphql.query("queryProfileSegment").params(params);
if (res.data.errors) { if (response.errors) {
this.segmentSpans = []; this.segmentSpans = [];
return res.data; return response;
} }
const { segment } = res.data.data; const { segment } = response.data;
if (!segment) { if (!segment) {
this.segmentSpans = []; this.segmentSpans = [];
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
this.segmentSpans = segment.spans.map((d: SegmentSpan) => { this.segmentSpans = segment.spans.map((d: SegmentSpan) => {
return { return {
@ -189,52 +188,52 @@ export const profileStore = defineStore({
}); });
if (!(segment.spans && segment.spans.length)) { if (!(segment.spans && segment.spans.length)) {
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
const index = segment.spans.length - 1 || 0; const index = segment.spans.length - 1 || 0;
this.currentSpan = segment.spans[index]; this.currentSpan = segment.spans[index];
return res.data; return response;
}, },
async getProfileAnalyze(params: Array<{ segmentId: string; timeRange: { start: number; end: number } }>) { async getProfileAnalyze(params: Array<{ segmentId: string; timeRange: { start: number; end: number } }>) {
if (!params.length) { if (!params.length) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql.query("getProfileAnalyze").params({ queries: params }); const response = await graphql.query("getProfileAnalyze").params({ queries: params });
if (res.data.errors) { if (response.errors) {
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
const { analyze, tip } = res.data.data; const { analyze, tip } = response.data;
if (tip) { if (tip) {
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
if (!analyze) { if (!analyze) {
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return response;
} }
this.analyzeTrees = analyze.trees; this.analyzeTrees = analyze.trees;
return res.data; return response;
}, },
async createTask(param: ProfileTaskCreationRequest) { async createTask(param: ProfileTaskCreationRequest) {
const res: AxiosResponse = await graphql.query("saveProfileTask").params({ creationRequest: param }); const response = await graphql.query("saveProfileTask").params({ creationRequest: param });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.getTaskList(); this.getTaskList();
return res.data; return response;
}, },
async getTaskLogs(param: { taskID: string }) { async getTaskLogs(param: { taskID: string }) {
const res: AxiosResponse = await graphql.query("getProfileTaskLogs").params(param); const response = await graphql.query("getProfileTaskLogs").params(param);
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.taskLogs = res.data.data.taskLogs; this.taskLogs = response.data.taskLogs;
return res.data; return response;
}, },
}, },
}); });

View File

@ -18,7 +18,6 @@ import { defineStore } from "pinia";
import type { Service, Instance, Endpoint, Process } from "@/types/selector"; import type { Service, Instance, Endpoint, Process } from "@/types/selector";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { EndpointsTopNDefault } from "../data"; import { EndpointsTopNDefault } from "../data";
interface SelectorState { interface SelectorState {
@ -77,62 +76,55 @@ export const selectorStore = defineStore({
setDestProcesses(processes: Array<Process>) { setDestProcesses(processes: Array<Process>) {
this.destProcesses = processes; this.destProcesses = processes;
}, },
async fetchLayers(): Promise<AxiosResponse> { async fetchLayers() {
const res: AxiosResponse = await graphql.query("queryLayers").params({}); return await graphql.query("queryLayers").params({});
return res.data || {};
}, },
async fetchServices(layer: string): Promise<AxiosResponse> { async fetchServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({ layer }); const res = await graphql.query("queryServices").params({ layer });
if (!res.data.errors) { if (!res.errors) {
this.services = res.data.data.services || []; this.services = res.data.services || [];
this.destServices = res.data.data.services || []; this.destServices = res.data.services || [];
} }
return res.data; return res.data;
}, },
async getServiceInstances(param?: { serviceId: string; isRelation: boolean }): Promise<Nullable<AxiosResponse>> { async getServiceInstances(param?: { serviceId: string; isRelation: boolean }) {
const serviceId = param ? param.serviceId : this.currentService?.id; const serviceId = param ? param.serviceId : this.currentService?.id;
if (!serviceId) { if (!serviceId) {
return null; return null;
} }
const res: AxiosResponse = await graphql.query("queryInstances").params({ const resp = await graphql.query("queryInstances").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
}); });
if (!res.data.errors) { if (!resp.errors) {
if (param && param.isRelation) { if (param && param.isRelation) {
this.destPods = res.data.data.pods || []; this.destPods = resp.data.pods || [];
return res.data; return resp;
} }
this.pods = res.data.data.pods || []; this.pods = resp.data.pods || [];
} }
return res.data; return resp;
}, },
async getProcesses(param?: { instanceId: string; isRelation: boolean }): Promise<Nullable<AxiosResponse>> { async getProcesses(param?: { instanceId: string; isRelation: boolean }) {
const instanceId = param ? param.instanceId : this.currentPod?.id; const instanceId = param ? param.instanceId : this.currentPod?.id;
if (!instanceId) { if (!instanceId) {
return null; return null;
} }
const res: AxiosResponse = await graphql.query("queryProcesses").params({ const res = await graphql.query("queryProcesses").params({
instanceId, instanceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
}); });
if (!res.data.errors) { if (!res.errors) {
if (param && param.isRelation) { if (param && param.isRelation) {
this.destProcesses = res.data.data.processes || []; this.destProcesses = res.data.processes || [];
return res.data; return res;
} }
this.processes = res.data.data.processes || []; this.processes = res.data.processes || [];
} }
return res.data; return res;
}, },
async getEndpoints(params: { async getEndpoints(params: { keyword?: string; serviceId?: string; isRelation?: boolean; limit?: number }) {
keyword?: string;
serviceId?: string;
isRelation?: boolean;
limit?: number;
}): Promise<Nullable<AxiosResponse>> {
if (!params) { if (!params) {
params = {}; params = {};
} }
@ -140,96 +132,96 @@ export const selectorStore = defineStore({
if (!serviceId) { if (!serviceId) {
return null; return null;
} }
const res: AxiosResponse = await graphql.query("queryEndpoints").params({ const res = await graphql.query("queryEndpoints").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
keyword: params.keyword || "", keyword: params.keyword || "",
limit: params.limit || EndpointsTopNDefault, limit: params.limit || EndpointsTopNDefault,
}); });
if (!res.data.errors) { if (!res.errors) {
if (params.isRelation) { if (params.isRelation) {
this.destPods = res.data.data.pods || []; this.destPods = res.data.pods || [];
return res.data; return res;
} }
this.pods = res.data.data.pods || []; this.pods = res.data.pods || [];
} }
return res.data; return res;
}, },
async getService(serviceId: string, isRelation: boolean) { async getService(serviceId: string, isRelation: boolean) {
if (!serviceId) { if (!serviceId) {
return; return;
} }
const res: AxiosResponse = await graphql.query("queryService").params({ const res = await graphql.query("queryService").params({
serviceId, serviceId,
}); });
if (!res.data.errors) { if (!res.errors) {
if (isRelation) { if (isRelation) {
this.setCurrentDestService(res.data.data.service); this.setCurrentDestService(res.data.service);
this.destServices = [res.data.data.service]; this.destServices = [res.data.service];
return res.data; return res;
} }
this.setCurrentService(res.data.data.service); this.setCurrentService(res.data.service);
this.services = [res.data.data.service]; this.services = [res.data.service];
} }
return res.data; return res;
}, },
async getInstance(instanceId: string, isRelation?: boolean) { async getInstance(instanceId: string, isRelation?: boolean) {
if (!instanceId) { if (!instanceId) {
return; return;
} }
const res: AxiosResponse = await graphql.query("queryInstance").params({ const res = await graphql.query("queryInstance").params({
instanceId, instanceId,
}); });
if (!res.data.errors) { if (!res.errors) {
if (isRelation) { if (isRelation) {
this.currentDestPod = res.data.data.instance || null; this.currentDestPod = res.data.instance || null;
this.destPods = [res.data.data.instance]; this.destPods = [res.data.instance];
return res.data; return res;
} }
this.currentPod = res.data.data.instance || null; this.currentPod = res.data.instance || null;
this.pods = [res.data.data.instance]; this.pods = [res.data.instance];
} }
return res.data; return res;
}, },
async getEndpoint(endpointId: string, isRelation?: string) { async getEndpoint(endpointId: string, isRelation?: string) {
if (!endpointId) { if (!endpointId) {
return; return;
} }
const res: AxiosResponse = await graphql.query("queryEndpoint").params({ const res = await graphql.query("queryEndpoint").params({
endpointId, endpointId,
}); });
if (res.data.errors) { if (res.errors) {
return res.data; return res;
} }
if (isRelation) { if (isRelation) {
this.currentDestPod = res.data.data.endpoint || null; this.currentDestPod = res.data.endpoint || null;
this.destPods = [res.data.data.endpoint]; this.destPods = [res.data.endpoint];
return res.data; return res;
} }
this.currentPod = res.data.data.endpoint || null; this.currentPod = res.data.endpoint || null;
this.pods = [res.data.data.endpoint]; this.pods = [res.data.endpoint];
return res.data; return res;
}, },
async getProcess(processId: string, isRelation?: boolean) { async getProcess(processId: string, isRelation?: boolean) {
if (!processId) { if (!processId) {
return; return;
} }
const res: AxiosResponse = await graphql.query("queryProcess").params({ const res = await graphql.query("queryProcess").params({
processId, processId,
}); });
if (!res.data.errors) { if (!res.errors) {
if (isRelation) { if (isRelation) {
this.currentDestProcess = res.data.data.process || null; this.currentDestProcess = res.data.process || null;
this.destProcesses = [res.data.data.process]; this.destProcesses = [res.data.process];
return res.data; return res.data;
} }
this.currentProcess = res.data.data.process || null; this.currentProcess = res.data.process || null;
this.processes = [res.data.data.process]; this.processes = [res.data.process];
} }
return res.data; return res;
}, },
}, },
}); });

View File

@ -18,7 +18,6 @@ import { defineStore } from "pinia";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import type { EBPFTaskList } from "@/types/ebpf"; import type { EBPFTaskList } from "@/types/ebpf";
import { useNetworkProfilingStore } from "@/store/modules/network-profiling"; import { useNetworkProfilingStore } from "@/store/modules/network-profiling";
@ -57,20 +56,18 @@ export const taskTimelineStore = defineStore({
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
this.loading = true; this.loading = true;
const res: AxiosResponse = await graphql.query("getEBPFTasks").params(params); const response = await graphql.query("getEBPFTasks").params(params);
this.loading = false; this.loading = false;
this.errorTip = ""; this.errorTip = "";
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
this.taskList = (res.data.data.queryEBPFTasks || []).filter( this.taskList = (response.data.queryEBPFTasks || []).filter(
(d: EBPFTaskList) => selectorStore.currentProcess && d.processId === selectorStore.currentProcess.id, (d: EBPFTaskList) => selectorStore.currentProcess && d.processId === selectorStore.currentProcess.id,
); );
// this.selectedTask = this.taskList[0] || {}; return response;
// await this.getGraphData();
return res.data;
}, },
async getGraphData() { async getGraphData() {
let res: any = {}; let res: any = {};

View File

@ -21,8 +21,7 @@ import graphql from "@/graphql";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import type { AxiosResponse } from "axios"; import fetchQuery from "@/graphql/fetch";
import query from "@/graphql/fetch";
import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor"; import { useQueryTopologyExpressionsProcessor } from "@/hooks/useExpressionsProcessor";
interface MetricVal { interface MetricVal {
@ -305,14 +304,14 @@ export const topologyStore = defineStore({
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const duration = useAppStoreWithOut().durationTime; const duration = useAppStoreWithOut().durationTime;
const res: AxiosResponse = await graphql.query("getServicesTopology").params({ const res = await graphql.query("getServicesTopology").params({
serviceIds, serviceIds,
duration, duration,
}); });
if (res.data.errors) { if (res.errors) {
return res.data; return res;
} }
return res.data.data.topology; return res.data.topology;
}, },
async getInstanceTopology() { async getInstanceTopology() {
const { currentService, currentDestService } = useSelectorStore(); const { currentService, currentDestService } = useSelectorStore();
@ -322,15 +321,15 @@ export const topologyStore = defineStore({
if (!(serverServiceId && clientServiceId)) { if (!(serverServiceId && clientServiceId)) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql.query("getInstanceTopology").params({ const res = await graphql.query("getInstanceTopology").params({
clientServiceId, clientServiceId,
serverServiceId, serverServiceId,
duration, duration,
}); });
if (!res.data.errors) { if (!res.errors) {
this.setInstanceTopology(res.data.data.topology); this.setInstanceTopology(res.data.topology);
} }
return res.data; return res;
}, },
async updateEndpointTopology(endpointIds: string[], depth: number) { async updateEndpointTopology(endpointIds: string[], depth: number) {
if (!endpointIds.length) { if (!endpointIds.length) {
@ -432,12 +431,12 @@ export const topologyStore = defineStore({
}); });
const queryStr = `query queryData(${variables}) {${fragment}}`; const queryStr = `query queryData(${variables}) {${fragment}}`;
const conditions = { duration }; const conditions = { duration };
const res: AxiosResponse = await query({ queryStr, conditions }); const res = await fetchQuery({ queryStr, conditions });
if (res.data.errors) { if (res.errors) {
return res.data; return res;
} }
const topo = res.data.data; const topo = res.data;
const calls = [] as Call[]; const calls = [] as Call[];
const nodes = [] as Node[]; const nodes = [] as Node[];
for (const key of Object.keys(topo)) { for (const key of Object.keys(topo)) {
@ -449,13 +448,13 @@ export const topologyStore = defineStore({
return { calls, nodes }; return { calls, nodes };
}, },
async getTopologyExpressionValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) { async getTopologyExpressionValue(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const res: AxiosResponse = await query(param); const res = await fetchQuery(param);
if (res.data.errors) { if (res.errors) {
return res.data; return res;
} }
return res.data; return res;
}, },
async getLinkExpressions(expressions: string[], type: string) { async getLinkExpressions(expressions: string[], type: string) {
if (!expressions.length) { if (!expressions.length) {
@ -503,22 +502,20 @@ export const topologyStore = defineStore({
if (!(id && layer)) { if (!(id && layer)) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql const res = await graphql.query("getHierarchyServiceTopology").params({ serviceId: id, layer: layer });
.query("getHierarchyServiceTopology") if (res.errors) {
.params({ serviceId: id, layer: layer }); return res;
if (res.data.errors) {
return res.data;
} }
const resp = await this.getListLayerLevels(); const resp = await this.getListLayerLevels();
if (resp.errors) { if (resp.errors) {
return resp; return resp;
} }
const levels = resp.data.levels || []; const levels = resp.levels || [];
this.setHierarchyServiceTopology(res.data.data.hierarchyServiceTopology || {}, levels); this.setHierarchyServiceTopology(res.data.hierarchyServiceTopology || {}, levels);
return res.data; return res;
}, },
async getListLayerLevels() { async getListLayerLevels() {
const res: AxiosResponse = await graphql.query("queryListLayerLevels").params({}); const res = await graphql.query("queryListLayerLevels").params({});
return res.data; return res.data;
}, },
@ -529,19 +526,19 @@ export const topologyStore = defineStore({
if (!(currentPod && dashboardStore.layerId)) { if (!(currentPod && dashboardStore.layerId)) {
return new Promise((resolve) => resolve({})); return new Promise((resolve) => resolve({}));
} }
const res: AxiosResponse = await graphql const res = await graphql
.query("getHierarchyInstanceTopology") .query("getHierarchyInstanceTopology")
.params({ instanceId: currentPod.id, layer: dashboardStore.layerId }); .params({ instanceId: currentPod.id, layer: dashboardStore.layerId });
if (res.data.errors) { if (res.errors) {
return res.data; return res;
} }
const resp = await this.getListLayerLevels(); const resp = await this.getListLayerLevels();
if (resp.errors) { if (resp.errors) {
return resp; return resp;
} }
const levels = resp.data.levels || []; const levels = resp.levels || [];
this.setHierarchyInstanceTopology(res.data.data.hierarchyInstanceTopology || {}, levels); this.setHierarchyInstanceTopology(res.data.hierarchyInstanceTopology || {}, levels);
return res.data; return res;
}, },
async queryHierarchyNodeExpressions(expressions: string[], layer: string) { async queryHierarchyNodeExpressions(expressions: string[], layer: string) {
const nodes = this.hierarchyServiceNodes.filter((n: HierarchyNode) => n.layer === layer); const nodes = this.hierarchyServiceNodes.filter((n: HierarchyNode) => n.layer === layer);

View File

@ -19,7 +19,6 @@ import type { Instance, Endpoint, Service } from "@/types/selector";
import type { Trace, Span } from "@/types/trace"; import type { Trace, Span } from "@/types/trace";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { QueryOrders } from "@/views/dashboard/data"; import { QueryOrders } from "@/views/dashboard/data";
@ -34,6 +33,7 @@ interface TraceState {
conditions: Recordable; conditions: Recordable;
traceSpanLogs: Recordable[]; traceSpanLogs: Recordable[];
selectorStore: Recordable; selectorStore: Recordable;
selectedSpan: Recordable<Span>;
} }
export const traceStore = defineStore({ export const traceStore = defineStore({
@ -45,6 +45,7 @@ export const traceStore = defineStore({
traceList: [], traceList: [],
traceSpans: [], traceSpans: [],
currentTrace: {}, currentTrace: {},
selectedSpan: {},
conditions: { conditions: {
queryDuration: useAppStoreWithOut().durationTime, queryDuration: useAppStoreWithOut().durationTime,
traceState: "ALL", traceState: "ALL",
@ -64,6 +65,9 @@ export const traceStore = defineStore({
setTraceSpans(spans: Span[]) { setTraceSpans(spans: Span[]) {
this.traceSpans = spans; this.traceSpans = spans;
}, },
setSelectedSpan(span: Span) {
this.selectedSpan = span;
},
resetState() { resetState() {
this.traceSpans = []; this.traceSpans = [];
this.traceList = []; this.traceList = [];
@ -76,125 +80,115 @@ export const traceStore = defineStore({
}; };
}, },
async getServices(layer: string) { async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({ const response = await graphql.query("queryServices").params({
layer, layer,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.services = res.data.data.services; this.services = response.data.services;
return res.data; return response;
}, },
async getService(serviceId: string) { async getService(serviceId: string) {
if (!serviceId) { if (!serviceId) {
return; return;
} }
const res: AxiosResponse = await graphql.query("queryService").params({ const response = await graphql.query("queryService").params({
serviceId, serviceId,
}); });
return res.data; return response;
}, },
async getInstance(instanceId: string) { async getInstance(instanceId: string) {
if (!instanceId) { if (!instanceId) {
return; return;
} }
const res: AxiosResponse = await graphql.query("queryInstance").params({ const response = await graphql.query("queryInstance").params({
instanceId, instanceId,
}); });
return res.data; return response;
}, },
async getEndpoint(endpointId: string) { async getEndpoint(endpointId: string) {
if (!endpointId) { if (!endpointId) {
return; return;
} }
const res: AxiosResponse = await graphql.query("queryEndpoint").params({ return await graphql.query("queryEndpoint").params({
endpointId, endpointId,
}); });
return res.data;
}, },
async getInstances(id: string) { async getInstances(id: string) {
const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id; const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id;
const res: AxiosResponse = await graphql.query("queryInstances").params({ const response = await graphql.query("queryInstances").params({
serviceId: serviceId, serviceId: serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.instances = [{ value: "0", label: "All" }, ...res.data.data.pods]; this.instances = [{ value: "0", label: "All" }, ...response.data.pods];
return res.data; return response;
}, },
async getEndpoints(id: string, keyword?: string) { async getEndpoints(id: string, keyword?: string) {
const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id; const serviceId = this.selectorStore.currentService ? this.selectorStore.currentService.id : id;
const res: AxiosResponse = await graphql.query("queryEndpoints").params({ const response = await graphql.query("queryEndpoints").params({
serviceId, serviceId,
duration: useAppStoreWithOut().durationTime, duration: useAppStoreWithOut().durationTime,
keyword: keyword || "", keyword: keyword || "",
limit: EndpointsTopNDefault, limit: EndpointsTopNDefault,
}); });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
this.endpoints = [{ value: "0", label: "All" }, ...res.data.data.pods]; this.endpoints = [{ value: "0", label: "All" }, ...response.data.pods];
return res.data; return response;
}, },
async getTraces() { async getTraces() {
const res: AxiosResponse = await graphql.query("queryTraces").params({ condition: this.conditions }); const response = await graphql.query("queryTraces").params({ condition: this.conditions });
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
if (!res.data.data.data.traces.length) { if (!response.data.data.traces.length) {
this.traceList = []; this.traceList = [];
this.setCurrentTrace({}); this.setCurrentTrace({});
this.setTraceSpans([]); this.setTraceSpans([]);
return res.data; return response;
} }
this.getTraceSpans({ traceId: res.data.data.data.traces[0].traceIds[0] }); this.getTraceSpans({ traceId: response.data.data.traces[0].traceIds[0] });
this.traceList = res.data.data.data.traces.map((d: Trace) => { this.traceList = response.data.data.traces.map((d: Trace) => {
d.traceIds = d.traceIds.map((id: string) => { d.traceIds = d.traceIds.map((id: string) => {
return { value: id, label: id }; return { value: id, label: id };
}); });
return d; return d;
}); });
this.setCurrentTrace(res.data.data.data.traces[0] || {}); this.setCurrentTrace(response.data.data.traces[0] || {});
return res.data; return response;
}, },
async getTraceSpans(params: { traceId: string }) { async getTraceSpans(params: { traceId: string }) {
const res: AxiosResponse = await graphql.query("queryTrace").params(params); const response = await graphql.query("queryTrace").params(params);
if (res.data.errors) { if (response.errors) {
return res.data; return response;
} }
const data = res.data.data.trace.spans; const data = response.data.trace.spans;
this.setTraceSpans(data || []); this.setTraceSpans(data || []);
return res.data; return response;
}, },
async getSpanLogs(params: Recordable) { async getSpanLogs(params: Recordable) {
const res: AxiosResponse = await graphql.query("queryServiceLogs").params(params); const response = await graphql.query("queryServiceLogs").params(params);
if (res.data.errors) { if (response.errors) {
this.traceSpanLogs = []; this.traceSpanLogs = [];
return res.data; return response;
} }
this.traceSpanLogs = res.data.data.queryLogs.logs || []; this.traceSpanLogs = response.data.queryLogs.logs || [];
return res.data; return response;
}, },
async getTagKeys() { async getTagKeys() {
const res: AxiosResponse = await graphql return await graphql.query("queryTraceTagKeys").params({ duration: useAppStoreWithOut().durationTime });
.query("queryTraceTagKeys")
.params({ duration: useAppStoreWithOut().durationTime });
return res.data;
}, },
async getTagValues(tagKey: string) { async getTagValues(tagKey: string) {
const res: AxiosResponse = await graphql return await graphql.query("queryTraceTagValues").params({ tagKey, duration: useAppStoreWithOut().durationTime });
.query("queryTraceTagValues")
.params({ tagKey, duration: useAppStoreWithOut().durationTime });
return res.data;
}, },
}, },
}); });

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

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

View File

@ -77,6 +77,7 @@ limitations under the License. -->
border-bottom: 1px solid $border-color-primary; border-bottom: 1px solid $border-color-primary;
width: 100%; width: 100%;
overflow: auto; overflow: auto;
min-height: 350px;
} }
.log-header { .log-header {

View File

@ -43,8 +43,7 @@ limitations under the License. -->
<Table <Table
:data="profileStore.segmentSpans" :data="profileStore.segmentSpans"
:traceId="profileStore.currentSegment.traceId" :traceId="profileStore.currentSegment.traceId"
:showBtnDetail="true" :headerType="WidgetType.Profile"
headerType="profile"
@select="selectSpan" @select="selectSpan"
/> />
</div> </div>
@ -53,13 +52,14 @@ limitations under the License. -->
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue"; import { ref } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import Table from "../../trace/components/Table/Index.vue"; import Table from "../../trace/components/Table.vue";
import { useProfileStore } from "@/store/modules/profile"; import { useProfileStore } from "@/store/modules/profile";
import Selector from "@/components/Selector.vue"; import Selector from "@/components/Selector.vue";
import type { Span } from "@/types/trace"; import type { Span } from "@/types/trace";
import type { Option } from "@/types/app"; import type { Option } from "@/types/app";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { ProfileDataMode, ProfileDisplayMode } from "./data"; import { ProfileDataMode, ProfileDisplayMode } from "./data";
import { WidgetType } from "@/views/dashboard/data";
/* global defineEmits*/ /* global defineEmits*/
const emits = defineEmits(["loading", "displayMode"]); const emits = defineEmits(["loading", "displayMode"]);

View File

@ -148,7 +148,7 @@ limitations under the License. -->
</div> </div>
<div class="label">Unhealthy Description</div> <div class="label">Unhealthy Description</div>
<el-input v-model="description.unhealthy" placeholder="Please input description" size="small" class="mt-5" /> <el-input v-model="description.unhealthy" placeholder="Please input description" size="small" class="mt-5" />
<el-button @click="setLegend" class="mt-20" size="small" type="primary"> <el-button @click="setLegend" class="mt-20 mb-20" size="small" type="primary">
{{ t("setLegend") }} {{ t("setLegend") }}
</el-button> </el-button>
</div> </div>
@ -208,11 +208,6 @@ limitations under the License. -->
getDashboardList(); getDashboardList();
async function getDashboardList() { async function getDashboardList() {
const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]"); const list = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
const json = await dashboardStore.fetchMetricList();
if (json.errors) {
ElMessage.error(json.errors);
return;
}
const entity = const entity =
dashboardStore.entity === EntityType[1].value dashboardStore.entity === EntityType[1].value
? EntityType[0].value ? EntityType[0].value

View File

@ -84,7 +84,7 @@ limitations under the License. -->
:data="traceStore.traceSpans" :data="traceStore.traceSpans"
:traceId="traceStore.currentTrace.traceIds[0].value" :traceId="traceStore.currentTrace.traceIds[0].value"
:showBtnDetail="false" :showBtnDetail="false"
HeaderType="trace" :headerType="WidgetType.Trace"
/> />
</div> </div>
</div> </div>
@ -160,6 +160,7 @@ limitations under the License. -->
appStore, appStore,
loading, loading,
traceId, traceId,
WidgetType,
}; };
}, },
}); });

View File

@ -14,8 +14,35 @@ limitations under the License. -->
<div class="trace-t-loading" v-show="loading"> <div class="trace-t-loading" v-show="loading">
<Icon iconName="spinner" size="sm" /> <Icon iconName="spinner" size="sm" />
</div> </div>
<div ref="traceGraph" class="d3-graph"></div> <TableContainer
<el-dialog v-model="showDetail" :destroy-on-close="true" fullscreen @closed="showDetail = false"> v-if="type === TraceGraphType.TABLE"
:tableData="segmentId"
:type="type"
:headerType="headerType"
:traceId="traceId"
@select="handleSelectSpan"
>
<div class="trace-tips" v-if="!segmentId.length">{{ $t("noData") }}</div>
</TableContainer>
<div v-else ref="traceGraph" class="d3-graph"></div>
<div id="trace-action-box">
<div @click="viewSpanDetails">Span details</div>
<div v-for="span in parentSpans" :key="span.segmentId" @click="viewParentSpan(span)">
{{ `Parent span: ${span.endpointName} -> Start time: ${visDate(span.startTime)}` }}
</div>
<div v-for="span in refParentSpans" :key="span.segmentId" @click="viewParentSpan(span)">
{{ `Ref to span: ${span.endpointName} -> Start time: ${visDate(span.startTime)}` }}
</div>
</div>
<el-dialog
v-model="showDetail"
width="60%"
center
align-center
:destroy-on-close="true"
@closed="showDetail = false"
v-if="currentSpan?.segmentId"
>
<SpanDetail :currentSpan="currentSpan" /> <SpanDetail :currentSpan="currentSpan" />
</el-dialog> </el-dialog>
</template> </template>
@ -23,70 +50,138 @@ limitations under the License. -->
import { ref, watch, onBeforeUnmount, onMounted } from "vue"; import { ref, watch, onBeforeUnmount, onMounted } from "vue";
import type { PropType } from "vue"; import type { PropType } from "vue";
import * as d3 from "d3"; import * as d3 from "d3";
import dayjs from "dayjs";
import ListGraph from "../../utils/d3-trace-list"; import ListGraph from "../../utils/d3-trace-list";
import TreeGraph from "../../utils/d3-trace-tree"; import TreeGraph from "../../utils/d3-trace-tree";
import type { Span, Ref } from "@/types/trace"; import type { Span, Ref } from "@/types/trace";
import SpanDetail from "./SpanDetail.vue"; import SpanDetail from "./SpanDetail.vue";
import TableContainer from "../Table/TableContainer.vue";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { debounce } from "@/utils/debounce"; import { debounce } from "@/utils/debounce";
import { mutationObserver } from "@/utils/mutation"; import { mutationObserver } from "@/utils/mutation";
import { TraceGraphType } from "../constant";
import { Themes } from "@/constants/data";
/* global defineProps, Nullable, defineExpose, Recordable */ /* global Recordable, Nullable */
const props = defineProps({ const props = defineProps({
data: { type: Array as PropType<Span[]>, default: () => [] }, data: { type: Array as PropType<Span[]>, default: () => [] },
traceId: { type: String, default: "" }, traceId: { type: String, default: "" },
type: { type: String, default: "List" }, type: { type: String, default: TraceGraphType.LIST },
headerType: { type: String, default: "" },
}); });
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const loading = ref<boolean>(false); const loading = ref<boolean>(false);
const showDetail = ref<boolean>(false); const showDetail = ref<boolean>(false);
const fixSpansSize = ref<number>(0); const fixSpansSize = ref<number>(0);
const segmentId = ref<Recordable[]>([]); const segmentId = ref<Recordable[]>([]);
const currentSpan = ref<Array<Span>>([]); const currentSpan = ref<Nullable<Span>>(null);
const refSpans = ref<Array<Ref>>([]); const refSpans = ref<Array<Ref>>([]);
const tree = ref<Nullable<any>>(null); const tree = ref<Nullable<any>>(null);
const traceGraph = ref<Nullable<HTMLDivElement>>(null); const traceGraph = ref<Nullable<HTMLDivElement>>(null);
const parentSpans = ref<Array<Span>>([]);
const refParentSpans = ref<Array<Span>>([]);
const debounceFunc = debounce(draw, 500); const debounceFunc = debounce(draw, 500);
const visDate = (date: number, pattern = "YYYY-MM-DD HH:mm:ss:SSS") => dayjs(date).format(pattern);
defineExpose({
tree,
});
onMounted(() => { onMounted(() => {
loading.value = true; loading.value = true;
changeTree(); changeTree();
if (!traceGraph.value) {
loading.value = false;
return;
}
draw(); draw();
loading.value = false; loading.value = false;
// monitor segment list width changes. // monitor segment list width changes.
mutationObserver.create("trigger-resize", () => { mutationObserver.create("trigger-resize", () => {
d3.selectAll(".d3-tip").remove(); d3.selectAll(".d3-tip").remove();
debounceFunc(); debounceFunc();
}); });
window.addEventListener("resize", debounceFunc); window.addEventListener("resize", debounceFunc);
}); });
function draw() { function draw() {
if (props.type === TraceGraphType.TABLE) {
segmentId.value = setLevel(segmentId.value);
return;
}
if (!traceGraph.value) { if (!traceGraph.value) {
return; return;
} }
d3.selectAll(".d3-tip").remove(); d3.selectAll(".d3-tip").remove();
if (props.type === "List") { if (props.type === TraceGraphType.LIST) {
tree.value = new ListGraph(traceGraph.value, handleSelectSpan); tree.value = new ListGraph(traceGraph.value, handleSelectSpan);
tree.value.init({ label: "TRACE_ROOT", children: segmentId.value }, props.data, fixSpansSize.value); tree.value.init(
{ label: "TRACE_ROOT", children: segmentId.value },
getRefsAllNodes({ label: "TRACE_ROOT", children: segmentId.value }),
fixSpansSize.value,
);
tree.value.draw(); tree.value.draw();
} else { return;
}
if (props.type === TraceGraphType.TREE) {
tree.value = new TreeGraph(traceGraph.value, handleSelectSpan); tree.value = new TreeGraph(traceGraph.value, handleSelectSpan);
tree.value.init({ label: `${props.traceId}`, children: segmentId.value }, props.data); tree.value.init(
{ label: `${props.traceId}`, children: segmentId.value },
getRefsAllNodes({ label: "TRACE_ROOT", children: segmentId.value }),
);
} }
} }
function handleSelectSpan(i: Recordable) { function handleSelectSpan(i: any) {
currentSpan.value = i.data; const spans = [];
const refSpans = [];
parentSpans.value = [];
refParentSpans.value = [];
if (props.type === TraceGraphType.TABLE) {
currentSpan.value = i;
} else {
currentSpan.value = i.data;
}
if (!currentSpan.value) {
return;
}
for (const ref of currentSpan.value.refs || []) {
refSpans.push(ref);
}
if (currentSpan.value.parentSpanId > -1) {
spans.push({
parentSegmentId: currentSpan.value.segmentId,
parentSpanId: currentSpan.value.parentSpanId,
traceId: currentSpan.value.traceId,
});
}
for (const span of refSpans) {
const item = props.data.find(
(d) => d.segmentId === span.parentSegmentId && d.spanId === span.parentSpanId && d.traceId === span.traceId,
);
item && refParentSpans.value.push(item);
}
for (const span of spans) {
const item = props.data.find(
(d) => d.segmentId === span.parentSegmentId && d.spanId === span.parentSpanId && d.traceId === span.traceId,
);
item && parentSpans.value.push(item);
}
}
function viewParentSpan(span: Recordable) {
if (props.type === TraceGraphType.TABLE) {
setTableSpanStyle(span);
return;
}
tree.value.highlightParents(span);
}
function viewSpanDetails() {
showDetail.value = true; showDetail.value = true;
hideActionBox();
}
function setTableSpanStyle(span: Recordable) {
const itemDom: any = document.querySelector(`.trace-item-${span.key}`);
const items: any = document.querySelectorAll(".trace-item");
for (const item of items) {
item.style.background = appStore.theme === Themes.Dark ? "#212224" : "#fff";
}
itemDom.style.background = appStore.theme === Themes.Dark ? "rgba(255, 255, 255, 0.1)" : "rgba(0, 0, 0, 0.1)";
hideActionBox();
}
function hideActionBox() {
const box: any = document.querySelector("#trace-action-box");
box.style.display = "none";
} }
function traverseTree(node: Recordable, spanId: string, segmentId: string, data: Recordable) { function traverseTree(node: Recordable, spanId: string, segmentId: string, data: Recordable) {
if (!node || node.isBroken) { if (!node || node.isBroken) {
@ -229,6 +324,7 @@ limitations under the License. -->
} }
for (const i of [...fixSpans, ...props.data]) { for (const i of [...fixSpans, ...props.data]) {
i.label = i.endpointName || "no operation name"; i.label = i.endpointName || "no operation name";
i.key = Math.random().toString(36).substring(2, 36);
i.children = []; i.children = [];
if (segmentGroup[i.segmentId]) { if (segmentGroup[i.segmentId]) {
segmentGroup[i.segmentId].push(i); segmentGroup[i.segmentId].push(i);
@ -272,21 +368,12 @@ limitations under the License. -->
} }
} }
for (const i in segmentGroup) { for (const i in segmentGroup) {
if (segmentGroup[i].refs.length) { for (const ref of segmentGroup[i].refs) {
let exit = null; if (!segmentGroup[ref.parentSegmentId]) {
for (const ref of segmentGroup[i].refs) {
const e = props.data.find(
(i: Recordable) =>
ref.traceId === i.traceId && ref.parentSegmentId === i.segmentId && ref.parentSpanId === i.spanId,
);
if (e) {
exit = e;
}
}
if (exit) {
segmentId.value.push(segmentGroup[i]); segmentId.value.push(segmentGroup[i]);
} }
} else { }
if (!segmentGroup[i].refs.length && segmentGroup[i].parentSpanId === -1) {
segmentId.value.push(segmentGroup[i]); segmentId.value.push(segmentGroup[i]);
} }
} }
@ -310,6 +397,34 @@ limitations under the License. -->
} }
} }
} }
function setLevel(arr: Recordable[], level = 1, totalExec?: number) {
for (const item of arr) {
item.level = level;
totalExec = totalExec || item.endTime - item.startTime;
item.totalExec = totalExec;
if (item.children && item.children.length > 0) {
setLevel(item.children, level + 1, totalExec);
}
}
return arr;
}
function getRefsAllNodes(tree: Recordable) {
let nodes = [];
let stack = [tree];
while (stack.length > 0) {
const node = stack.pop();
nodes.push(node);
if (node?.children && node.children.length > 0) {
for (let i = node.children.length - 1; i >= 0; i--) {
stack.push(node.children[i]);
}
}
}
return nodes;
}
function compare(p: string) { function compare(p: string) {
return (m: Recordable, n: Recordable) => { return (m: Recordable, n: Recordable) => {
const a = m[p]; const a = m[p];
@ -346,7 +461,7 @@ limitations under the License. -->
}, },
); );
</script> </script>
<style lang="scss" scoped> <style lang="scss">
.d3-graph { .d3-graph {
height: 100%; height: 100%;
} }
@ -356,36 +471,50 @@ limitations under the License. -->
fill-opacity: 0; fill-opacity: 0;
} }
.trace-node-container {
fill: rgb(0 0 0 / 0%);
stroke-width: 5px;
cursor: pointer;
&:hover {
fill: rgb(0 0 0 / 5%);
}
}
.trace-node .node-text { .trace-node .node-text {
font: 12.5px sans-serif; font: 12px sans-serif;
pointer-events: none; pointer-events: none;
} }
.domain { .trace-node.highlighted .node-text,
.trace-node.highlightedParent .node-text {
font-weight: bold;
fill: #409eff;
}
.highlightedParent .node,
.highlighted .node {
stroke-width: 4;
fill: var(--font-color);
stroke: var(--font-color);
}
.trace-node.highlighted .trace-node-text,
.trace-node.highlightedParent .trace-node-text {
font-weight: bold;
fill: #409eff;
}
#trace-action-box {
position: absolute;
color: $font-color;
cursor: pointer;
border: var(--sw-topology-border);
border-radius: 3px;
background-color: $theme-background;
padding: 10px 0;
display: none; display: none;
}
.time-charts-item { div {
display: inline-block; height: 30px;
padding: 2px 8px; line-height: 30px;
border: 1px solid; text-align: left;
font-size: 11px; padding: 0 15px;
border-radius: 4px; }
}
.dialog-c-text { div:hover {
white-space: pre; color: $active-color;
overflow: auto; background-color: $popper-hover-bg-color;
font-family: monospace; }
} }
</style> </style>

View File

@ -87,7 +87,14 @@ limitations under the License. -->
{{ t("relatedTraceLogs") }} {{ t("relatedTraceLogs") }}
</el-button> </el-button>
</div> </div>
<el-dialog v-model="showEventDetail" :destroy-on-close="true" fullscreen @closed="showEventDetail = false"> <el-dialog
v-model="showEventDetail"
width="60%"
center
align-center
:destroy-on-close="true"
@closed="showEventDetail = false"
>
<div> <div>
<div class="mb-10"> <div class="mb-10">
<span class="grey title">Name:</span> <span class="grey title">Name:</span>
@ -115,7 +122,14 @@ limitations under the License. -->
</div> </div>
</div> </div>
</el-dialog> </el-dialog>
<el-dialog v-model="showRelatedLogs" :destroy-on-close="true" fullscreen @closed="showRelatedLogs = false"> <el-dialog
v-model="showRelatedLogs"
width="60%"
center
align-center
:destroy-on-close="true"
@closed="showRelatedLogs = false"
>
<el-pagination <el-pagination
v-model="pageNum" v-model="pageNum"
:page-size="pageSize" :page-size="pageSize"
@ -295,4 +309,10 @@ limitations under the License. -->
.link { .link {
text-decoration: underline; text-decoration: underline;
} }
.log-tips {
width: 100%;
text-align: center;
margin: 50px 0;
}
</style> </style>

View File

@ -22,7 +22,7 @@ limitations under the License. -->
</el-button> </el-button>
</div> </div>
<div class="list"> <div class="list">
<Graph :data="data" :traceId="traceId" type="List" /> <Graph :data="data" :traceId="traceId" :type="TraceGraphType.LIST" />
</div> </div>
</div> </div>
</template> </template>
@ -31,8 +31,11 @@ limitations under the License. -->
import type { PropType } from "vue"; import type { PropType } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import * as d3 from "d3"; import * as d3 from "d3";
import { useAppStoreWithOut } from "@/store/modules/app";
import type { Span } from "@/types/trace"; import type { Span } from "@/types/trace";
import Graph from "./D3Graph/Index.vue"; import Graph from "./D3Graph/Index.vue";
import { Themes } from "@/constants/data";
import { TraceGraphType } from "./constant";
/* global defineProps, Recordable*/ /* global defineProps, Recordable*/
const props = defineProps({ const props = defineProps({
@ -40,6 +43,7 @@ limitations under the License. -->
traceId: { type: String, default: "" }, traceId: { type: String, default: "" },
}); });
const { t } = useI18n(); const { t } = useI18n();
const appStore = useAppStoreWithOut();
const list = computed(() => Array.from(new Set(props.data.map((i: Span) => i.serviceCode)))); const list = computed(() => Array.from(new Set(props.data.map((i: Span) => i.serviceCode))));
function computedScale(i: number) { function computedScale(i: number) {
@ -52,13 +56,13 @@ limitations under the License. -->
function downloadTrace() { function downloadTrace() {
const serializer = new XMLSerializer(); const serializer = new XMLSerializer();
const svgNode: any = d3.select(".trace-list-dowanload").node(); const svgNode: any = d3.select(".trace-list").node();
const source = `<?xml version="1.0" standalone="no"?>\r\n${serializer.serializeToString(svgNode)}`; const source = `<?xml version="1.0" standalone="no"?>\r\n${serializer.serializeToString(svgNode)}`;
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
const context: any = canvas.getContext("2d"); const context: any = canvas.getContext("2d");
canvas.width = (d3.select(".trace-list-dowanload") as Recordable)._groups[0][0].clientWidth; canvas.width = (d3.select(".trace-list") as Recordable)._groups[0][0].clientWidth;
canvas.height = (d3.select(".trace-list-dowanload") as Recordable)._groups[0][0].clientHeight; canvas.height = (d3.select(".trace-list") as Recordable)._groups[0][0].clientHeight;
context.fillStyle = "#fff"; context.fillStyle = appStore.theme === Themes.Dark ? "#212224" : `#fff`;
context.fillRect(0, 0, canvas.width, canvas.height); context.fillRect(0, 0, canvas.width, canvas.height);
const image = new Image(); const image = new Image();
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(source)}`; image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(source)}`;
@ -93,6 +97,7 @@ limitations under the License. -->
.list { .list {
height: calc(100% - 150px); height: calc(100% - 150px);
position: relative;
} }
.event-tag { .event-tag {

View File

@ -17,7 +17,7 @@ limitations under the License. -->
<div class="trace-t-loading" v-show="loading"> <div class="trace-t-loading" v-show="loading">
<Icon iconName="spinner" size="sm" /> <Icon iconName="spinner" size="sm" />
</div> </div>
<TableContainer :tableData="tableData" type="statistics" :HeaderType="HeaderType"> <TableContainer :tableData="tableData" :type="TraceGraphType.STATISTICS" :headerType="headerType">
<div class="trace-tips" v-if="!tableData.length">{{ $t("noData") }}</div> <div class="trace-tips" v-if="!tableData.length">{{ $t("noData") }}</div>
</TableContainer> </TableContainer>
</div> </div>
@ -28,13 +28,14 @@ limitations under the License. -->
import TableContainer from "./Table/TableContainer.vue"; import TableContainer from "./Table/TableContainer.vue";
import traceTable from "../utils/trace-table"; import traceTable from "../utils/trace-table";
import type { StatisticsSpan, Span, StatisticsGroupRef } from "@/types/trace"; import type { StatisticsSpan, Span, StatisticsGroupRef } from "@/types/trace";
import { TraceGraphType } from "./constant";
/* global defineProps, defineEmits, Recordable*/ /* global defineProps, defineEmits, Recordable*/
const props = defineProps({ const props = defineProps({
data: { type: Array as PropType<Span[]>, default: () => [] }, data: { type: Array as PropType<Span[]>, default: () => [] },
traceId: { type: String, default: "" }, traceId: { type: String, default: "" },
showBtnDetail: { type: Boolean, default: false }, showBtnDetail: { type: Boolean, default: false },
HeaderType: { type: String, default: "" }, headerType: { type: String, default: "" },
}); });
const emit = defineEmits(["load"]); const emit = defineEmits(["load"]);
const loading = ref<boolean>(true); const loading = ref<boolean>(true);

View File

@ -0,0 +1,38 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="trace-table-charts">
<Graph :data="data" :traceId="traceId" :type="TraceGraphType.TABLE" :headerType="headerType" />
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import type { Span } from "@/types/trace";
import Graph from "./D3Graph/Index.vue";
import { TraceGraphType } from "./constant";
defineProps({
data: { type: Array as PropType<Span[]>, default: () => [] },
traceId: { type: String, default: "" },
headerType: { type: String, default: "" },
});
</script>
<style lang="scss" scoped>
.trace-table-charts {
overflow: auto;
padding: 10px;
height: 100%;
width: 100%;
position: relative;
}
</style>

View File

@ -1,108 +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. -->
<template>
<div class="trace-table">
<div class="trace-t-loading" v-show="loading">
<Icon iconName="spinner" size="sm" />
</div>
<TableContainer
:tableData="tableData"
type="table"
:headerType="headerType"
:traceId="traceId"
@select="handleSelectSpan"
>
<div class="trace-tips" v-if="!tableData.length">{{ $t("noData") }}</div>
</TableContainer>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted } from "vue";
import type { PropType } from "vue";
import TableContainer from "./TableContainer.vue";
import traceTable from "../../utils/trace-table";
import type { Span } from "@/types/trace";
/* global defineProps, defineEmits, Recordable */
const props = defineProps({
data: { type: Array as PropType<Span[]>, default: () => [] },
traceId: { type: String, default: "" },
showBtnDetail: { type: Boolean, default: false },
headerType: { type: String, default: "" },
});
const emit = defineEmits(["select", "view", "load"]);
const loading = ref<boolean>(true);
const tableData = ref<Recordable[]>([]);
const showDetail = ref<boolean>(false);
const currentSpan = ref<Span | Recordable>({});
onMounted(() => {
tableData.value = formatData(traceTable.changeTree(props.data, props.traceId));
loading.value = false;
emit("load", () => {
loading.value = true;
});
});
function formatData(arr: Recordable[], level = 1, totalExec?: number) {
for (const item of arr) {
item.level = level;
totalExec = totalExec || item.endTime - item.startTime;
item.totalExec = totalExec;
if (item.children && item.children.length > 0) {
formatData(item.children, level + 1, totalExec);
}
}
return arr;
}
function handleSelectSpan(data: Span) {
currentSpan.value = data;
if (!props.showBtnDetail) {
showDetail.value = true;
}
emit("select", data);
}
watch(
() => props.data,
() => {
if (!props.data.length) {
tableData.value = [];
return;
}
tableData.value = formatData(traceTable.changeTree(props.data, props.traceId));
loading.value = false;
},
);
</script>
<style lang="scss" scoped>
.dialog-c-text {
white-space: pre;
overflow: auto;
font-family: monospace;
}
.trace-tips {
width: 100%;
text-align: center;
margin-top: 10px;
}
.trace-table {
height: 100%;
width: 100%;
}
</style>

View File

@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div class="trace"> <div class="trace-table">
<div class="trace-header" v-if="type === 'statistics'"> <div class="trace-table-header" v-if="type === TraceGraphType.STATISTICS">
<div :class="item.label" v-for="(item, index) in headerData" :key="index"> <div :class="item.label" v-for="(item, index) in headerData" :key="index">
{{ item.value }} {{ item.value }}
<span <span
@ -28,7 +28,7 @@ limitations under the License. -->
</span> </span>
</div> </div>
</div> </div>
<div class="trace-header" v-else> <div class="trace-table-header" v-else>
<div class="method" :style="`width: ${method}px`"> <div class="method" :style="`width: ${method}px`">
<span class="cp dragger" ref="dragger"> <span class="cp dragger" ref="dragger">
<Icon iconName="settings_ethernet" size="sm" /> <Icon iconName="settings_ethernet" size="sm" />
@ -44,10 +44,10 @@ limitations under the License. -->
:traceId="traceId" :traceId="traceId"
v-for="(item, index) in tableData" v-for="(item, index) in tableData"
:data="item" :data="item"
:key="'key' + index" :key="`key${index}`"
:type="type" :type="type"
:headerType="headerType" :headerType="headerType"
@select="selectItem" @click="selectItem"
/> />
<slot></slot> <slot></slot>
</div> </div>
@ -55,9 +55,11 @@ limitations under the License. -->
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import type { PropType } from "vue"; import type { PropType } from "vue";
import type { Span } from "@/types/trace"; import { useTraceStore } from "@/store/modules/trace";
import TableItem from "./TableItem.vue"; import TableItem from "./TableItem.vue";
import { ProfileConstant, TraceConstant, StatisticsConstant } from "./data"; import { ProfileConstant, TraceConstant, StatisticsConstant } from "./data";
import { TraceGraphType } from "../constant";
import { WidgetType } from "@/views/dashboard/data";
/* global defineProps, Nullable, defineEmits, Recordable*/ /* global defineProps, Nullable, defineEmits, Recordable*/
const props = defineProps({ const props = defineProps({
@ -67,20 +69,21 @@ limitations under the License. -->
traceId: { type: String, default: "" }, traceId: { type: String, default: "" },
}); });
const emits = defineEmits(["select"]); const emits = defineEmits(["select"]);
const traceStore = useTraceStore();
const method = ref<number>(300); const method = ref<number>(300);
const componentKey = ref<number>(300); const componentKey = ref<number>(300);
const flag = ref<boolean>(true); const flag = ref<boolean>(true);
const dragger = ref<Nullable<HTMLSpanElement>>(null); const dragger = ref<Nullable<HTMLSpanElement>>(null);
let headerData: Recordable[] = TraceConstant; let headerData: Recordable[] = TraceConstant;
if (props.headerType === "profile") { if (props.headerType === WidgetType.Profile) {
headerData = ProfileConstant; headerData = ProfileConstant;
} }
if (props.type === "statistics") { if (props.type === TraceGraphType.STATISTICS) {
headerData = StatisticsConstant; headerData = StatisticsConstant;
} }
onMounted(() => { onMounted(() => {
if (props.type === "statistics") { if (props.type === TraceGraphType.STATISTICS) {
return; return;
} }
const drag: Nullable<HTMLSpanElement> = dragger.value; const drag: Nullable<HTMLSpanElement> = dragger.value;
@ -101,8 +104,24 @@ limitations under the License. -->
}; };
}; };
}); });
function selectItem(span: Span) { function selectItem(event: MouseEvent) {
emits("select", span); emits("select", traceStore.selectedSpan);
if (props.headerType === WidgetType.Profile) {
return;
}
if (props.type === TraceGraphType.STATISTICS) {
return;
}
const item: any = document.querySelector("#trace-action-box");
const tableBox = document.querySelector(".trace-table-charts")?.getBoundingClientRect();
if (!tableBox) {
return;
}
const offsetX = event.x - tableBox.x;
const offsetY = event.y - tableBox.y;
item.style.display = "block";
item.style.top = `${offsetY + 20}px`;
item.style.left = `${offsetX + 10}px`;
} }
function sortStatistics(key: string) { function sortStatistics(key: string) {
const element = props.tableData; const element = props.tableData;
@ -152,7 +171,7 @@ limitations under the License. -->
<style lang="scss" scoped> <style lang="scss" scoped>
@import url("./table.scss"); @import url("./table.scss");
.trace { .trace-table {
font-size: $font-size-smaller; font-size: $font-size-smaller;
height: 100%; height: 100%;
overflow: auto; overflow: auto;
@ -163,7 +182,7 @@ limitations under the License. -->
float: right; float: right;
} }
.trace-header { .trace-table-header {
white-space: nowrap; white-space: nowrap;
user-select: none; user-select: none;
border-left: 0; border-left: 0;
@ -171,7 +190,7 @@ limitations under the License. -->
border-bottom: 1px solid var(--sw-trace-list-border); border-bottom: 1px solid var(--sw-trace-list-border);
} }
.trace-header div { .trace-table-header div {
display: inline-block; display: inline-block;
background-color: var(--sw-table-header); background-color: var(--sw-table-header);
padding: 0 4px; padding: 0 4px;

View File

@ -14,17 +14,17 @@ See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div v-if="type === 'statistics'"> <div v-if="type === TraceGraphType.STATISTICS">
<div class="trace-item"> <div class="trace-item">
<div :class="['method']"> <div :class="['method']">
<el-tooltip :content="data.groupRef.endpointName" placement="bottom" :show-after="300"> <el-tooltip :content="data.groupRef.endpointName" placement="top" :show-after="300">
<span> <span>
{{ data.groupRef.endpointName }} {{ data.groupRef.endpointName }}
</span> </span>
</el-tooltip> </el-tooltip>
</div> </div>
<div :class="['type']"> <div :class="['type']">
<el-tooltip :content="data.groupRef.type" placement="bottom" :show-after="300"> <el-tooltip :content="data.groupRef.type" placement="top" :show-after="300">
<span> <span>
{{ data.groupRef.type }} {{ data.groupRef.type }}
</span> </span>
@ -55,6 +55,7 @@ limitations under the License. -->
'level' + (data.level - 1), 'level' + (data.level - 1),
{ 'trace-item-error': data.isError }, { 'trace-item-error': data.isError },
{ profiled: data.profiled === false }, { profiled: data.profiled === false },
`trace-item-${data.key}`,
]" ]"
:data-text="data.profiled === false ? 'No Thread Dump' : ''" :data-text="data.profiled === false ? 'No Thread Dump' : ''"
> >
@ -75,7 +76,7 @@ limitations under the License. -->
/> />
<el-tooltip <el-tooltip
:content="data.type === 'Entry' ? 'Entry' : 'Exit'" :content="data.type === 'Entry' ? 'Entry' : 'Exit'"
placement="bottom" placement="top"
:show-after="300" :show-after="300"
v-if="['Entry', 'Exit'].includes(data.type)" v-if="['Entry', 'Exit'].includes(data.type)"
> >
@ -83,12 +84,12 @@ limitations under the License. -->
<Icon :iconName="data.type === 'Entry' ? 'entry' : 'exit'" size="sm" class="mr-5" /> <Icon :iconName="data.type === 'Entry' ? 'entry' : 'exit'" size="sm" class="mr-5" />
</span> </span>
</el-tooltip> </el-tooltip>
<el-tooltip v-if="isCrossThread" content="CROSS_THREAD" placement="bottom" :show-after="300"> <el-tooltip v-if="isCrossThread" content="CROSS_THREAD" placement="top" :show-after="300">
<span> <span>
<Icon iconName="cross" size="sm" class="mr-5" /> <Icon iconName="cross" size="sm" class="mr-5" />
</span> </span>
</el-tooltip> </el-tooltip>
<el-tooltip :content="data.endpointName" placement="bottom" :show-after="300"> <el-tooltip :content="data.endpointName" placement="top" :show-after="300">
<span> <span>
{{ data.endpointName }} {{ data.endpointName }}
</span> </span>
@ -109,19 +110,19 @@ limitations under the License. -->
{{ data.dur ? data.dur + "" : "0" }} {{ data.dur ? data.dur + "" : "0" }}
</div> </div>
<div class="api"> <div class="api">
<el-tooltip :show-after="300" :content="data.component || '-'" placement="bottom"> <el-tooltip :show-after="300" :content="data.component || '-'" placement="top">
<span>{{ data.component || "-" }}</span> <span>{{ data.component || "-" }}</span>
</el-tooltip> </el-tooltip>
</div> </div>
<div class="application"> <div class="application">
<el-tooltip :show-after="300" :content="data.serviceCode || '-'" placement="bottom"> <el-tooltip :show-after="300" :content="data.serviceCode || '-'" placement="top">
<span>{{ data.serviceCode }}</span> <span>{{ data.serviceCode }}</span>
</el-tooltip> </el-tooltip>
</div> </div>
<div class="application" v-show="headerType === 'profile'" @click="viewSpan($event)"> <div class="application" v-show="headerType === WidgetType.Profile" @click="viewSpan($event)">
<span>{{ t("view") }}</span> <span>{{ t("view") }}</span>
</div> </div>
<div class="application" v-show="headerType !== 'profile'"> <div class="application" v-show="headerType !== WidgetType.Profile">
<span>{{ data.attachedEvents && data.attachedEvents.length }}</span> <span>{{ data.attachedEvents && data.attachedEvents.length }}</span>
</div> </div>
</div> </div>
@ -133,7 +134,6 @@ limitations under the License. -->
:data="child" :data="child"
:type="type" :type="type"
:headerType="headerType" :headerType="headerType"
@select="selectedItem(child)"
/> />
</div> </div>
<el-dialog v-model="showDetail" :destroy-on-close="true" fullscreen @closed="showDetail = false"> <el-dialog v-model="showDetail" :destroy-on-close="true" fullscreen @closed="showDetail = false">
@ -148,7 +148,10 @@ limitations under the License. -->
import SpanDetail from "../D3Graph/SpanDetail.vue"; import SpanDetail from "../D3Graph/SpanDetail.vue";
import { dateFormat } from "@/utils/dateFormat"; import { dateFormat } from "@/utils/dateFormat";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { useTraceStore } from "@/store/modules/trace";
import { Themes } from "@/constants/data"; import { Themes } from "@/constants/data";
import { TraceGraphType } from "../constant";
import { WidgetType } from "@/views/dashboard/data";
/*global Recordable*/ /*global Recordable*/
const props = { const props = {
@ -161,10 +164,10 @@ limitations under the License. -->
export default defineComponent({ export default defineComponent({
name: "TableItem", name: "TableItem",
props, props,
emits: ["select"],
components: { SpanDetail }, components: { SpanDetail },
setup(props, { emit }) { setup(props) {
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const traceStore = useTraceStore();
const displayChildren = ref<boolean>(true); const displayChildren = ref<boolean>(true);
const showDetail = ref<boolean>(false); const showDetail = ref<boolean>(false);
const { t } = useI18n(); const { t } = useI18n();
@ -193,7 +196,6 @@ limitations under the License. -->
const key = props.data.refs.findIndex((d: { type: string }) => d.type === "CROSS_THREAD"); const key = props.data.refs.findIndex((d: { type: string }) => d.type === "CROSS_THREAD");
return key > -1 ? true : false; return key > -1 ? true : false;
}); });
function toggle() { function toggle() {
displayChildren.value = !displayChildren.value; displayChildren.value = !displayChildren.value;
} }
@ -213,26 +215,27 @@ limitations under the License. -->
} }
function selectSpan(event: Recordable) { function selectSpan(event: Recordable) {
const dom = event.composedPath().find((d: Recordable) => d.className.includes("trace-item")); const dom = event.composedPath().find((d: Recordable) => d.className.includes("trace-item"));
selectedItem(props.data);
emit("select", props.data); if (props.headerType === WidgetType.Profile) {
if (props.headerType === "profile") {
showSelectSpan(dom); showSelectSpan(dom);
return; return;
} }
viewSpanDetail(dom); viewSpanDetail(dom);
} }
function viewSpan(event: Recordable) { function viewSpan(event: Recordable) {
showDetail.value = true;
const dom = event.composedPath().find((d: Recordable) => d.className.includes("trace-item")); const dom = event.composedPath().find((d: Recordable) => d.className.includes("trace-item"));
emit("select", props.data); selectedItem(props.data);
viewSpanDetail(dom); viewSpanDetail(dom);
} }
function selectedItem(span: Recordable) {
function selectedItem(data: HTMLSpanElement) { traceStore.setSelectedSpan(span);
emit("select", data);
} }
function viewSpanDetail(dom: HTMLSpanElement) { function viewSpanDetail(dom: HTMLSpanElement) {
showSelectSpan(dom); showSelectSpan(dom);
showDetail.value = true; if (props.type === TraceGraphType.STATISTICS) {
showDetail.value = true;
}
} }
watch( watch(
() => appStore.theme, () => appStore.theme,
@ -262,6 +265,8 @@ limitations under the License. -->
viewSpan, viewSpan,
t, t,
appStore, appStore,
TraceGraphType,
WidgetType,
}; };
}, },
}); });
@ -279,8 +284,6 @@ limitations under the License. -->
} }
.trace-item.level0 { .trace-item.level0 {
color: #448dfe;
&:hover { &:hover {
background: rgb(0 0 0 / 4%); background: rgb(0 0 0 / 4%);
} }

View File

@ -35,7 +35,7 @@ limitations under the License. -->
</a> </a>
</div> </div>
<div class="trace-tree"> <div class="trace-tree">
<Graph ref="charts" :data="data" :traceId="traceId" type="Tree" /> <Graph ref="charts" :data="data" :traceId="traceId" :type="TraceGraphType.TREE" />
</div> </div>
</div> </div>
</template> </template>
@ -46,6 +46,7 @@ limitations under the License. -->
import type { Span } from "@/types/trace"; import type { Span } from "@/types/trace";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { ref, computed } from "vue"; import { ref, computed } from "vue";
import { TraceGraphType } from "./constant";
/* global defineProps */ /* global defineProps */
const props = defineProps({ const props = defineProps({

View File

@ -15,11 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
import axios from "axios"; export enum TraceGraphType {
const CancelToken = axios.CancelToken; TREE = "Tree",
LIST = "List",
export const cancelToken = (): any => TABLE = "Table",
new CancelToken(function executor(c) { STATISTICS = "Statistics",
const w = window as any; }
(w.axiosCancel || []).push(c);
});

View File

@ -17,7 +17,7 @@
import List from "./List.vue"; import List from "./List.vue";
import Tree from "./Tree.vue"; import Tree from "./Tree.vue";
import Table from "./Table/Index.vue"; import Table from "./Table.vue";
import Statistics from "./Statistics.vue"; import Statistics from "./Statistics.vue";
export default { export default {

View File

@ -42,16 +42,17 @@ export default class ListGraph {
private xAxis: any = null; private xAxis: any = null;
private sequentialScale: any = null; private sequentialScale: any = null;
private root: any = null; private root: any = null;
private selectedNode: any = null;
constructor(el: HTMLDivElement, handleSelectSpan: (i: Trace) => void) { constructor(el: HTMLDivElement, handleSelectSpan: (i: Trace) => void) {
this.handleSelectSpan = handleSelectSpan; this.handleSelectSpan = handleSelectSpan;
this.el = el; this.el = el;
this.width = el.getBoundingClientRect().width - 10; this.width = el.getBoundingClientRect().width - 10;
this.height = el.getBoundingClientRect().height - 10; this.height = el.getBoundingClientRect().height - 10;
d3.select(".trace-list-dowanload").remove(); d3.select(`.${this.el?.className} .trace-list`).remove();
this.svg = d3 this.svg = d3
.select(this.el) .select(this.el)
.append("svg") .append("svg")
.attr("class", "trace-list-dowanload") .attr("class", "trace-list")
.attr("width", this.width > 0 ? this.width : 10) .attr("width", this.width > 0 ? this.width : 10)
.attr("height", this.height > 0 ? this.height : 10) .attr("height", this.height > 0 ? this.height : 10)
.attr("transform", `translate(-5, 0)`); .attr("transform", `translate(-5, 0)`);
@ -85,7 +86,8 @@ export default class ListGraph {
L${d.target.y} ${d.target.x - 5}`; L${d.target.y} ${d.target.x - 5}`;
} }
init(data: Recordable, row: Recordable[], fixSpansSize: number) { init(data: Recordable, row: Recordable[], fixSpansSize: number) {
d3.select(".trace-xaxis").remove(); d3.select(`.${this.el?.className} .trace-xaxis`).remove();
d3.select("#trace-action-box").style("display", "none");
this.row = row; this.row = row;
this.data = data; this.data = data;
this.min = d3.min(this.row.map((i) => i.startTime)); this.min = d3.min(this.row.map((i) => i.startTime));
@ -113,6 +115,14 @@ export default class ListGraph {
this.root = d3.hierarchy(this.data, (d) => d.children); this.root = d3.hierarchy(this.data, (d) => d.children);
this.root.x0 = 0; this.root.x0 = 0;
this.root.y0 = 0; this.root.y0 = 0;
const t = this;
d3.select("svg.trace-list").on("click", function (event: MouseEvent) {
if (event.target === this) {
d3.select("#trace-action-box").style("display", "none");
t.selectedNode && t.selectedNode.classed("highlighted", false);
t.clearParentHighlight();
}
});
} }
draw(callback: Function) { draw(callback: Function) {
this.update(this.root, callback); this.update(this.root, callback);
@ -142,19 +152,35 @@ export default class ListGraph {
.enter() .enter()
.append("g") .append("g")
.attr("transform", `translate(${source.y0},${source.x0})`) .attr("transform", `translate(${source.y0},${source.x0})`)
.attr("id", (d: Recordable) => `list-node-${d.id}`)
.attr("class", "trace-node") .attr("class", "trace-node")
.attr("style", "cursor: pointer") .attr("style", "cursor: pointer")
.style("opacity", 0)
.on("mouseover", function (event: MouseEvent, d: Trace) { .on("mouseover", function (event: MouseEvent, d: Trace) {
t.tip.show(d, this); t.tip.show(d, this);
}) })
.on("mouseout", function (event: MouseEvent, d: Trace) { .on("mouseout", function (event: MouseEvent, d: Trace) {
t.tip.hide(d, this); t.tip.hide(d, this);
}) })
.on("click", (event: MouseEvent, d: Trace) => { .on("click", function (event: MouseEvent, d: Trace & { id: string }) {
if (this.handleSelectSpan) { event.stopPropagation();
this.handleSelectSpan(d); t.tip.hide(d, this);
d3.select(this).classed("highlighted", true);
const nodeBox = this.getBoundingClientRect();
const svgBox = (d3.select(`.${t.el?.className} .trace-list`) as any).node().getBoundingClientRect();
const offsetX = nodeBox.x - svgBox.x;
const offsetY = nodeBox.y - svgBox.y;
d3.select("#trace-action-box")
.style("display", "block")
.style("left", `${offsetX + 30}px`)
.style("top", `${offsetY + 40}px`);
t.selectedNode = d3.select(this);
if (t.handleSelectSpan) {
t.handleSelectSpan(d);
} }
t.root.descendants().map((node: { id: number }) => {
d3.select(`#list-node-${node.id}`).classed("highlightedParent", false);
return node;
});
}); });
nodeEnter nodeEnter
.append("rect") .append("rect")
@ -239,14 +265,14 @@ export default class ListGraph {
.attr("cx", (d: Recordable) => { .attr("cx", (d: Recordable) => {
const events = d.data.attachedEvents; const events = d.data.attachedEvents;
if (events && events.length > 9) { if (events && events.length > 9) {
return 272; return 273;
} else { } else {
return 270; return 270;
} }
}) })
.attr("cy", -5) .attr("cy", -5)
.attr("fill", "none") .attr("fill", "none")
.attr("stroke", appStore.theme === Themes.Dark ? "#666" : "#e66") .attr("stroke", "#e66")
.style("opacity", (d: Recordable) => { .style("opacity", (d: Recordable) => {
const events = d.data.attachedEvents; const events = d.data.attachedEvents;
if (events && events.length) { if (events && events.length) {
@ -259,7 +285,7 @@ export default class ListGraph {
.append("text") .append("text")
.attr("x", 267) .attr("x", 267)
.attr("y", -1) .attr("y", -1)
.attr("fill", appStore.theme === Themes.Dark ? "#666" : "#e66") .attr("fill", "#e66")
.style("font-size", "10px") .style("font-size", "10px")
.text((d: Recordable) => { .text((d: Recordable) => {
const events = d.data.attachedEvents; const events = d.data.attachedEvents;
@ -324,16 +350,6 @@ export default class ListGraph {
if (d.data.children.length === 0) return; if (d.data.children.length === 0) return;
this.click(d, this); this.click(d, this);
}); });
nodeUpdate
.transition()
.duration(400)
.attr("transform", (d: Recordable) => `translate(${d.y + 3},${d.x})`)
.style("opacity", 1)
.select("circle")
.style("fill", (d: Recordable) =>
d._children ? `${this.sequentialScale(this.list.indexOf(d.data.serviceCode))}` : "#eee",
);
// Transition exiting nodes to the parent's new position. // Transition exiting nodes to the parent's new position.
node node
.exit() .exit()
@ -381,6 +397,39 @@ export default class ListGraph {
callback(); callback();
} }
} }
clearParentHighlight() {
return this.root.descendants().map((node: { id: number }) => {
d3.select(`#list-node-${node.id}`).classed("highlightedParent", false);
return node;
});
}
highlightParents(span: Recordable) {
if (!span) {
return;
}
const nodes = this.clearParentHighlight();
const parentSpan = nodes.find(
(node: Recordable) =>
span.spanId === node.data.spanId &&
span.segmentId === node.data.segmentId &&
span.traceId === node.data.traceId,
);
if (!parentSpan) return;
d3.select(`#list-node-${parentSpan.id}`).classed("highlightedParent", true);
d3.select("#trace-action-box").style("display", "none");
this.selectedNode.classed("highlighted", false);
const container = document.querySelector(".trace-chart .charts");
const containerRect = container?.getBoundingClientRect();
if (!containerRect) return;
const targetElement = document.querySelector(`#list-node-${parentSpan.id}`);
if (!targetElement) return;
const targetRect = targetElement.getBoundingClientRect();
container?.scrollTo({
left: targetRect.left - containerRect.left + container?.scrollLeft,
top: targetRect.top - containerRect.top + container?.scrollTop - 100,
behavior: "smooth",
});
}
visDate(date: number, pattern = "YYYY-MM-DD HH:mm:ss:SSS") { visDate(date: number, pattern = "YYYY-MM-DD HH:mm:ss:SSS") {
return dayjs(date).format(pattern); return dayjs(date).format(pattern);
} }

View File

@ -46,6 +46,7 @@ export default class TraceMap {
private topChildMax: number[] = []; private topChildMax: number[] = [];
private topChildMin: number[] = []; private topChildMin: number[] = [];
private nodeUpdate: Nullable<any> = null; private nodeUpdate: Nullable<any> = null;
private selectedNode: any = null;
constructor(el: HTMLDivElement, handleSelectSpan: (i: Trace) => void) { constructor(el: HTMLDivElement, handleSelectSpan: (i: Trace) => void) {
this.el = el; this.el = el;
@ -55,7 +56,7 @@ export default class TraceMap {
this.topChild = []; this.topChild = [];
this.width = el.clientWidth - 20; this.width = el.clientWidth - 20;
this.height = el.clientHeight - 30; this.height = el.clientHeight - 30;
d3.select(".d3-trace-tree").remove(); d3.select(`.${this.el?.className} .d3-trace-tree`).remove();
this.body = d3 this.body = d3
.select(this.el) .select(this.el)
.append("svg") .append("svg")
@ -80,6 +81,7 @@ export default class TraceMap {
this.svg.call(this.tip); this.svg.call(this.tip);
} }
init(data: Recordable, row: Recordable) { init(data: Recordable, row: Recordable) {
d3.select("#trace-action-box").style("display", "none");
this.treemap = d3.tree().size([row.length * 35, this.width]); this.treemap = d3.tree().size([row.length * 35, this.width]);
this.row = row; this.row = row;
this.data = data; this.data = data;
@ -124,24 +126,33 @@ export default class TraceMap {
this.update(this.root); this.update(this.root);
} }
update(source: Recordable) { update(source: Recordable) {
const t = this;
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const that: any = this; const that: any = this;
const treeData = this.treemap(this.root); const treeData = this.treemap(this.root);
const nodes = treeData.descendants(), const nodes = treeData.descendants();
links = treeData.descendants().slice(1); const links = treeData.descendants().slice(1);
nodes.forEach(function (d: Recordable) { nodes.forEach(function (d: Recordable) {
d.y = d.depth * 140; d.y = d.depth * 140;
}); });
const node = this.svg.selectAll("g.node").data(nodes, (d: Recordable) => { const node = this.svg.selectAll("g.trace-node").data(nodes, (d: Recordable) => {
return d.id || (d.id = ++this.i); return d.id || (d.id = ++this.i);
}); });
d3.select("svg.d3-trace-tree").on("click", function (event: MouseEvent) {
if (event.target === this) {
d3.select("#trace-action-box").style("display", "none");
t.selectedNode && t.selectedNode.classed("highlighted", false);
t.clearParentHighlight();
}
});
const nodeEnter = node const nodeEnter = node
.enter() .enter()
.append("g") .append("g")
.attr("class", "node") .attr("class", "trace-node")
.attr("id", (d: Recordable) => `trace-node-${d.id}`)
.attr("cursor", "pointer") .attr("cursor", "pointer")
.attr("transform", function () { .attr("transform", function () {
return "translate(" + source.y0 + "," + source.x0 + ")"; return "translate(" + source.y0 + "," + source.x0 + ")";
@ -165,9 +176,6 @@ export default class TraceMap {
if (_node.length) { if (_node.length) {
that.timeTip.hide(d, _node[0].children[1]); that.timeTip.hide(d, _node[0].children[1]);
} }
})
.on("click", function (event: MouseEvent, d: Recordable) {
that.handleSelectSpan(d);
}); });
nodeEnter nodeEnter
.append("circle") .append("circle")
@ -208,15 +216,15 @@ export default class TraceMap {
nodeEnter nodeEnter
.append("circle") .append("circle")
.attr("class", "node") .attr("class", "node")
.attr("r", 1e-6) .attr("r", 2)
.style("fill", (d: Recordable) =>
d._children ? this.sequentialScale(this.list.indexOf(d.data.serviceCode)) : "#fff",
)
.attr("stroke", (d: Recordable) => this.sequentialScale(this.list.indexOf(d.data.serviceCode))) .attr("stroke", (d: Recordable) => this.sequentialScale(this.list.indexOf(d.data.serviceCode)))
.attr("stroke-width", 2.5); .attr("stroke-width", 2.5)
.attr("fill", (d: Recordable) =>
d.data.children.length ? this.sequentialScale(this.list.indexOf(d.data.serviceCode)) : "#fff",
);
nodeEnter nodeEnter
.append("text") .append("text")
.attr("class", "trace-node-text")
.attr("font-size", 11) .attr("font-size", 11)
.attr("dy", "-0.5em") .attr("dy", "-0.5em")
.attr("x", function (d: Recordable) { .attr("x", function (d: Recordable) {
@ -230,24 +238,27 @@ export default class TraceMap {
? (d.data.isError ? "◉ " : "") + d.data.label.slice(0, 10) + "..." ? (d.data.isError ? "◉ " : "") + d.data.label.slice(0, 10) + "..."
: (d.data.isError ? "◉ " : "") + d.data.label, : (d.data.isError ? "◉ " : "") + d.data.label,
) )
.style("fill", (d: Recordable) => .attr("fill", (d: Recordable) =>
!d.data.isError ? (appStore.theme === Themes.Dark ? "#eee" : "#3d444f") : "#E54C17", !d.data.isError ? (appStore.theme === Themes.Dark ? "#eee" : "#3d444f") : "#E54C17",
); );
nodeEnter nodeEnter
.append("text") .append("text")
.attr("class", "node-text") .attr("class", "node-text")
.attr("x", function (d: Recordable) { .attr("x", function (d: Recordable) {
return d.children || d._children ? -45 : 15; return d.children || d._children ? -30 : 15;
}) })
.attr("dy", "1em") .attr("dy", "1.5em")
.attr("fill", appStore.theme === Themes.Dark ? "#888" : "#bbb") .attr("fill", appStore.theme === Themes.Dark ? "#888" : "#bbb")
.attr("text-anchor", function (d: Recordable) { .attr("text-anchor", function (d: Recordable) {
return d.children || d._children ? "end" : "start"; return d.children || d._children ? "end" : "start";
}) })
.style("font-size", "10px") .style("font-size", "10px")
.text( .text((d: Recordable) => {
(d: Recordable) => `${d.data.layer || ""}${d.data.component ? "-" + d.data.component : d.data.component || ""}`, const label = d.data.component
); ? " - " + (d.data.component.length > 10 ? d.data.label.slice(0, 10) + "..." : d.data.component)
: "";
return `${d.data.layer || ""}${label}`;
});
nodeEnter nodeEnter
.append("rect") .append("rect")
.attr("rx", 1) .attr("rx", 1)
@ -290,12 +301,37 @@ export default class TraceMap {
nodeUpdate nodeUpdate
.select("circle.node") .select("circle.node")
.attr("r", 5) .attr("r", 5)
.style("fill", (d: Recordable) =>
d._children ? this.sequentialScale(this.list.indexOf(d.data.serviceCode)) : "#fff",
)
.attr("cursor", "pointer") .attr("cursor", "pointer")
.on("click", (event: any, d: Recordable) => { .on("click", function (event: MouseEvent, d: Trace & { id: string }) {
event.stopPropagation(); event.stopPropagation();
t.tip.hide(d, this);
d3.select(this.parentNode).classed("highlighted", true);
const nodeBox = this.getBoundingClientRect();
const svgBox = (d3.select(`.${t.el?.className} .d3-trace-tree`) as any).node().getBoundingClientRect();
const offsetX = nodeBox.x - svgBox.x;
const offsetY = nodeBox.y - svgBox.y;
d3.select("#trace-action-box")
.style("display", "block")
.style("left", `${offsetX + 30}px`)
.style("top", `${offsetY + 40}px`);
t.selectedNode = d3.select(this.parentNode);
if (t.handleSelectSpan) {
t.handleSelectSpan(d);
}
t.root.descendants().map((node: { id: number }) => {
d3.select(`#trace-node-${node.id}`).classed("highlightedParent", false);
return node;
});
})
.on("dblclick", function (event: MouseEvent, d: Recordable) {
event.stopPropagation();
t.tip.hide(d, this);
if (d.data.children.length === 0) return;
click(d);
})
.on("contextmenu", function (event: MouseEvent, d: Recordable) {
event.stopPropagation();
t.tip.hide(d, this);
if (d.data.children.length === 0) return; if (d.data.children.length === 0) return;
click(d); click(d);
}); });
@ -369,6 +405,39 @@ export default class TraceMap {
that.update(d); that.update(d);
} }
} }
clearParentHighlight() {
return this.root.descendants().map((node: { id: number }) => {
d3.select(`#trace-node-${node.id}`).classed("highlightedParent", false);
return node;
});
}
highlightParents(span: Recordable) {
if (!span) {
return;
}
const nodes = this.clearParentHighlight();
const parentSpan = nodes.find(
(node: Recordable) =>
span.spanId === node.data.spanId &&
span.segmentId === node.data.segmentId &&
span.traceId === node.data.traceId,
);
if (!parentSpan) return;
d3.select(`#trace-node-${parentSpan.id}`).classed("highlightedParent", true);
d3.select("#trace-action-box").style("display", "none");
this.selectedNode.classed("highlighted", false);
const container = document.querySelector(".trace-chart .charts");
const containerRect = container?.getBoundingClientRect();
if (!containerRect) return;
const targetElement = document.querySelector(`#trace-node-${parentSpan.id}`);
if (!targetElement) return;
const targetRect = targetElement.getBoundingClientRect();
container?.scrollTo({
left: targetRect.left - containerRect.left + container?.scrollLeft,
top: targetRect.top - containerRect.top + container?.scrollTop - 100,
behavior: "smooth",
});
}
setDefault() { setDefault() {
d3.selectAll(".time-inner").style("opacity", 1); d3.selectAll(".time-inner").style("opacity", 1);
d3.selectAll(".time-inner-duration").style("opacity", 0); d3.selectAll(".time-inner-duration").style("opacity", 0);

View File

@ -15,42 +15,13 @@
* limitations under the License. * limitations under the License.
*/ */
import type { Ref, Span, StatisticsSpan, StatisticsGroupRef, TraceTreeRef } from "@/types/trace"; import type { Span, TraceTreeRef } from "@/types/trace";
export default class TraceUtil { export default class TraceUtil {
public static buildTraceDataList(data: Span[]): string[] { public static buildTraceDataList(data: Span[]): string[] {
return Array.from(new Set(data.map((span: Span) => span.serviceCode))); return Array.from(new Set(data.map((span: Span) => span.serviceCode)));
} }
public static changeTree(data: Span[], currentTraceId: string) {
const segmentIdList: Span[] = [];
const traceTreeRef: Recordable = this.changeTreeCore(data);
traceTreeRef.segmentIdGroup.forEach((segmentId: string) => {
if (traceTreeRef.segmentMap.get(segmentId).refs) {
traceTreeRef.segmentMap.get(segmentId).refs.forEach((ref: Ref) => {
if (ref.traceId === currentTraceId) {
this.traverseTree(
traceTreeRef.segmentMap.get(ref.parentSegmentId) as Span,
ref.parentSpanId,
ref.parentSegmentId,
traceTreeRef.segmentMap.get(segmentId) as Span,
);
}
});
}
});
// set a breakpoint at this line
traceTreeRef.segmentMap.forEach((value: Span) => {
if ((value.refs && value.refs.length === 0) || !value.refs) {
segmentIdList.push(value as Span);
}
});
segmentIdList.forEach((segmentId: Span) => {
this.collapse(segmentId);
});
return segmentIdList;
}
public static changeStatisticsTree(data: Span[]): Map<string, Span[]> { public static changeStatisticsTree(data: Span[]): Map<string, Span[]> {
const result = new Map<string, Span[]>(); const result = new Map<string, Span[]>();
const traceTreeRef = this.changeTreeCore(data); const traceTreeRef = this.changeTreeCore(data);
@ -255,47 +226,6 @@ export default class TraceUtil {
} }
} }
private static traverseTree(node: Span, spanId: number, segmentId: string, childNode: Span) {
if (!node || node.isBroken) {
return;
}
if (node.spanId === spanId && node.segmentId === segmentId) {
node.children!.push(childNode);
return;
}
if (node.children && node.children.length > 0) {
for (const grandchild of node.children) {
this.traverseTree(grandchild, spanId, segmentId, childNode);
}
}
}
private static getSpanGroupData(groupspans: Span[], groupRef: StatisticsGroupRef): StatisticsSpan {
let maxTime = 0;
let minTime = 0;
let sumTime = 0;
const count = groupspans.length;
groupspans.forEach((groupspan: Span) => {
const duration = groupspan.dur || 0;
if (duration > maxTime) {
maxTime = duration;
}
if (duration < minTime) {
minTime = duration;
}
sumTime = sumTime + duration;
});
const avgTime = count === 0 ? 0 : sumTime / count;
return {
groupRef,
maxTime,
minTime,
sumTime,
avgTime,
count,
};
}
private static calculationChildren(nodes: Span[], result: Map<string, Span[]>): void { private static calculationChildren(nodes: Span[], result: Map<string, Span[]>): void {
nodes.forEach((node: Span) => { nodes.forEach((node: Span) => {
const groupRef = node.endpointName + ":" + node.type; const groupRef = node.endpointName + ":" + node.type;

1
src/vite-env.d.ts vendored
View File

@ -26,6 +26,5 @@ declare global {
interface Window { interface Window {
Promise: any; Promise: any;
moment: any; moment: any;
axiosCancel: any;
} }
} }