Compare commits

...

16 Commits

Author SHA1 Message Date
Fine0830
35a1ff9b24
feat: enhance the TTL Tab on Setting page (#473) 2025-06-03 21:23:48 +08:00
Forgottener
e4a43d91e2
feat: enhance the async-profiling duration options (#472) 2025-06-02 17:18:58 +08:00
Fine0830
1f651cf528
Fix: Add the prefix for http url (#471) 2025-05-30 15:37:27 +08:00
Fine0830
7dcc67f455
feat: implement the Status API on Settings page (#470) 2025-05-30 12:53:22 +08:00
Fine0830
a28972bc5c
feat: support cold stage data for metrics, trace and log (#469) 2025-05-21 09:19:06 +08:00
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
90 changed files with 2694 additions and 1353 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"
},
"dependencies": {
"axios": "^1.8.2",
"d3": "^7.3.0",
"d3-flame-graph": "^4.1.3",
"d3-tip": "^0.9.1",
@ -72,7 +71,7 @@
"typescript": "^5.7.3",
"unplugin-auto-import": "^0.18.2",
"unplugin-vue-components": "^0.27.3",
"vite": "^6.1.0",
"vite": "^6.3.4",
"vite-plugin-monaco-editor": "^1.1.0",
"vite-plugin-svg-icons": "^2.0.1",
"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

View File

@ -169,12 +169,13 @@ limitations under the License. -->
value: { type: Date },
left: { type: Boolean, default: false },
right: { type: Boolean, default: false },
dates: { type: Array as PropType<number[] | string[]>, default: () => [] },
dates: { type: Array as PropType<Date[]>, default: () => [] },
disabledDate: { type: Function, default: () => false },
format: {
type: String,
default: "YYYY-MM-DD",
},
maxRange: { type: Array as PropType<Date[]>, default: () => [] },
});
const state = reactive({
pre: "",
@ -241,6 +242,12 @@ limitations under the License. -->
const end = computed(() => {
return parse(Number(props.dates[1]));
});
const minStart = computed(() => {
return parse(Number(props.maxRange[0]));
});
const maxEnd = computed(() => {
return parse(Number(props.maxRange[1]));
});
const ys = computed(() => {
return Math.floor(state.year / 10) * 10;
});
@ -369,7 +376,10 @@ limitations under the License. -->
flag = tf(props.value, format) === tf(time, format);
}
classObj[`${state.pre}-date`] = true;
classObj[`${state.pre}-date-disabled`] = (props.right && t < start.value) || props.disabledDate(time, format);
const rightDisabled = props.right && (t < start.value || t > maxEnd.value || !props.maxRange?.length);
const leftDisabled =
props.left && (t < minStart.value || t > end.value || !props.maxRange?.length || t > maxEnd.value);
classObj[`${state.pre}-date-disabled`] = rightDisabled || leftDisabled || props.disabledDate(time, format);
classObj[`${state.pre}-date-on`] = (props.left && t > start.value) || (props.right && t < end.value);
classObj[`${state.pre}-date-selected`] = flag;
return classObj;

View File

@ -68,6 +68,7 @@ limitations under the License. -->
:left="true"
:disabledDate="disabledDate"
:format="format"
:maxRange="maxRange"
@ok="ok"
@setDates="setDates"
/>
@ -78,6 +79,7 @@ limitations under the License. -->
:right="true"
:disabledDate="disabledDate"
:format="format"
:maxRange="maxRange"
@ok="ok"
@setDates="setDates"
/>
@ -112,11 +114,11 @@ limitations under the License. -->
import { useI18n } from "vue-i18n";
import DateCalendar from "./DateCalendar.vue";
import { useTimeoutFn } from "@/hooks/useTimeout";
/*global defineProps, defineEmits*/
/*global PropType, defineProps, defineEmits*/
const datepicker = ref(null);
const { t } = useI18n();
const show = ref<boolean>(false);
const dates = ref<Date | string[] | any>([]);
const dates = ref<Date[]>([]);
const props = defineProps({
position: { type: String, default: "bottom" },
name: [String],
@ -149,7 +151,7 @@ limitations under the License. -->
type: Boolean,
default: false,
},
dateRangeSelect: [Function],
maxRange: { type: Array as PropType<Date[]>, default: () => [] },
});
const emit = defineEmits(["clear", "input", "confirm", "cancel"]);
const local = computed(() => {

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;
}
}
export const BasePath = `/graphql`;
export async function httpQuery({
url = "",
method = "GET",
json,
headers = {},
}: {
method: string;
json: unknown;
headers?: Recordable;
url: string;
}) {
const timeoutId = setTimeout(() => {
abortRequestsAndUpdate();
}, Timeout);
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
* limitations under the License.
*/
import type { AxiosResponse } from "axios";
import axios from "axios";
import { cancelToken } from "@/utils/cancelToken";
import { httpQuery, BasePath } from "./base";
async function query(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const res: AxiosResponse = await axios.post(
"/graphql",
{ query: param.queryStr, variables: { ...param.conditions } },
{ cancelToken: cancelToken() },
);
if (res.data.errors) {
res.data.errors = res.data.errors.map((e: { message: string }) => e.message).join(" ");
async function customQuery(param: { queryStr: string; conditions: { [key: string]: unknown } }) {
const response = await httpQuery({
url: BasePath,
method: "post",
json: { query: param.queryStr, variables: { ...param.conditions } },
});
if (response.errors) {
response.errors = response.errors.map((e: { message: string }) => e.message).join(" ");
}
return res;
return response;
}
export default query;
export default customQuery;

View File

@ -49,3 +49,28 @@ export const MenuItems = {
}
`,
};
export const RecordsTTL = {
query: `getRecordsTTL {
normal
trace
zipkinTrace
log
browserErrorLog
coldNormal
coldTrace
coldZipkinTrace
coldLog
coldBrowserErrorLog
}`,
};
export const MetricsTTL = {
query: `getMetricsTTL {
minute
hour
day
coldMinute
coldHour
coldDay
}`,
};

View File

@ -14,22 +14,6 @@
* See the License for the specific language governing permissions and
* 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 = {
query: `

View File

@ -103,3 +103,63 @@ export const TraceTagValues = {
query: `
tagValues: queryTraceTagAutocompleteValues(tagKey: $tagKey, duration: $duration)`,
};
export const TraceSpansFromColdStage = {
variable: "$traceId: ID!, $duration: Duration!, $debug: Boolean",
query: `
trace: queryTrace(traceId: $traceId, duration: $duration, debug: $debug) {
spans {
traceId
segmentId
spanId
parentSpanId
refs {
traceId
parentSegmentId
parentSpanId
type
}
serviceCode
serviceInstanceName
startTime
endTime
endpointName
type
peer
component
isError
layer
tags {
key
value
}
logs {
time
data {
key
value
}
}
attachedEvents {
startTime {
seconds
nanos
}
event
endTime {
seconds
nanos
}
tags {
key
value
}
summary {
key
value
}
}
}
}
`,
};

30
src/graphql/http/index.ts Normal file
View File

@ -0,0 +1,30 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { httpQuery } from "../base";
import { HttpURL } from "./url";
export default async function fetchQuery({ method, json, path }: { method: string; json?: unknown; path: string }) {
const response = await httpQuery({
method,
json,
url: (HttpURL as { [key: string]: string })[path],
});
if (response.errors) {
response.errors = response.errors.map((e: { message: string }) => e.message).join(" ");
}
return response;
}

22
src/graphql/http/url.ts Normal file
View File

@ -0,0 +1,22 @@
/**
* 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.
*/
const PREFIX = process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test" ? "/api" : "";
export const HttpURL = {
ClusterNodes: `${PREFIX}/status/cluster/nodes`,
ConfigTTL: `${PREFIX}/status/config/ttl`,
DebuggingConfigDump: `${PREFIX}/debugging/config/dump`,
};

View File

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

View File

@ -14,10 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { OAPTimeInfo, OAPVersion, MenuItems } from "../fragments/app";
import { OAPTimeInfo, OAPVersion, MenuItems, MetricsTTL, RecordsTTL } from "../fragments/app";
export const queryOAPTimeInfo = `query queryOAPTimeInfo {${OAPTimeInfo.query}}`;
export const queryOAPVersion = `query ${OAPVersion.query}`;
export const queryMenuItems = `query menuItems {${MenuItems.query}}`;
export const queryMetricsTTL = `query MetricsTTL {${MetricsTTL.query}}`;
export const queryRecordsTTL = `query RecordsTTL {${RecordsTTL.query}}`;

View File

@ -14,18 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
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}}`;
import { getAllTemplates, addTemplate, changeTemplate, deleteTemplate } from "../fragments/dashboard";
export const addNewTemplate = `mutation template(${addTemplate.variable}) {${addTemplate.query}}`;

View File

@ -15,12 +15,14 @@
* limitations under the License.
*/
import { Traces, TraceSpans, TraceTagKeys, TraceTagValues } from "../fragments/trace";
import { Traces, TraceSpans, TraceTagKeys, TraceTagValues, TraceSpansFromColdStage } from "../fragments/trace";
export const queryTraces = `query queryTraces(${Traces.variable}) {${Traces.query}}`;
export const queryTrace = `query queryTrace(${TraceSpans.variable}) {${TraceSpans.query}}`;
export const querySpans = `query querySpans(${TraceSpans.variable}) {${TraceSpans.query}}`;
export const queryTraceTagKeys = `query queryTraceTagKeys(${TraceTagKeys.variable}) {${TraceTagKeys.query}}`;
export const queryTraceTagValues = `query queryTraceTagValues(${TraceTagValues.variable}) {${TraceTagValues.query}}`;
export const queryTraceSpansFromColdStage = `query queryTraceSpansFromColdStage(${TraceSpansFromColdStage.variable}) {${TraceSpansFromColdStage.query}}`;

55
src/hooks/useDuration.ts Normal file
View File

@ -0,0 +1,55 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useAppStoreWithOut, InitializationDurationRow } from "@/store/modules/app";
import type { Duration, DurationTime } from "@/types/app";
import getLocalTime from "@/utils/localtime";
import dateFormatStep from "@/utils/dateFormat";
export function useDuration() {
let durationRow: Duration = InitializationDurationRow;
function getDuration() {
const appStore = useAppStoreWithOut();
return {
start: getLocalTime(appStore.utc, durationRow.start),
end: getLocalTime(appStore.utc, durationRow.end),
step: durationRow.step,
};
}
function getDurationTime(): DurationTime {
const { start, step, end } = getDuration();
return {
start: dateFormatStep(start, step, true),
end: dateFormatStep(end, step, true),
step: step,
};
}
function setDurationRow(data: Duration) {
durationRow = data;
}
function getMaxRange(day: number) {
if (day === -1) {
return [];
}
const gap = (day + 1) * 24 * 60 * 60 * 1000;
const dates: Date[] = [new Date(new Date().getTime() - gap), new Date()];
return dates;
}
return { setDurationRow, getDurationTime, getMaxRange };
}

View File

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

View File

@ -40,14 +40,25 @@ limitations under the License. -->
</el-breadcrumb>
<div class="title" v-else>{{ pageTitle }}</div>
<div class="app-config">
<span class="red" v-show="timeRange">{{ t("timeTips") }}</span>
<span class="red" v-show="showTimeRangeTips">{{ t("timeTips") }}</span>
<TimePicker
:value="[appStore.durationRow.start, appStore.durationRow.end]"
:maxRange="appStore.maxRange"
position="bottom"
format="YYYY-MM-DD HH:mm"
@input="changeTimeRange"
/>
<span> UTC{{ appStore.utcHour >= 0 ? "+" : "" }}{{ `${appStore.utcHour}:${appStore.utcMin}` }} </span>
<span class="ml-5">
<el-switch
v-model="coldStage"
inline-prompt
active-text="Active Data"
inactive-text="Cold Data"
@change="changeDataMode"
width="90px"
/>
</span>
<span class="ml-5" ref="themeSwitchRef">
<el-switch
v-model="theme"
@ -75,7 +86,7 @@ limitations under the License. -->
<script lang="ts" setup>
import { Themes } from "@/constants/data";
import router from "@/router";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useAppStoreWithOut, InitializationDurationRow } from "@/store/modules/app";
import { useDashboardStore } from "@/store/modules/dashboard";
import type { DashboardItem } from "@/types/dashboard";
import timeFormat from "@/utils/timeFormat";
@ -92,10 +103,11 @@ limitations under the License. -->
const dashboardStore = useDashboardStore();
const route = useRoute();
const pathNames = ref<{ path?: string; name: string; selected: boolean }[][]>([]);
const timeRange = ref<number>(0);
const showTimeRangeTips = ref<boolean>(false);
const pageTitle = ref<string>("");
const theme = ref<boolean>(true);
const themeSwitchRef = ref<HTMLElement>();
const coldStage = ref<boolean>(false);
const savedTheme = window.localStorage.getItem("theme-is-dark");
if (savedTheme === "false") {
@ -110,6 +122,7 @@ limitations under the License. -->
resetDuration();
getVersion();
getNavPaths();
setTTL();
function changeTheme() {
const root = document.documentElement;
@ -126,6 +139,24 @@ limitations under the License. -->
window.localStorage.setItem("theme-is-dark", String(theme.value));
}
function changeDataMode() {
appStore.setColdStageMode(coldStage.value);
if (coldStage.value) {
handleMetricsTTL({
minute: appStore.metricsTTL.coldMinute,
hour: appStore.metricsTTL.coldHour,
day: appStore.metricsTTL.coldDay,
});
} else {
handleMetricsTTL({
minute: appStore.metricsTTL.minute,
hour: appStore.metricsTTL.hour,
day: appStore.metricsTTL.day,
});
}
appStore.setDuration(InitializationDurationRow);
}
function handleChangeTheme() {
const x = themeSwitchRef.value?.offsetLeft ?? 0;
const y = themeSwitchRef.value?.offsetTop ?? 0;
@ -184,13 +215,49 @@ limitations under the License. -->
}
function changeTimeRange(val: Date[]) {
timeRange.value = val[1].getTime() - val[0].getTime() > 60 * 24 * 60 * 60 * 1000 ? 1 : 0;
if (timeRange.value) {
showTimeRangeTips.value = val[1].getTime() - val[0].getTime() > 60 * 24 * 60 * 60 * 1000;
if (showTimeRangeTips.value) {
return;
}
appStore.setDuration(timeFormat(val));
}
async function setTTL() {
await getMetricsTTL();
await getRecordsTTL();
changeDataMode();
}
async function getRecordsTTL() {
const resp = await appStore.queryRecordsTTL();
if (resp.errors) {
ElMessage.error(resp.errors);
}
}
async function getMetricsTTL() {
const resp = await appStore.queryMetricsTTL();
if (resp.errors) {
ElMessage.error(resp.errors);
}
}
function handleMetricsTTL({ minute, hour, day }: { minute: number; hour: number; day: number }) {
if (minute === -1 || hour === -1 || day === -1) {
return appStore.setMaxRange([]);
}
if (!day) {
return appStore.setMaxRange([]);
}
const gap = Math.max(day, hour, minute);
const dates: Date[] = [new Date(new Date().getTime() - dayToMS(gap + 1)), new Date()];
appStore.setMaxRange(dates);
}
function dayToMS(day: number) {
return day * 24 * 60 * 60 * 1000;
}
function getNavPaths() {
pathNames.value = [];
pageTitle.value = "";

View File

@ -397,5 +397,14 @@ const msg = {
instances: "Instances",
snapshot: "Snapshot",
expression: "Expression",
metricsTTL: "Metrics TTL (day)",
recordsTTL: "Records TTL (day)",
clusterNodes: "Cluster Nodes",
debuggingConfigDump: "Dump Effective Configurations",
customDuration: "Custom Duration",
maxDuration: "Max Duration",
minutes: "Minutes",
invalidProfilingDurationRange: "Please enter a valid duration between 1 and 900 seconds",
taskCreatedSuccessfully: "Task created successfully",
};
export default msg;

View File

@ -397,5 +397,14 @@ const msg = {
snapshot: "Snapshot",
expression: "Expression",
asSelector: "As Selector",
metricsTTL: "Metrics TTL (day)",
recordsTTL: "Records TTL (day)",
clusterNodes: "Cluster Nodes",
debuggingConfigDump: "Dump Effective Configurations",
customDuration: "Duración Personalizada",
maxDuration: "Duración Máxima",
minutes: "Minutos",
invalidProfilingDurationRange: "Por favor ingrese una duración válida entre 1 y 900 segundos",
taskCreatedSuccessfully: "Tarea creada exitosamente",
};
export default msg;

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_service: "Cilium Service",
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;

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_service: "Cilium Service",
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;

View File

@ -119,6 +119,11 @@ const titles = {
cilium_desc: "Cilium是Kubernetes上的CNI插件提供基于eBPF的网络、安全和负载均衡。",
cilium_service: "Cilium服务",
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;

View File

@ -395,5 +395,14 @@ const msg = {
instances: "实例",
snapshot: "快照",
expression: "表达式",
metricsTTL: "Metrics TTL (day)",
recordsTTL: "Records TTL (day)",
clusterNodes: "集群节点",
debuggingConfigDump: "转储有效配置",
customDuration: "自定义时长",
maxDuration: "最大时长",
minutes: "分钟",
invalidProfilingDurationRange: "请输入1到900秒之间的有效时长",
taskCreatedSuccessfully: "任务创建成功",
};
export default msg;

View File

@ -37,16 +37,8 @@ const router = createRouter({
routes,
});
(window as any).axiosCancel = [];
router.beforeEach((to, from, next) => {
router.beforeEach((to, _, next) => {
// 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 === "/") {
let defaultPath = "";

View File

@ -15,6 +15,7 @@
* limitations under the License.
*/
import { WidgetType } from "@/views/dashboard/data";
import { HotAndWarmOpt } from "@/views/settings/data";
export const NewControl = {
x: 0,
@ -58,3 +59,18 @@ export enum EBPFProfilingTriggerType {
}
export const EndpointsTopNDefault = 20;
export const TTLTypes = {
HotAndWarm: "Hot / Warm",
Cold: "Cold",
};
export const TTLColdMap: Indexable<string> = {
coldDay: HotAndWarmOpt[0],
coldHour: HotAndWarmOpt[1],
coldMinute: HotAndWarmOpt[2],
coldNormal: HotAndWarmOpt[3],
coldTrace: HotAndWarmOpt[4],
coldZipkinTrace: HotAndWarmOpt[5],
coldLog: HotAndWarmOpt[6],
coldBrowserErrorLog: HotAndWarmOpt[7],
};

View File

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

View File

@ -19,14 +19,13 @@ import { store } from "@/store";
import graphql from "@/graphql";
import type { Duration, DurationTime } from "@/types/app";
import getLocalTime from "@/utils/localtime";
import type { AxiosResponse } from "axios";
import dateFormatStep, { dateFormatTime } from "@/utils/dateFormat";
import { TimeType } from "@/constants/data";
import type { MenuOptions, SubItem } from "@/types/app";
import type { MenuOptions, SubItem, MetricsTTL, RecordsTTL } from "@/types/app";
import { Themes } from "@/constants/data";
/*global Nullable*/
interface AppState {
durationRow: Recordable;
durationRow: Duration;
utc: string;
utcHour: number;
utcMin: number;
@ -38,16 +37,22 @@ interface AppState {
reloadTimer: Nullable<IntervalHandle>;
allMenus: MenuOptions[];
theme: string;
coldStageMode: boolean;
maxRange: Date[];
metricsTTL: Recordable<MetricsTTL>;
recordsTTL: Recordable<RecordsTTL>;
}
export const InitializationDurationRow = {
start: new Date(new Date().getTime() - 1800000),
end: new Date(),
step: TimeType.MINUTE_TIME,
};
export const appStore = defineStore({
id: "app",
state: (): AppState => ({
durationRow: {
start: new Date(new Date().getTime() - 1800000),
end: new Date(),
step: TimeType.MINUTE_TIME,
},
durationRow: InitializationDurationRow,
utc: "",
utcHour: 0,
utcMin: 0,
@ -59,6 +64,10 @@ export const appStore = defineStore({
reloadTimer: null,
allMenus: [],
theme: Themes.Dark,
coldStageMode: false,
maxRange: [],
metricsTTL: {},
recordsTTL: {},
}),
getters: {
duration(): Duration {
@ -118,17 +127,14 @@ export const appStore = defineStore({
actions: {
setDuration(data: Duration): void {
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();
},
updateDurationRow(data: Duration) {
this.durationRow = data;
},
setMaxRange(times: Date[]) {
this.maxRange = times;
},
setTheme(data: string) {
this.theme = data;
},
@ -150,6 +156,9 @@ export const appStore = defineStore({
setAutoRefresh(auto: boolean) {
this.autoRefresh = auto;
},
setColdStageMode(mode: boolean) {
this.coldStageMode = mode;
},
runEventStack() {
if (this.timer) {
clearTimeout(this.timer);
@ -185,11 +194,11 @@ export const appStore = defineStore({
});
},
async queryOAPTimeInfo() {
const res: AxiosResponse = await graphql.query("queryOAPTimeInfo").params({});
if (res.data.errors) {
const res = await graphql.query("queryOAPTimeInfo").params({});
if (res.errors) {
this.utc = -(new Date().getTimezoneOffset() / 60) + ":0";
} else {
this.utc = res.data.data.getTimeInfo.timezone / 100 + ":0";
this.utc = res.data.getTimeInfo.timezone / 100 + ":0";
}
const utcArr = this.utc.split(":");
this.utcHour = isNaN(Number(utcArr[0])) ? 0 : Number(utcArr[0]);
@ -197,21 +206,37 @@ export const appStore = defineStore({
return res.data;
},
async fetchVersion(): Promise<void> {
const res: AxiosResponse = await graphql.query("queryOAPVersion").params({});
if (res.data.errors) {
return res.data;
async fetchVersion() {
const res = await graphql.query("queryOAPVersion").params({});
if (res.errors) {
return res;
}
this.version = res.data.data.version;
this.version = res.data.version;
return res.data;
},
async queryMenuItems() {
const res: AxiosResponse = await graphql.query("queryMenuItems").params({});
if (res.data.errors) {
return res.data;
const res = await graphql.query("queryMenuItems").params({});
if (res.errors) {
return res;
}
return res.data.data;
return res.data;
},
async queryMetricsTTL() {
const response = await graphql.query("queryMetricsTTL").params({});
if (response.errors) {
return response;
}
this.metricsTTL = response.data.getMetricsTTL;
return response.data;
},
async queryRecordsTTL() {
const res = await graphql.query("queryRecordsTTL").params({});
if (res.errors) {
return res;
}
this.recordsTTL = res.data.getRecordsTTL;
return res.data;
},
setReloadTimer(timer: IntervalHandle) {
this.reloadTimer = timer;

View File

@ -25,7 +25,6 @@ import { store } from "@/store";
import graphql from "@/graphql";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import type { AxiosResponse } from "axios";
import type { Instance } from "@/types/selector";
interface AsyncProfilingState {
@ -59,79 +58,77 @@ export const asyncProfilingStore = defineStore({
async getTaskList() {
const selectorStore = useSelectorStore();
this.loadingTasks = true;
const res: AxiosResponse = await graphql.query("getAsyncTaskList").params({
const response = await graphql.query("getAsyncTaskList").params({
request: {
serviceId: selectorStore.currentService.id,
limit: 10000,
},
});
this.loadingTasks = false;
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.taskList = res.data.data.asyncTaskList.tasks || [];
this.taskList = response.data.asyncTaskList.tasks || [];
this.selectedTask = this.taskList[0] || {};
this.setAnalyzeTrees([]);
this.setSelectedTask(this.selectedTask);
if (!this.taskList.length) {
return res.data;
return response;
}
return res.data;
return response;
},
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) {
return res.data;
if (response.errors) {
return response;
}
this.taskProgress = res.data.data.taskProgress;
return res.data;
this.taskProgress = response.data.taskProgress;
return response;
},
async getServiceInstances(param: { serviceId: string; isRelation: boolean }): Promise<Nullable<AxiosResponse>> {
async getServiceInstances(param: { serviceId: string; isRelation: boolean }) {
if (!param.serviceId) {
return null;
}
const res: AxiosResponse = await graphql.query("queryInstances").params({
const response = await graphql.query("queryInstances").params({
serviceId: param.serviceId,
duration: useAppStoreWithOut().durationTime,
});
if (!res.data.errors) {
this.instances = (res.data.data.pods || []).map((d: Instance) => {
if (!response.errors) {
this.instances = (response.data.pods || []).map((d: Instance) => {
d.value = d.id || "";
return d;
});
}
return res.data;
return response;
},
async createTask(param: AsyncProfileTaskCreationRequest) {
const res: AxiosResponse = await graphql
.query("saveAsyncProfileTask")
.params({ asyncProfilerTaskCreationRequest: param });
const response = await graphql.query("saveAsyncProfileTask").params({ asyncProfilerTaskCreationRequest: param });
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.getTaskList();
return res.data;
return response;
},
async getAsyncProfilingAnalyze(params: { taskId: string; instanceIds: Array<string>; eventType: string }) {
if (!params.instanceIds.length) {
return new Promise((resolve) => resolve({}));
}
this.loadingTree = true;
const res: AxiosResponse = await graphql.query("getAsyncProfileAnalyze").params({ request: params });
const response = await graphql.query("getAsyncProfileAnalyze").params({ request: params });
this.loadingTree = false;
if (res.data.errors) {
if (response.errors) {
this.analyzeTrees = [];
return res.data;
return response;
}
const { analysisResult } = res.data.data;
const { analysisResult } = response.data;
if (!analysisResult) {
this.analyzeTrees = [];
return res.data;
return response;
}
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 graphql from "@/graphql";
import type { MonitorInstance, MonitorProcess } from "@/types/continous-profiling";
import type { AxiosResponse } from "axios";
import { dateFormat } from "@/utils/dateFormat";
interface ContinousProfilingState {
@ -84,37 +83,37 @@ export const continousProfilingStore = defineStore({
checkItems: CheckItems[];
}[],
) {
const res: AxiosResponse = await graphql.query("editStrategy").params({
const response = await graphql.query("editStrategy").params({
request: {
serviceId,
targets,
},
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
return res.data;
return response;
},
async getStrategyList(params: { serviceId: string }) {
if (!params.serviceId) {
return new Promise((resolve) => resolve({}));
}
this.policyLoading = true;
const res: AxiosResponse = await graphql.query("getStrategyList").params(params);
const response = await graphql.query("getStrategyList").params(params);
this.policyLoading = false;
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
const list = res.data.data.strategyList || [];
const list = response.data.strategyList || [];
if (!list.length) {
this.taskList = [];
this.instances = [];
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) => {
return {
...d,
@ -123,25 +122,25 @@ export const continousProfilingStore = defineStore({
});
this.setSelectedStrategy(this.strategyList[0]);
if (!this.selectedStrategy.type) {
return res.data;
return response;
}
this.getMonitoringInstances(params.serviceId);
return res.data;
return response;
},
async getMonitoringInstances(serviceId: string): Promise<Nullable<AxiosResponse>> {
async getMonitoringInstances(serviceId: string) {
this.instancesLoading = true;
if (!serviceId) {
return null;
}
const res: AxiosResponse = await graphql.query("getMonitoringInstances").params({
const response = await graphql.query("getMonitoringInstances").params({
serviceId,
target: this.selectedStrategy.type,
});
this.instancesLoading = false;
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.instances = (res.data.data.instances || [])
this.instances = (response.data.instances || [])
.map((d: MonitorInstance) => {
const processes = (d.processes || [])
.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);
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 type { LayoutConfig } from "@/types/dashboard";
import graphql from "@/graphql";
import query from "@/graphql/fetch";
import customQuery from "@/graphql/custom-query";
import type { DashboardItem } from "@/types/dashboard";
import { useSelectorStore } from "@/store/modules/selectors";
import { NewControl, TextConfig, TimeRangeConfig, ControlsTypes } from "../data";
import type { AxiosResponse } from "axios";
import { ElMessage } from "element-plus";
import { EntityType, WidgetType } from "@/views/dashboard/data";
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 } }) {
const res: AxiosResponse = await query(param);
return res.data;
return await customQuery(param);
},
async fetchTemplates() {
const res: AxiosResponse = await graphql.query("getTemplates").params({});
const res = await graphql.query("getTemplates").params({});
if (res.data.errors) {
return res.data;
if (res.errors) {
return res;
}
const data = res.data.data.getAllTemplates;
const data = res.data.getAllTemplates;
let list = [];
for (const t of data) {
const c = JSON.parse(t.configuration);
const key = [c.layer, c.entity, c.name].join("_");
list.push({
...c,
id: t.id,
@ -372,20 +354,20 @@ export const dashboardStore = defineStore({
this.dashboards = JSON.parse(sessionStorage.getItem("dashboards") || "[]");
},
async updateDashboard(setting: { id: string; configuration: string }) {
const res: AxiosResponse = await graphql.query("updateTemplate").params({
const resp = await graphql.query("updateTemplate").params({
setting,
});
if (res.data.errors) {
ElMessage.error(res.data.errors);
return res.data;
if (resp.errors) {
ElMessage.error(resp.errors);
return resp;
}
const json = res.data.data.changeTemplate;
const json = resp.data.changeTemplate;
if (!json.status) {
ElMessage.error(json.message);
return res.data;
return resp;
}
ElMessage.success("Saved successfully");
return res.data;
return resp;
},
async saveDashboard() {
if (!this.currentDashboard?.name) {
@ -419,14 +401,14 @@ export const dashboardStore = defineStore({
}
res = await graphql.query("addNewTemplate").params({ setting: { configuration: JSON.stringify(c) } });
json = res.data.data.addTemplate;
json = res.data.addTemplate;
if (!json.status) {
ElMessage.error(json.message);
}
}
if (res.data.errors || res.errors) {
ElMessage.error(res.data.errors);
return res.data;
if (res.errors) {
ElMessage.error(res.errors);
return res;
}
if (!json.status) {
return json;
@ -448,16 +430,16 @@ export const dashboardStore = defineStore({
return json;
},
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) {
ElMessage.error(res.data.errors);
return res.data;
if (res.errors) {
ElMessage.error(res.errors);
return res;
}
const json = res.data.data.disableTemplate;
const json = res.data.disableTemplate;
if (!json.status) {
ElMessage.error(json.message);
return res.data;
return res;
}
this.dashboards = this.dashboards.filter((d: Recordable) => d.id !== this.currentDashboard?.id);
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 { store } from "@/store";
import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import type { Conditions, Log } from "@/types/demand-log";
@ -60,16 +59,16 @@ export const demandLogStore = defineStore({
},
async getInstances(id: string) {
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,
duration: useAppStoreWithOut().durationTime,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.instances = res.data.data.pods || [];
return res.data;
this.instances = response.data.pods || [];
return response;
},
async getContainers(serviceInstanceId: string) {
if (!serviceInstanceId) {
@ -78,35 +77,35 @@ export const demandLogStore = defineStore({
const condition = {
serviceInstanceId,
};
const res: AxiosResponse = await graphql.query("fetchContainers").params({ condition });
const response = await graphql.query("fetchContainers").params({ condition });
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
if (res.data.data.containers.errorReason) {
if (response.data.containers.errorReason) {
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 res.data;
return response;
},
async getDemandLogs() {
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;
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
if (res.data.data.logs.errorReason) {
this.setLogs([], res.data.data.logs.errorReason);
return res.data;
if (response.data.logs.errorReason) {
this.setLogs([], response.data.logs.errorReason);
return response;
}
this.total = res.data.data.logs.logs.length;
const logs = res.data.data.logs.logs.map((d: Log) => d.content).join("\n");
this.total = response.data.logs.logs.length;
const logs = response.data.logs.logs.map((d: Log) => d.content).join("\n");
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 { store } from "@/store";
import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { EBPFProfilingTriggerType } from "../data";
interface EbpfState {
taskList: Array<Recordable<EBPFTaskList>>;
@ -57,70 +56,70 @@ export const ebpfStore = defineStore({
this.analyzeTrees = tree;
},
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) {
return res.data;
if (response.errors) {
return response;
}
const json = res.data.data.createTaskData;
const json = response.data.createTaskData;
this.couldProfiling = json.couldProfiling || false;
this.labels = json.processLabels.map((d: string) => {
return { label: d, value: d };
});
return res.data;
return response;
},
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) {
return res.data;
if (response.errors) {
return response;
}
this.getTaskList({
serviceId: param.serviceId,
targets: ["ON_CPU", "OFF_CPU"],
triggerType: EBPFProfilingTriggerType.FIXED_TIME,
});
return res.data;
return response;
},
async getTaskList(params: { serviceId: string; targets: string[] }) {
if (!params.serviceId) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql.query("getEBPFTasks").params(params);
const response = await graphql.query("getEBPFTasks").params(params);
this.ebpfTips = "";
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.taskList = res.data.data.queryEBPFTasks || [];
this.taskList = response.data.queryEBPFTasks || [];
this.selectedTask = this.taskList[0] || {};
this.setSelectedTask(this.selectedTask);
if (!this.taskList.length) {
return res.data;
return response;
}
this.getEBPFSchedules({ taskId: String(this.taskList[0].taskId) });
return res.data;
return response;
},
async getEBPFSchedules(params: { taskId: string }) {
if (!params.taskId) {
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 = [];
return res.data;
return response;
}
this.ebpfTips = "";
const { eBPFSchedules } = res.data.data;
const { eBPFSchedules } = response.data;
this.eBPFSchedules = eBPFSchedules;
if (!eBPFSchedules.length) {
this.eBPFSchedules = [];
this.analyzeTrees = [];
}
return res.data;
return response;
},
async getEBPFAnalyze(params: {
scheduleIdList: string[];
@ -134,24 +133,24 @@ export const ebpfStore = defineStore({
if (!params.timeRanges.length) {
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 = [];
return res.data;
return response;
}
const { analysisEBPFResult } = res.data.data;
const { analysisEBPFResult } = response.data;
this.ebpfTips = analysisEBPFResult.tip;
if (!analysisEBPFResult) {
this.analyzeTrees = [];
return res.data;
return response;
}
if (analysisEBPFResult.tip) {
this.analyzeTrees = [];
return res.data;
return response;
}
this.analyzeTrees = analysisEBPFResult.trees;
return res.data;
return response;
},
},
});

View File

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

View File

@ -18,10 +18,10 @@ import { defineStore } from "pinia";
import type { Instance, Endpoint, Service } from "@/types/selector";
import { store } from "@/store";
import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useDuration } from "@/hooks/useDuration";
import { EndpointsTopNDefault } from "../data";
interface LogState {
@ -34,6 +34,7 @@ interface LogState {
logs: Recordable[];
loadLogs: boolean;
}
const { getDurationTime } = useDuration();
export const logStore = defineStore({
id: "log",
@ -42,7 +43,7 @@ export const logStore = defineStore({
instances: [{ value: "0", label: "All" }],
endpoints: [{ value: "0", label: "All" }],
conditions: {
queryDuration: useAppStoreWithOut().durationTime,
queryDuration: getDurationTime(),
paging: { pageNum: 1, pageSize: 15 },
},
supportQueryLogsByKeywords: true,
@ -57,56 +58,56 @@ export const logStore = defineStore({
resetState() {
this.logs = [];
this.conditions = {
queryDuration: useAppStoreWithOut().durationTime,
queryDuration: getDurationTime(),
paging: { pageNum: 1, pageSize: 15 },
};
},
async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({
const response = await graphql.query("queryServices").params({
layer,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.services = res.data.data.services;
return res.data;
this.services = response.data.services;
return response;
},
async getInstances(id: string) {
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,
duration: useAppStoreWithOut().durationTime,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.instances = [{ value: "0", label: "All" }, ...res.data.data.pods];
return res.data;
this.instances = [{ value: "0", label: "All" }, ...response.data.pods];
return response;
},
async getEndpoints(id: string, keyword?: string) {
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,
duration: useAppStoreWithOut().durationTime,
keyword: keyword || "",
limit: EndpointsTopNDefault,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.endpoints = [{ value: "0", label: "All" }, ...res.data.data.pods];
return res.data;
this.endpoints = [{ value: "0", label: "All" }, ...response.data.pods];
return response;
},
async getLogsByKeywords() {
const res: AxiosResponse = await graphql.query("queryLogsByKeywords").params({});
const response = await graphql.query("queryLogsByKeywords").params({});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.supportQueryLogsByKeywords = res.data.data.support;
return res.data;
this.supportQueryLogsByKeywords = response.data.support;
return response;
},
async getLogs() {
const dashboardStore = useDashboardStore();
@ -117,39 +118,31 @@ export const logStore = defineStore({
},
async getServiceLogs() {
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;
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.logs = res.data.data.queryLogs.logs;
return res.data;
this.logs = response.data.queryLogs.logs;
return response;
},
async getBrowserLogs() {
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;
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.logs = res.data.data.queryBrowserErrorLogs.logs;
return res.data;
this.logs = response.data.queryBrowserErrorLogs.logs;
return response;
},
async getLogTagKeys() {
const res: AxiosResponse = await graphql
.query("queryLogTagKeys")
.params({ duration: useAppStoreWithOut().durationTime });
return res.data;
return await graphql.query("queryLogTagKeys").params({ duration: useAppStoreWithOut().durationTime });
},
async getLogTagValues(tagKey: string) {
const res: AxiosResponse = await graphql
.query("queryLogTagValues")
.params({ tagKey, duration: useAppStoreWithOut().durationTime });
return res.data;
return await graphql.query("queryLogTagValues").params({ tagKey, duration: useAppStoreWithOut().durationTime });
},
},
});

View File

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

View File

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

View File

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

View File

@ -0,0 +1,80 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineStore } from "pinia";
import { store } from "@/store";
import fetchQuery from "@/graphql/http";
import type { ClusterNode, ConfigTTL } from "@/types/settings";
import { HotAndWarmOpt } from "@/views/settings/data";
import { TTLTypes, TTLColdMap } from "../data";
interface SettingsState {
clusterNodes: ClusterNode[];
debuggingConfig: Indexable<string>;
configTTL: Recordable<ConfigTTL>;
}
export const settingsStore = defineStore({
id: "settings",
state: (): SettingsState => ({
clusterNodes: [],
debuggingConfig: {},
configTTL: {},
}),
actions: {
async getClusterNodes() {
const response = await fetchQuery({
method: "get",
path: "ClusterNodes",
});
this.clusterNodes = response.nodes;
return response;
},
async getConfigTTL() {
const response = await fetchQuery({
method: "get",
path: "ConfigTTL",
});
for (const item of Object.keys(response)) {
const rows = [];
const row: Indexable<string> = { type: TTLTypes.HotAndWarm };
const rowCold: Indexable<string> = { type: TTLTypes.Cold };
for (const key of Object.keys(response[item])) {
if (HotAndWarmOpt.includes(key)) {
row[key] = response[item][key];
} else {
rowCold[TTLColdMap[key] as string] = response[item][key];
}
}
rows.push(row, rowCold);
this.configTTL[item] = rows;
}
return response;
},
async getDebuggingConfigDump() {
const response = await fetchQuery({
method: "get",
path: "DebuggingConfigDump",
});
this.debuggingConfig = response;
return response;
},
},
});
export function useSettingsStore(): Recordable {
return settingsStore(store);
}

View File

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

View File

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

View File

@ -19,11 +19,11 @@ import type { Instance, Endpoint, Service } from "@/types/selector";
import type { Trace, Span } from "@/types/trace";
import { store } from "@/store";
import graphql from "@/graphql";
import type { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { QueryOrders } from "@/views/dashboard/data";
import { EndpointsTopNDefault } from "../data";
import { useDuration } from "@/hooks/useDuration";
interface TraceState {
services: Service[];
instances: Instance[];
@ -34,7 +34,9 @@ interface TraceState {
conditions: Recordable;
traceSpanLogs: Recordable[];
selectorStore: Recordable;
selectedSpan: Recordable<Span>;
}
const { getDurationTime } = useDuration();
export const traceStore = defineStore({
id: "trace",
@ -45,8 +47,9 @@ export const traceStore = defineStore({
traceList: [],
traceSpans: [],
currentTrace: {},
selectedSpan: {},
conditions: {
queryDuration: useAppStoreWithOut().durationTime,
queryDuration: getDurationTime(),
traceState: "ALL",
queryOrder: QueryOrders[0].value,
paging: { pageNum: 1, pageSize: 20 },
@ -64,137 +67,138 @@ export const traceStore = defineStore({
setTraceSpans(spans: Span[]) {
this.traceSpans = spans;
},
setSelectedSpan(span: Span) {
this.selectedSpan = span;
},
resetState() {
this.traceSpans = [];
this.traceList = [];
this.currentTrace = {};
this.conditions = {
queryDuration: useAppStoreWithOut().durationTime,
queryDuration: getDurationTime(),
paging: { pageNum: 1, pageSize: 20 },
traceState: "ALL",
queryOrder: QueryOrders[0].value,
};
},
async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({
const response = await graphql.query("queryServices").params({
layer,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.services = res.data.data.services;
return res.data;
this.services = response.data.services;
return response;
},
async getService(serviceId: string) {
if (!serviceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryService").params({
const response = await graphql.query("queryService").params({
serviceId,
});
return res.data;
return response;
},
async getInstance(instanceId: string) {
if (!instanceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryInstance").params({
const response = await graphql.query("queryInstance").params({
instanceId,
});
return res.data;
return response;
},
async getEndpoint(endpointId: string) {
if (!endpointId) {
return;
}
const res: AxiosResponse = await graphql.query("queryEndpoint").params({
return await graphql.query("queryEndpoint").params({
endpointId,
});
return res.data;
},
async getInstances(id: string) {
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,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.instances = [{ value: "0", label: "All" }, ...res.data.data.pods];
return res.data;
this.instances = [{ value: "0", label: "All" }, ...response.data.pods];
return response;
},
async getEndpoints(id: string, keyword?: string) {
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,
duration: useAppStoreWithOut().durationTime,
keyword: keyword || "",
limit: EndpointsTopNDefault,
});
if (res.data.errors) {
return res.data;
if (response.errors) {
return response;
}
this.endpoints = [{ value: "0", label: "All" }, ...res.data.data.pods];
return res.data;
this.endpoints = [{ value: "0", label: "All" }, ...response.data.pods];
return response;
},
async getTraces() {
const res: AxiosResponse = await graphql.query("queryTraces").params({ condition: this.conditions });
if (res.data.errors) {
return res.data;
const response = await graphql.query("queryTraces").params({ condition: this.conditions });
if (response.errors) {
return response;
}
if (!res.data.data.data.traces.length) {
if (!response.data.data.traces.length) {
this.traceList = [];
this.setCurrentTrace({});
this.setTraceSpans([]);
return res.data;
return response;
}
this.getTraceSpans({ traceId: res.data.data.data.traces[0].traceIds[0] });
this.traceList = res.data.data.data.traces.map((d: Trace) => {
this.getTraceSpans({ traceId: response.data.data.traces[0].traceIds[0] });
this.traceList = response.data.data.traces.map((d: Trace) => {
d.traceIds = d.traceIds.map((id: string) => {
return { value: id, label: id };
});
return d;
});
this.setCurrentTrace(res.data.data.data.traces[0] || {});
return res.data;
this.setCurrentTrace(response.data.data.traces[0] || {});
return response;
},
async getTraceSpans(params: { traceId: string }) {
const res: AxiosResponse = await graphql.query("queryTrace").params(params);
if (res.data.errors) {
return res.data;
const appStore = useAppStoreWithOut();
let response;
if (appStore.coldStageMode) {
response = await graphql
.query("queryTraceSpansFromColdStage")
.params({ ...params, duration: this.conditions.queryDuration });
} else {
response = await graphql.query("querySpans").params(params);
}
const data = res.data.data.trace.spans;
if (response.errors) {
return response;
}
const data = response.data.trace.spans;
this.setTraceSpans(data || []);
return res.data;
return response;
},
async getSpanLogs(params: Recordable) {
const res: AxiosResponse = await graphql.query("queryServiceLogs").params(params);
if (res.data.errors) {
const response = await graphql.query("queryServiceLogs").params(params);
if (response.errors) {
this.traceSpanLogs = [];
return res.data;
return response;
}
this.traceSpanLogs = res.data.data.queryLogs.logs || [];
return res.data;
this.traceSpanLogs = response.data.queryLogs.logs || [];
return response;
},
async getTagKeys() {
const res: AxiosResponse = await graphql
.query("queryTraceTagKeys")
.params({ duration: useAppStoreWithOut().durationTime });
return res.data;
return await graphql.query("queryTraceTagKeys").params({ duration: useAppStoreWithOut().durationTime });
},
async getTagValues(tagKey: string) {
const res: AxiosResponse = await graphql
.query("queryTraceTagValues")
.params({ tagKey, duration: useAppStoreWithOut().durationTime });
return res.data;
return await graphql.query("queryTraceTagValues").params({ tagKey, duration: useAppStoreWithOut().durationTime });
},
},
});

View File

@ -39,14 +39,12 @@ export type EventParams = {
seriesIndex: number;
seriesName: string;
name: string;
dataIndex: number;
data: unknown;
dataType: string;
value: number | any[];
color: string;
event: Record<string, T>;
event: Recordable;
dataIndex: number;
event: any;
};
export interface MenuOptions extends SubItem {
@ -68,3 +66,25 @@ export interface SubItem {
descKey: string;
i18nKey: string;
}
export interface MetricsTTL {
minute: number;
hour: number;
day: number;
coldMinute: number;
coldHour: number;
coldDay: number;
}
export interface RecordsTTL {
normal: number;
trace: number;
zipkinTrace: number;
log: number;
browserErrorLog: number;
coldNormal: number;
coldTrace: number;
coldZipkinTrace: number;
coldLog: number;
coldBrowserErrorLog: number;
}

View File

@ -42,6 +42,8 @@ declare module 'vue' {
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTabPane: typeof import('element-plus/es')['ElTabPane']
ElTabs: typeof import('element-plus/es')['ElTabs']
ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
Graph: typeof import('./../components/Graph/Graph.vue')['default']

View File

@ -35,7 +35,7 @@ export interface MonitorInstance {
lastTriggerTimestamp: number;
processes: MonitorProcess[];
}
interface MonitorProcess {
export interface MonitorProcess {
id: string;
name: string;
detectType: string;

View File

@ -60,10 +60,7 @@ export type Filters = {
dataIndex: number;
sourceId: string;
isRange?: boolean;
duration?: {
startTime: string;
endTime: string;
};
duration?: DurationTime;
traceId?: string;
spanId?: string;
segmentId?: string;

View File

@ -15,7 +15,8 @@
* limitations under the License.
*/
import type { Process } from "./selector";
import type { Process as selectorProcess } from "./selector";
export interface EBPFTaskCreationRequest {
serviceId: string;
processLabels: string[];
@ -58,12 +59,12 @@ interface ProfilingCause {
export interface EBPFProfilingSchedule {
scheduleId: string;
taskId: string;
process: Process;
process: selectorProcess;
endTime: number;
startTime: number;
}
export type Process = Process;
export type Process = selectorProcess;
export type StackElement = {
id: string;
originId: string;
@ -106,7 +107,6 @@ export type ProcessNode = {
serviceName: string;
serviceInstanceId: string;
serviceInstanceName: string;
name: string;
isReal: boolean;
x?: number;
y?: number;

View File

@ -14,11 +14,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Duration } from "./app";
const enum EventType {
ALL = "",
NORMAL = "Normal",
ERROR = "Error",
}
export type Event = {
uuid: string;
source: SourceInput;
name: string;
type: string;
type: EventType;
message: string;
parameters: { key: string; value: string }[];
startTime: number | string;

View File

@ -42,16 +42,6 @@ export type Endpoint = {
merge?: string;
};
export type Service = {
id: string;
value: string;
label: string;
group: string;
normal: boolean;
layers: string[];
shortName: string;
};
export type Process = {
id: string;
name: string;

View File

@ -15,11 +15,15 @@
* limitations under the License.
*/
import axios from "axios";
const CancelToken = axios.CancelToken;
import type { MetricsTTL, RecordsTTL } from "@/types/app";
export const cancelToken = (): any =>
new CancelToken(function executor(c) {
const w = window as any;
(w.axiosCancel || []).push(c);
});
export type ClusterNode = {
host: string;
port: number;
self: boolean;
};
export type ConfigTTL = {
metrics: MetricsTTL;
records: RecordsTTL;
};

View File

@ -47,10 +47,10 @@ export interface Span {
tags?: Array<Map<string, string>>;
logs?: log[];
parentSegmentId?: string;
refs?: Ref[];
key?: string;
}
export type Ref = {
type: string;
type?: string;
parentSegmentId: string;
parentSpanId: number;
traceId: string;
@ -60,13 +60,6 @@ export interface log {
data: Map<string, string>;
}
export interface Ref {
traceId: string;
parentSegmentId: string;
parentSpanId: number;
type: string;
}
export interface StatisticsSpan {
groupRef: StatisticsGroupRef;
maxTime: number;
@ -81,7 +74,7 @@ export interface StatisticsGroupRef {
type: string;
}
export class TraceTreeRef {
export interface TraceTreeRef {
segmentMap: Map<string, Span>;
segmentIdGroup: string[];
}

View File

@ -13,172 +13,8 @@ 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="settings">
<div class="flex-h item">
<span class="label">{{ t("language") }}</span>
<Selector
v-model="lang"
:options="Languages"
placeholder="Select a language"
@change="setLang"
size="small"
style="font-size: 14px; width: 180px"
/>
</div>
<div class="flex-h item">
<span class="label">{{ t("serverZone") }}</span>
<div>
<span>UTC</span>
<span class="ml-5 mr-5">{{ utcHour >= 0 ? "+" : "" }}</span>
<input type="number" v-model="utcHour" min="-12" max="14" class="utc-input" @change="setUTCHour" />
<span class="ml-5 mr-5">:</span>
<span class="utc-min">{{ utcMin > 9 || utcMin === 0 ? null : 0 }}</span>
<input type="number" v-model="utcMin" min="0" max="59" class="utc-input" @change="setUTCMin" />
</div>
</div>
<div class="flex-h item">
<span class="label">{{ t("auto") }}</span>
<el-switch v-model="auto" @change="handleAuto" style="height: 25px" />
<div class="auto-time ml-5">
<span class="auto-select">
<input type="number" v-model="autoTime" @change="changeAutoTime" min="1" />
</span>
{{ t("second") }}
<i class="ml-10">{{ t("timeReload") }}</i>
</div>
</div>
</div>
<Content />
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useAppStoreWithOut } from "@/store/modules/app";
import timeFormat from "@/utils/timeFormat";
import { Languages } from "@/constants/data";
import Selector from "@/components/Selector.vue";
import getLocalTime from "@/utils/localtime";
const { t, locale } = useI18n();
const appStore = useAppStoreWithOut();
const lang = ref<string>(locale.value || "en");
const autoTime = ref<number>(6);
const auto = ref<boolean>(appStore.autoRefresh || false);
const utcHour = ref<number>(appStore.utcHour);
const utcMin = ref<number>(appStore.utcMin);
const handleReload = () => {
const gap = appStore.duration.end.getTime() - appStore.duration.start.getTime();
const dates: Date[] = [
getLocalTime(appStore.utc, new Date(new Date().getTime() - gap)),
getLocalTime(appStore.utc, new Date()),
];
appStore.setDuration(timeFormat(dates));
};
const handleAuto = () => {
if (autoTime.value < 1) {
return;
}
appStore.setAutoRefresh(auto.value);
if (auto.value) {
handleReload();
appStore.setReloadTimer(setInterval(handleReload, autoTime.value * 1000));
} else {
if (appStore.reloadTimer) {
clearInterval(appStore.reloadTimer);
}
}
};
const changeAutoTime = () => {
if (autoTime.value < 1) {
return;
}
if (appStore.reloadTimer) {
clearInterval(appStore.reloadTimer);
}
if (auto.value) {
handleReload();
appStore.setReloadTimer(setInterval(handleReload, autoTime.value * 1000));
}
};
const setLang = (): void => {
locale.value = lang.value;
window.localStorage.setItem("language", lang.value);
};
const setUTCHour = () => {
if (utcHour.value < -12) {
utcHour.value = -12;
}
if (utcHour.value > 14) {
utcHour.value = 14;
}
if (isNaN(utcHour.value)) {
utcHour.value = 0;
}
appStore.setUTC(utcHour.value, utcMin.value);
};
const setUTCMin = () => {
if (utcMin.value < 0) {
utcMin.value = 0;
}
if (utcMin.value > 59) {
utcMin.value = 59;
}
if (isNaN(utcMin.value)) {
utcMin.value = 0;
}
appStore.setUTC(utcHour.value, utcMin.value);
};
import Content from "./settings/Index.vue";
</script>
<style lang="scss" scoped>
.utc-input {
color: inherit;
background: 0;
border: 0;
outline: none;
padding-bottom: 0;
}
.utc-min {
display: inline-block;
padding-top: 2px;
}
.auto-select {
border-radius: 3px;
background-color: $theme-background;
padding: 1px;
input {
width: 38px;
border-style: unset;
outline: 0;
}
}
.settings {
color: var(--sw-setting-color);
font-size: 13px;
padding: 20px;
.item {
margin-top: 10px;
}
input {
outline: 0;
width: 50px;
border-radius: 3px;
border: 1px solid $disabled-color;
text-align: center;
height: 25px;
}
.label {
width: 180px;
display: inline-block;
font-weight: 500;
color: $font-color;
line-height: 25px;
}
}
</style>

View File

@ -30,6 +30,16 @@ limitations under the License. -->
<span class="grey">{{ t("searchKeyword") }}: </span>
<el-input size="small" v-model="keyword" class="alarm-tool-input" @change="refreshAlarms({ pageNum: 1 })" />
</div>
<div>
<span class="sm b grey mr-5">{{ t("timeRange") }}:</span>
<TimePicker
:value="[durationRow.start, durationRow.end]"
:maxRange="maxRange"
position="bottom"
format="YYYY-MM-DD HH:mm"
@input="changeDuration"
/>
</div>
<div class="pagination">
<el-pagination
v-model="pageNum"
@ -51,31 +61,40 @@ limitations under the License. -->
</nav>
</template>
<script lang="ts" setup>
import { ref, computed } from "vue";
import { ref, computed, watch } from "vue";
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
import ConditionTags from "@/views/components/ConditionTags.vue";
import { AlarmOptions } from "./data";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useAppStoreWithOut, InitializationDurationRow } from "@/store/modules/app";
import { useAlarmStore } from "@/store/modules/alarm";
import { ElMessage } from "element-plus";
import { useDuration } from "@/hooks/useDuration";
import timeFormat from "@/utils/timeFormat";
import type { DurationTime, Duration } from "@/types/app";
import { Themes } from "@/constants/data";
/*global Recordable */
const appStore = useAppStoreWithOut();
const alarmStore = useAlarmStore();
const { t } = useI18n();
const { setDurationRow, getDurationTime, getMaxRange } = useDuration();
const pageSize = 20;
const entity = ref<string>("");
const keyword = ref<string>("");
const pageNum = ref<number>(1);
const duration = ref<DurationTime>(getDurationTime());
const durationRow = ref<Duration>(InitializationDurationRow);
const total = computed(() =>
alarmStore.alarms.length === pageSize ? pageSize * pageNum.value + 1 : pageSize * pageNum.value,
);
const maxRange = computed(() =>
getMaxRange(appStore.coldStageMode ? appStore.recordsTTL.coldNormal : appStore.recordsTTL.normal),
);
refreshAlarms({ pageNum: 1 });
async function refreshAlarms(param: { pageNum: number; tagsMap?: any }) {
const params: any = {
duration: appStore.durationTime,
async function refreshAlarms(param: { pageNum: number; tagsMap?: Recordable }) {
const params: Recordable = {
duration: duration.value,
paging: {
pageNum: param.pageNum,
pageSize,
@ -91,7 +110,14 @@ limitations under the License. -->
}
}
function changeEntity(param: any) {
function changeDuration(val: Date[]) {
durationRow.value = timeFormat(val);
setDurationRow(durationRow.value);
duration.value = getDurationTime();
refreshAlarms({ pageNum: 1 });
}
function changeEntity(param: { value: string }[]) {
entity.value = param[0].value;
refreshAlarms({ pageNum: 1 });
}
@ -100,6 +126,16 @@ limitations under the License. -->
pageNum.value = p;
refreshAlarms({ pageNum: p });
}
watch(
() => appStore.coldStageMode,
() => {
durationRow.value = InitializationDurationRow;
setDurationRow(durationRow.value);
duration.value = getDurationTime();
refreshAlarms({ pageNum: 1 });
},
);
</script>
<style lang="scss" scoped>
.alarm-tool {

View File

@ -25,12 +25,25 @@ limitations under the License. -->
:options="asyncProfilingStore.instances"
placeholder="Select instances"
@change="changeInstances"
:filterable="false"
:filterable="true"
/>
</div>
<div>
<div class="label">{{ t("duration") }}</div>
<Radio class="mb-5" :value="duration" :options="DurationOptions" @change="changeDuration" />
<div v-if="duration === DurationOptions[5].value" class="custom-duration">
<div class="label">{{ t("customDuration") }} ({{ t("seconds") }})</div>
<el-input
size="small"
class="profile-input"
v-model="customDurationSeconds"
type="number"
:min="1"
:max="900"
placeholder="Enter duration in seconds (1-900)"
/>
<div class="hint">{{ t("maxDuration") }}: 900 {{ t("seconds") }} (15 {{ t("minutes") }})</div>
</div>
</div>
<div>
<div class="label">{{ t("profilingEvents") }}</div>
@ -113,6 +126,7 @@ limitations under the License. -->
const execArgs = ref<string>("");
const loading = ref<boolean>(false);
const PartofEvents = [ProfilingEvents[3], ProfilingEvents[4], ProfilingEvents[5]];
const customDurationSeconds = ref<number>(60);
function changeDuration(val: string) {
duration.value = val;
@ -138,10 +152,22 @@ limitations under the License. -->
}
async function createTask() {
let finalDuration: number;
if (duration.value === DurationOptions[5].value) {
if (!customDurationSeconds.value || customDurationSeconds.value < 1 || customDurationSeconds.value > 900) {
ElMessage.error(t("invalidProfilingDurationRange"));
return;
}
finalDuration = customDurationSeconds.value;
} else {
finalDuration = Number(duration.value);
}
const params = {
serviceId: selectorStore.currentService.id,
serviceInstanceIds: serviceInstanceIds.value,
duration: Number(duration.value) * 60,
duration: finalDuration,
events: asyncEvents.value,
execArgs: execArgs.value,
};
@ -158,7 +184,7 @@ limitations under the License. -->
return;
}
emits("close");
ElMessage.success("Task created successfully");
ElMessage.success(t("taskCreatedSuccessfully"));
}
</script>
<style lang="scss" scoped>
@ -184,4 +210,13 @@ limitations under the License. -->
width: 600px;
margin-top: 50px;
}
.custom-duration {
margin-top: 10px;
}
.hint {
font-size: $font-size-smaller;
color: var(--text-color-placeholder);
}
</style>

View File

@ -16,9 +16,12 @@
*/
export const DurationOptions = [
{ value: "5", label: "5 min" },
{ value: "10", label: "10 min" },
{ value: "15", label: "15 min" },
{ value: "30", label: "30 sec" },
{ value: "60", label: "1 min" },
{ value: "300", label: "5 min" },
{ value: "600", label: "10 min" },
{ value: "900", label: "15 min" },
{ value: "custom", label: "Custom" },
];
export const ProfilingEvents = ["CPU", "ALLOC", "LOCK", "WALL", "CTIMER", "ITIMER"];

View File

@ -56,6 +56,16 @@ limitations under the License. -->
@change="changeField('category', $event)"
/>
</div>
<div>
<span class="sm b grey mr-5">{{ t("timeRange") }}:</span>
<TimePicker
:value="[durationRow.start, durationRow.end]"
:maxRange="maxRange"
position="bottom"
format="YYYY-MM-DD HH:mm"
@input="changeDuration"
/>
</div>
<el-button class="search-btn" size="small" type="primary" @click="searchLogs">
{{ t("search") }}
</el-button>
@ -119,20 +129,21 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, watch, onUnmounted } from "vue";
import { ref, reactive, watch, onUnmounted, computed } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import type { Option } from "@/types/app";
import { ElMessage } from "element-plus";
import { useLogStore } from "@/store/modules/log";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useAppStoreWithOut, InitializationDurationRow } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { useDuration } from "@/hooks/useDuration";
import ConditionTags from "@/views/components/ConditionTags.vue";
import { ElMessage } from "element-plus";
import timeFormat from "@/utils/timeFormat";
import { EntityType } from "../../data";
import { ErrorCategory } from "./data";
import type { LayoutConfig } from "@/types/dashboard";
import type { DurationTime } from "@/types/app";
import type { Option, DurationTime, Duration } from "@/types/app";
/*global defineProps, Recordable */
const props = defineProps({
@ -147,8 +158,9 @@ limitations under the License. -->
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const logStore = useLogStore();
const { setDurationRow, getDurationTime, getMaxRange } = useDuration();
const traceId = ref<string>((props.data.filters && props.data.filters.traceId) || "");
const duration = ref<DurationTime>((props.data.filters && props.data.filters.duration) || appStore.durationTime);
const duration = ref<DurationTime>((props.data.filters && props.data.filters.duration) || getDurationTime());
const keywordsOfContent = ref<string[]>([]);
const excludingKeywordsOfContent = ref<string[]>([]);
const tagsList = ref<string[]>([]);
@ -156,12 +168,24 @@ limitations under the License. -->
const contentStr = ref<string>("");
const excludingContentStr = ref<string>("");
const isBrowser = ref<boolean>(dashboardStore.layerId === "BROWSER");
const durationRow = ref<Duration>(InitializationDurationRow);
const state = reactive<Recordable>({
instance: { value: "0", label: "All" },
endpoint: { value: "0", label: "All" },
service: { value: "", label: "" },
category: { value: "ALL", label: "All" },
});
const maxRange = computed(() =>
getMaxRange(
appStore.coldStageMode
? isBrowser.value
? appStore.recordsTTL.coldBrowserErrorLog
: appStore.recordsTTL.coldLog
: isBrowser.value
? appStore.recordsTTL.browserErrorLog
: appStore.recordsTTL.log,
),
);
if (props.needQuery) {
init();
}
@ -275,6 +299,11 @@ limitations under the License. -->
ElMessage.error(res.errors);
}
}
function changeDuration(val: Date[]) {
durationRow.value = timeFormat(val);
setDurationRow(durationRow.value);
duration.value = getDurationTime();
}
function changeField(type: string, opt: any) {
state[type] = opt[0];
if (type === "service") {
@ -352,12 +381,12 @@ limitations under the License. -->
},
);
watch(
() => appStore.durationTime,
() => appStore.coldStageMode,
() => {
duration.value = appStore.durationTime;
if (dashboardStore.entity === EntityType[1].value) {
durationRow.value = InitializationDurationRow;
setDurationRow(durationRow.value);
duration.value = getDurationTime();
init();
}
},
);
watch(
@ -368,7 +397,7 @@ limitations under the License. -->
return;
}
traceId.value = props.data.filters.traceId || "";
duration.value = props.data.filters.duration || appStore.durationTime;
duration.value = props.data.filters.duration || getDurationTime();
init();
}
},

View File

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

View File

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

View File

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

View File

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

View File

@ -71,24 +71,36 @@ limitations under the License. -->
<span class="grey mr-5">-</span>
<el-input size="small" class="inputs" v-model="maxTraceDuration" type="number" />
</div>
<div>
<span class="sm b grey mr-5">{{ t("timeRange") }}:</span>
<TimePicker
:value="[durationRow.start, durationRow.end]"
:maxRange="maxRange"
position="bottom"
format="YYYY-MM-DD HH:mm"
@input="changeDuration"
/>
</div>
</div>
<div class="flex-h">
<ConditionTags :type="'TRACE'" @update="updateTags" />
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, watch, onUnmounted } from "vue";
import { ref, reactive, watch, onUnmounted, computed } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import type { Option, DurationTime } from "@/types/app";
import type { Option, DurationTime, Duration } from "@/types/app";
import { useTraceStore } from "@/store/modules/trace";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useAppStoreWithOut, InitializationDurationRow } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import timeFormat from "@/utils/timeFormat";
import ConditionTags from "@/views/components/ConditionTags.vue";
import { ElMessage } from "element-plus";
import { EntityType, QueryOrders, Status } from "../../data";
import type { LayoutConfig } from "@/types/dashboard";
import { useDuration } from "@/hooks/useDuration";
/*global defineProps, defineEmits, Recordable */
const emits = defineEmits(["get", "search"]);
@ -99,14 +111,15 @@ limitations under the License. -->
default: () => ({ graph: {} }),
},
});
const filters = reactive<Recordable>(props.data.filters || {});
const traceId = ref<string>(filters.traceId || "");
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const traceStore = useTraceStore();
const duration = ref<DurationTime>(filters.duration || appStore.durationTime);
const { setDurationRow, getDurationTime, getMaxRange } = useDuration();
const filters = reactive<Recordable>(props.data.filters || {});
const traceId = ref<string>(filters.traceId || "");
const duration = ref<DurationTime>(filters.duration || getDurationTime());
const minTraceDuration = ref<number>();
const maxTraceDuration = ref<number>();
const tagsList = ref<string[]>([]);
@ -117,6 +130,10 @@ limitations under the License. -->
endpoint: { value: "0", label: "All" },
service: { value: "", label: "" },
});
const durationRow = ref<Duration>(InitializationDurationRow);
const maxRange = computed(() =>
getMaxRange(appStore.coldStageMode ? appStore.recordsTTL.coldTrace : appStore.recordsTTL.trace),
);
if (filters.queryOrder) {
traceStore.setTraceCondition({
queryOrder: filters.queryOrder,
@ -255,6 +272,11 @@ limitations under the License. -->
ElMessage.error(resp.errors);
}
}
function changeDuration(val: Date[]) {
durationRow.value = timeFormat(val);
setDurationRow(durationRow.value);
duration.value = getDurationTime();
}
onUnmounted(() => {
traceStore.resetState();
const config = props.data;
@ -280,12 +302,12 @@ limitations under the License. -->
},
);
watch(
() => appStore.durationTime,
() => appStore.coldStageMode,
() => {
duration.value = appStore.durationTime;
if (dashboardStore.entity === EntityType[1].value) {
durationRow.value = InitializationDurationRow;
setDurationRow(durationRow.value);
duration.value = getDurationTime();
init();
}
},
);
// Event widget associate with trace widget
@ -299,7 +321,7 @@ limitations under the License. -->
return;
}
traceId.value = props.data.filters.traceId || "";
duration.value = props.data.filters.duration || appStore.durationTime;
duration.value = props.data.filters.duration || getDurationTime();
init();
},
);

View File

@ -31,6 +31,12 @@ limitations under the License. -->
@change="changeLatency"
class="ml-10"
/>
<TimePicker
:value="[appStore.durationRow.start, appStore.durationRow.end]"
position="bottom"
format="YYYY-MM-DD HH:mm"
@input="changeTimeRange"
/>
<el-popover trigger="hover" width="250" placement="bottom">
<template #reference>
<div class="cp conditions-popup">
@ -82,6 +88,7 @@ limitations under the License. -->
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import timeFormat from "@/utils/timeFormat";
import ConditionTags from "@/views/components/ConditionTags.vue";
import { ElMessage } from "element-plus";
import { EntityType, QueryOrders, Status } from "../../data";
@ -107,6 +114,7 @@ limitations under the License. -->
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const traceStore = useTraceStore();
const timeRange = ref<number>(NaN);
const tagsList = ref<string[]>([]);
const tagsMap = ref<Option[]>([]);
const traceId = ref<string>(filters.refId || "");
@ -169,6 +177,15 @@ limitations under the License. -->
}
await queryTraces();
}
function changeTimeRange(val: Date[]) {
timeRange.value = val[1].getTime() - val[0].getTime() > 60 * 24 * 60 * 60 * 1000 ? 1 : 0;
if (timeRange.value) {
return;
}
appStore.setDuration(timeFormat(val));
}
function changeCondition() {
if (conditions.value === "latency") {
currentLatency.value = filters.latency ? filters.latency[0].data : [];

View File

@ -14,8 +14,35 @@ limitations under the License. -->
<div class="trace-t-loading" v-show="loading">
<Icon iconName="spinner" size="sm" />
</div>
<div ref="traceGraph" class="d3-graph"></div>
<el-dialog v-model="showDetail" :destroy-on-close="true" fullscreen @closed="showDetail = false">
<TableContainer
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" />
</el-dialog>
</template>
@ -23,70 +50,138 @@ limitations under the License. -->
import { ref, watch, onBeforeUnmount, onMounted } from "vue";
import type { PropType } from "vue";
import * as d3 from "d3";
import dayjs from "dayjs";
import ListGraph from "../../utils/d3-trace-list";
import TreeGraph from "../../utils/d3-trace-tree";
import type { Span, Ref } from "@/types/trace";
import SpanDetail from "./SpanDetail.vue";
import TableContainer from "../Table/TableContainer.vue";
import { useAppStoreWithOut } from "@/store/modules/app";
import { debounce } from "@/utils/debounce";
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({
data: { type: Array as PropType<Span[]>, default: () => [] },
traceId: { type: String, default: "" },
type: { type: String, default: "List" },
type: { type: String, default: TraceGraphType.LIST },
headerType: { type: String, default: "" },
});
const appStore = useAppStoreWithOut();
const loading = ref<boolean>(false);
const showDetail = ref<boolean>(false);
const fixSpansSize = ref<number>(0);
const segmentId = ref<Recordable[]>([]);
const currentSpan = ref<Array<Span>>([]);
const currentSpan = ref<Nullable<Span>>(null);
const refSpans = ref<Array<Ref>>([]);
const tree = ref<Nullable<any>>(null);
const traceGraph = ref<Nullable<HTMLDivElement>>(null);
const parentSpans = ref<Array<Span>>([]);
const refParentSpans = ref<Array<Span>>([]);
const debounceFunc = debounce(draw, 500);
const visDate = (date: number, pattern = "YYYY-MM-DD HH:mm:ss:SSS") => dayjs(date).format(pattern);
defineExpose({
tree,
});
onMounted(() => {
loading.value = true;
changeTree();
if (!traceGraph.value) {
loading.value = false;
return;
}
draw();
loading.value = false;
// monitor segment list width changes.
mutationObserver.create("trigger-resize", () => {
d3.selectAll(".d3-tip").remove();
debounceFunc();
});
window.addEventListener("resize", debounceFunc);
});
function draw() {
if (props.type === TraceGraphType.TABLE) {
segmentId.value = setLevel(segmentId.value);
return;
}
if (!traceGraph.value) {
return;
}
d3.selectAll(".d3-tip").remove();
if (props.type === "List") {
if (props.type === TraceGraphType.LIST) {
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();
} else {
return;
}
if (props.type === TraceGraphType.TREE) {
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) {
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;
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) {
if (!node || node.isBroken) {
@ -229,6 +324,7 @@ limitations under the License. -->
}
for (const i of [...fixSpans, ...props.data]) {
i.label = i.endpointName || "no operation name";
i.key = Math.random().toString(36).substring(2, 36);
i.children = [];
if (segmentGroup[i.segmentId]) {
segmentGroup[i.segmentId].push(i);
@ -272,21 +368,12 @@ limitations under the License. -->
}
}
for (const i in segmentGroup) {
if (segmentGroup[i].refs.length) {
let exit = null;
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) {
if (!segmentGroup[ref.parentSegmentId]) {
segmentId.value.push(segmentGroup[i]);
}
} else {
}
if (!segmentGroup[i].refs.length && segmentGroup[i].parentSpanId === -1) {
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) {
return (m: Recordable, n: Recordable) => {
const a = m[p];
@ -346,7 +461,7 @@ limitations under the License. -->
},
);
</script>
<style lang="scss" scoped>
<style lang="scss">
.d3-graph {
height: 100%;
}
@ -356,36 +471,50 @@ limitations under the License. -->
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 {
font: 12.5px sans-serif;
font: 12px sans-serif;
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;
div {
height: 30px;
line-height: 30px;
text-align: left;
padding: 0 15px;
}
.time-charts-item {
display: inline-block;
padding: 2px 8px;
border: 1px solid;
font-size: 11px;
border-radius: 4px;
div:hover {
color: $active-color;
background-color: $popper-hover-bg-color;
}
.dialog-c-text {
white-space: pre;
overflow: auto;
font-family: monospace;
}
</style>

View File

@ -87,7 +87,14 @@ limitations under the License. -->
{{ t("relatedTraceLogs") }}
</el-button>
</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 class="mb-10">
<span class="grey title">Name:</span>
@ -115,7 +122,14 @@ limitations under the License. -->
</div>
</div>
</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
v-model="pageNum"
:page-size="pageSize"
@ -295,4 +309,10 @@ limitations under the License. -->
.link {
text-decoration: underline;
}
.log-tips {
width: 100%;
text-align: center;
margin: 50px 0;
}
</style>

View File

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

View File

@ -17,7 +17,7 @@ limitations under the License. -->
<div class="trace-t-loading" v-show="loading">
<Icon iconName="spinner" size="sm" />
</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>
</TableContainer>
</div>
@ -28,13 +28,14 @@ limitations under the License. -->
import TableContainer from "./Table/TableContainer.vue";
import traceTable from "../utils/trace-table";
import type { StatisticsSpan, Span, StatisticsGroupRef } from "@/types/trace";
import { TraceGraphType } from "./constant";
/* 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: "" },
headerType: { type: String, default: "" },
});
const emit = defineEmits(["load"]);
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. -->
<template>
<div class="trace">
<div class="trace-header" v-if="type === 'statistics'">
<div class="trace-table">
<div class="trace-table-header" v-if="type === TraceGraphType.STATISTICS">
<div :class="item.label" v-for="(item, index) in headerData" :key="index">
{{ item.value }}
<span
@ -28,7 +28,7 @@ limitations under the License. -->
</span>
</div>
</div>
<div class="trace-header" v-else>
<div class="trace-table-header" v-else>
<div class="method" :style="`width: ${method}px`">
<span class="cp dragger" ref="dragger">
<Icon iconName="settings_ethernet" size="sm" />
@ -44,10 +44,10 @@ limitations under the License. -->
:traceId="traceId"
v-for="(item, index) in tableData"
:data="item"
:key="'key' + index"
:key="`key${index}`"
:type="type"
:headerType="headerType"
@select="selectItem"
@click="selectItem"
/>
<slot></slot>
</div>
@ -55,9 +55,11 @@ limitations under the License. -->
<script lang="ts" setup>
import { ref, onMounted } 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 { ProfileConstant, TraceConstant, StatisticsConstant } from "./data";
import { TraceGraphType } from "../constant";
import { WidgetType } from "@/views/dashboard/data";
/* global defineProps, Nullable, defineEmits, Recordable*/
const props = defineProps({
@ -67,20 +69,21 @@ limitations under the License. -->
traceId: { type: String, default: "" },
});
const emits = defineEmits(["select"]);
const traceStore = useTraceStore();
const method = ref<number>(300);
const componentKey = ref<number>(300);
const flag = ref<boolean>(true);
const dragger = ref<Nullable<HTMLSpanElement>>(null);
let headerData: Recordable[] = TraceConstant;
if (props.headerType === "profile") {
if (props.headerType === WidgetType.Profile) {
headerData = ProfileConstant;
}
if (props.type === "statistics") {
if (props.type === TraceGraphType.STATISTICS) {
headerData = StatisticsConstant;
}
onMounted(() => {
if (props.type === "statistics") {
if (props.type === TraceGraphType.STATISTICS) {
return;
}
const drag: Nullable<HTMLSpanElement> = dragger.value;
@ -101,8 +104,24 @@ limitations under the License. -->
};
};
});
function selectItem(span: Span) {
emits("select", span);
function selectItem(event: MouseEvent) {
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) {
const element = props.tableData;
@ -152,7 +171,7 @@ limitations under the License. -->
<style lang="scss" scoped>
@import url("./table.scss");
.trace {
.trace-table {
font-size: $font-size-smaller;
height: 100%;
overflow: auto;
@ -163,7 +182,7 @@ limitations under the License. -->
float: right;
}
.trace-header {
.trace-table-header {
white-space: nowrap;
user-select: none;
border-left: 0;
@ -171,7 +190,7 @@ limitations under the License. -->
border-bottom: 1px solid var(--sw-trace-list-border);
}
.trace-header div {
.trace-table-header div {
display: inline-block;
background-color: var(--sw-table-header);
padding: 0 4px;

View File

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

View File

@ -35,7 +35,7 @@ limitations under the License. -->
</a>
</div>
<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>
</template>
@ -46,6 +46,7 @@ limitations under the License. -->
import type { Span } from "@/types/trace";
import { useI18n } from "vue-i18n";
import { ref, computed } from "vue";
import { TraceGraphType } from "./constant";
/* global defineProps */
const props = defineProps({

View File

@ -14,18 +14,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface HexagonCreateParams {
hexagonParam: number[];
count: number;
radius: number;
origin?: number[];
getShader?: any;
}
export interface HexagonGeo {
vertices: number[];
normals: number[];
insCenters: number[];
indices: number[];
origins: number[];
export enum TraceGraphType {
TREE = "Tree",
LIST = "List",
TABLE = "Table",
STATISTICS = "Statistics",
}

View File

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

View File

@ -42,16 +42,17 @@ export default class ListGraph {
private xAxis: any = null;
private sequentialScale: any = null;
private root: any = null;
private selectedNode: any = null;
constructor(el: HTMLDivElement, handleSelectSpan: (i: Trace) => void) {
this.handleSelectSpan = handleSelectSpan;
this.el = el;
this.width = el.getBoundingClientRect().width - 10;
this.height = el.getBoundingClientRect().height - 10;
d3.select(".trace-list-dowanload").remove();
d3.select(`.${this.el?.className} .trace-list`).remove();
this.svg = d3
.select(this.el)
.append("svg")
.attr("class", "trace-list-dowanload")
.attr("class", "trace-list")
.attr("width", this.width > 0 ? this.width : 10)
.attr("height", this.height > 0 ? this.height : 10)
.attr("transform", `translate(-5, 0)`);
@ -85,7 +86,8 @@ export default class ListGraph {
L${d.target.y} ${d.target.x - 5}`;
}
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.data = data;
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.x0 = 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) {
this.update(this.root, callback);
@ -142,19 +152,35 @@ export default class ListGraph {
.enter()
.append("g")
.attr("transform", `translate(${source.y0},${source.x0})`)
.attr("id", (d: Recordable) => `list-node-${d.id}`)
.attr("class", "trace-node")
.attr("style", "cursor: pointer")
.style("opacity", 0)
.on("mouseover", function (event: MouseEvent, d: Trace) {
t.tip.show(d, this);
})
.on("mouseout", function (event: MouseEvent, d: Trace) {
t.tip.hide(d, this);
})
.on("click", (event: MouseEvent, d: Trace) => {
if (this.handleSelectSpan) {
this.handleSelectSpan(d);
.on("click", function (event: MouseEvent, d: Trace & { id: string }) {
event.stopPropagation();
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
.append("rect")
@ -239,14 +265,14 @@ export default class ListGraph {
.attr("cx", (d: Recordable) => {
const events = d.data.attachedEvents;
if (events && events.length > 9) {
return 272;
return 273;
} else {
return 270;
}
})
.attr("cy", -5)
.attr("fill", "none")
.attr("stroke", appStore.theme === Themes.Dark ? "#666" : "#e66")
.attr("stroke", "#e66")
.style("opacity", (d: Recordable) => {
const events = d.data.attachedEvents;
if (events && events.length) {
@ -259,7 +285,7 @@ export default class ListGraph {
.append("text")
.attr("x", 267)
.attr("y", -1)
.attr("fill", appStore.theme === Themes.Dark ? "#666" : "#e66")
.attr("fill", "#e66")
.style("font-size", "10px")
.text((d: Recordable) => {
const events = d.data.attachedEvents;
@ -324,16 +350,6 @@ export default class ListGraph {
if (d.data.children.length === 0) return;
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.
node
.exit()
@ -381,6 +397,39 @@ export default class ListGraph {
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") {
return dayjs(date).format(pattern);
}

View File

@ -46,6 +46,7 @@ export default class TraceMap {
private topChildMax: number[] = [];
private topChildMin: number[] = [];
private nodeUpdate: Nullable<any> = null;
private selectedNode: any = null;
constructor(el: HTMLDivElement, handleSelectSpan: (i: Trace) => void) {
this.el = el;
@ -55,7 +56,7 @@ export default class TraceMap {
this.topChild = [];
this.width = el.clientWidth - 20;
this.height = el.clientHeight - 30;
d3.select(".d3-trace-tree").remove();
d3.select(`.${this.el?.className} .d3-trace-tree`).remove();
this.body = d3
.select(this.el)
.append("svg")
@ -80,6 +81,7 @@ export default class TraceMap {
this.svg.call(this.tip);
}
init(data: Recordable, row: Recordable) {
d3.select("#trace-action-box").style("display", "none");
this.treemap = d3.tree().size([row.length * 35, this.width]);
this.row = row;
this.data = data;
@ -124,24 +126,33 @@ export default class TraceMap {
this.update(this.root);
}
update(source: Recordable) {
const t = this;
const appStore = useAppStoreWithOut();
const that: any = this;
const treeData = this.treemap(this.root);
const nodes = treeData.descendants(),
links = treeData.descendants().slice(1);
const nodes = treeData.descendants();
const links = treeData.descendants().slice(1);
nodes.forEach(function (d: Recordable) {
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);
});
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
.enter()
.append("g")
.attr("class", "node")
.attr("class", "trace-node")
.attr("id", (d: Recordable) => `trace-node-${d.id}`)
.attr("cursor", "pointer")
.attr("transform", function () {
return "translate(" + source.y0 + "," + source.x0 + ")";
@ -165,9 +176,6 @@ export default class TraceMap {
if (_node.length) {
that.timeTip.hide(d, _node[0].children[1]);
}
})
.on("click", function (event: MouseEvent, d: Recordable) {
that.handleSelectSpan(d);
});
nodeEnter
.append("circle")
@ -208,15 +216,15 @@ export default class TraceMap {
nodeEnter
.append("circle")
.attr("class", "node")
.attr("r", 1e-6)
.style("fill", (d: Recordable) =>
d._children ? this.sequentialScale(this.list.indexOf(d.data.serviceCode)) : "#fff",
)
.attr("r", 2)
.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
.append("text")
.attr("class", "trace-node-text")
.attr("font-size", 11)
.attr("dy", "-0.5em")
.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,
)
.style("fill", (d: Recordable) =>
.attr("fill", (d: Recordable) =>
!d.data.isError ? (appStore.theme === Themes.Dark ? "#eee" : "#3d444f") : "#E54C17",
);
nodeEnter
.append("text")
.attr("class", "node-text")
.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("text-anchor", function (d: Recordable) {
return d.children || d._children ? "end" : "start";
})
.style("font-size", "10px")
.text(
(d: Recordable) => `${d.data.layer || ""}${d.data.component ? "-" + d.data.component : d.data.component || ""}`,
);
.text((d: Recordable) => {
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
.append("rect")
.attr("rx", 1)
@ -290,12 +301,37 @@ export default class TraceMap {
nodeUpdate
.select("circle.node")
.attr("r", 5)
.style("fill", (d: Recordable) =>
d._children ? this.sequentialScale(this.list.indexOf(d.data.serviceCode)) : "#fff",
)
.attr("cursor", "pointer")
.on("click", (event: any, d: Recordable) => {
.on("click", function (event: MouseEvent, d: Trace & { id: string }) {
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;
click(d);
});
@ -369,6 +405,39 @@ export default class TraceMap {
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() {
d3.selectAll(".time-inner").style("opacity", 1);
d3.selectAll(".time-inner-duration").style("opacity", 0);

View File

@ -15,42 +15,13 @@
* 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 {
public static buildTraceDataList(data: Span[]): string[] {
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[]> {
const result = new Map<string, Span[]>();
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 {
nodes.forEach((node: Span) => {
const groupRef = node.endpointName + ":" + node.type;

View File

@ -0,0 +1,47 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<el-tabs v-model="activeName" class="settings-tabs">
<el-tab-pane v-for="tab in SettingsTabs" :label="tab.label" :name="tab.value" :key="tab.value">
<component :is="TabPanes[tab.value]" />
</el-tab-pane>
</el-tabs>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { SettingsTabs } from "./data";
import ClusterNodes from "./components/ClusterNodes.vue";
import General from "./components/General.vue";
import TTL from "./components/TTL.vue";
import DebuggingConfigDump from "./components/DebuggingConfigDump.vue";
/*global Indexable*/
const TabPanes: Indexable = {
general: General,
ttl: TTL,
clusterNodes: ClusterNodes,
dumpEffectiveConfigurations: DebuggingConfigDump,
};
const activeName = ref(SettingsTabs[0].value);
</script>
<style lang="scss" scoped>
.settings-tabs {
padding: 10px 20px;
}
.settings-tabs > .el-tabs__content {
color: var(--sw-setting-color);
padding-top: 10px;
}
</style>

View File

@ -0,0 +1,71 @@
<!-- 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="cluster-nodes">
<el-table
:data="settingsStore.clusterNodes"
class="mb-5"
:row-style="{ backgroundColor: 'var(--layout-background)' }"
>
<el-table-column
v-for="item in ClusterNodeRow"
:prop="item.value"
:label="item.label"
:key="item.value"
:width="item.width"
/>
</el-table>
<el-pagination
class="pagination"
layout="prev, pager, next"
:page-size="pageSize"
:total="settingsStore.clusterNodes.length"
v-model="currentPage"
@current-change="changePage"
@prev-click="changePage"
@next-click="changePage"
/>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from "vue";
import { useSettingsStore } from "@/store/modules/settings";
import { ClusterNodeRow } from "../data";
const settingsStore = useSettingsStore();
const pageSize = 16;
const currentPage = ref<number>(1);
onMounted(() => {
settingsStore.getClusterNodes();
});
const changePage = (pageIndex: number) => {
currentPage.value = pageIndex;
};
</script>
<style lang="scss" scoped>
.cluster-nodes {
font-size: 13px;
width: 100%;
}
.label {
width: 200px;
display: inline-block;
font-weight: 500;
color: $font-color;
}
</style>

View File

@ -0,0 +1,70 @@
<!-- 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="debugging-config-dump">
<div class="config-dump-content">
<div
class="mb-10 flex-h"
v-for="(item, index) of Object.keys(settingsStore.debuggingConfig)"
:key="`${item}${index}`"
>
<div class="config-key">{{ item }}: </div>
<div>{{ settingsStore.debuggingConfig[item] }}</div>
</div>
<div v-if="!Object.keys(settingsStore.debuggingConfig).length" class="tips">No Data</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted } from "vue";
import { useSettingsStore } from "@/store/modules/settings";
const settingsStore = useSettingsStore();
onMounted(() => {
settingsStore.getDebuggingConfigDump();
});
</script>
<style lang="scss" scoped>
.config-dump-content {
border: 1px solid var(--el-color-info-light-8);
overflow: auto;
padding: 5px;
border-radius: 5px 3px;
height: 700px;
}
.tips {
text-align: center;
width: 100%;
color: var(--el-text-color-secondary);
}
.config-key {
width: 30%;
}
.debugging-config-dump {
color: var(--sw-setting-color);
font-size: 13px;
}
.label {
width: 200px;
display: inline-block;
font-weight: 500;
color: $font-color;
}
</style>

View File

@ -0,0 +1,181 @@
<!-- 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="general-settings">
<div class="flex-h item">
<div class="label">{{ t("language") }}</div>
<Selector
v-model="lang"
:options="Languages"
placeholder="Select a language"
@change="setLang"
size="small"
style="font-size: 14px; width: 180px"
/>
</div>
<div class="flex-h item">
<div class="label">{{ t("serverZone") }}</div>
<div>
<span>UTC</span>
<span class="ml-5 mr-5">{{ utcHour >= 0 ? "+" : "" }}</span>
<input type="number" v-model="utcHour" min="-12" max="14" class="utc-input" @change="setUTCHour" />
<span class="ml-5 mr-5">:</span>
<span class="utc-min">{{ utcMin > 9 || utcMin === 0 ? null : 0 }}</span>
<input type="number" v-model="utcMin" min="0" max="59" class="utc-input" @change="setUTCMin" />
</div>
</div>
<div class="flex-h item">
<div class="label">{{ t("auto") }}</div>
<el-switch v-model="auto" @change="handleAuto" style="height: 25px" />
<div class="auto-time ml-5">
<span class="auto-select">
<input type="number" v-model="autoTime" @change="changeAutoTime" min="1" />
</span>
{{ t("second") }}
<i class="ml-10">{{ t("timeReload") }}</i>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useAppStoreWithOut } from "@/store/modules/app";
import timeFormat from "@/utils/timeFormat";
import { Languages } from "@/constants/data";
import Selector from "@/components/Selector.vue";
import getLocalTime from "@/utils/localtime";
const { t, locale } = useI18n();
const appStore = useAppStoreWithOut();
const lang = ref<string>(locale.value || "en");
const autoTime = ref<number>(6);
const auto = ref<boolean>(appStore.autoRefresh || false);
const utcHour = ref<number>(appStore.utcHour);
const utcMin = ref<number>(appStore.utcMin);
const handleReload = () => {
const gap = appStore.duration.end.getTime() - appStore.duration.start.getTime();
const dates: Date[] = [
getLocalTime(appStore.utc, new Date(new Date().getTime() - gap)),
getLocalTime(appStore.utc, new Date()),
];
appStore.setDuration(timeFormat(dates));
};
const handleAuto = () => {
if (autoTime.value < 1) {
return;
}
appStore.setAutoRefresh(auto.value);
if (auto.value) {
handleReload();
appStore.setReloadTimer(setInterval(handleReload, autoTime.value * 1000));
} else {
if (appStore.reloadTimer) {
clearInterval(appStore.reloadTimer);
}
}
};
const changeAutoTime = () => {
if (autoTime.value < 1) {
return;
}
if (appStore.reloadTimer) {
clearInterval(appStore.reloadTimer);
}
if (auto.value) {
handleReload();
appStore.setReloadTimer(setInterval(handleReload, autoTime.value * 1000));
}
};
const setLang = (): void => {
locale.value = lang.value;
window.localStorage.setItem("language", lang.value);
};
const setUTCHour = () => {
if (utcHour.value < -12) {
utcHour.value = -12;
}
if (utcHour.value > 14) {
utcHour.value = 14;
}
if (isNaN(utcHour.value)) {
utcHour.value = 0;
}
appStore.setUTC(utcHour.value, utcMin.value);
};
const setUTCMin = () => {
if (utcMin.value < 0) {
utcMin.value = 0;
}
if (utcMin.value > 59) {
utcMin.value = 59;
}
if (isNaN(utcMin.value)) {
utcMin.value = 0;
}
appStore.setUTC(utcHour.value, utcMin.value);
};
</script>
<style lang="scss" scoped>
.utc-input {
color: inherit;
background: 0;
border: 0;
outline: none;
padding-bottom: 0;
}
.utc-min {
display: inline-block;
padding-top: 2px;
}
.auto-select {
border-radius: 3px;
background-color: $theme-background;
padding: 1px;
input {
width: 38px;
border-style: unset;
outline: 0;
}
}
.general-settings {
font-size: 13px;
.item {
margin-top: 15px;
}
input {
outline: 0;
width: 50px;
border-radius: 3px;
border: 1px solid $disabled-color;
text-align: center;
height: 25px;
}
.label {
width: 200px;
display: inline-block;
color: $font-color;
font-weight: 500;
}
}
</style>

View File

@ -0,0 +1,49 @@
<!-- 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="ttl">
<div class="label">{{ t("metricsTTL") }}</div>
<TTLTable :data="settingsStore.configTTL.metrics" :metricsRows="MetricsTTLRow" />
<div class="label">{{ t("recordsTTL") }}</div>
<TTLTable :data="settingsStore.configTTL.records" :metricsRows="RecordsTTLRow" />
</div>
</template>
<script lang="ts" setup>
import { onMounted } from "vue";
import { useI18n } from "vue-i18n";
import { useSettingsStore } from "@/store/modules/settings";
import TTLTable from "./TTLTable.vue";
import { MetricsTTLRow, RecordsTTLRow } from "../data";
const { t } = useI18n();
const settingsStore = useSettingsStore();
onMounted(() => {
settingsStore.getConfigTTL();
});
</script>
<style lang="scss" scoped>
.ttl {
color: var(--sw-setting-color);
font-size: 13px;
.label {
margin: 15px 0;
display: inline-block;
font-weight: 600;
color: $font-color;
}
}
</style>

View File

@ -0,0 +1,44 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<el-table
:data="data"
class="mb-5"
:row-style="{ backgroundColor: 'var(--layout-background)' }"
:cell-style="(data: Indexable) => (data.columnIndex === 0 ? { backgroundColor: 'var(--el-table-header-bg-color)' } : {})"
>
<el-table-column
v-for="item in metricsRows"
:prop="item.value"
:label="item.label"
:key="item.value"
:width="item.width"
>
<template #default="scope">
{{ scope.row && scope.row[item.value] ? (scope.row[item.value] < 0 ? "N/A" : scope.row[item.value]) : "N/A" }}
</template>
</el-table-column>
</el-table>
</template>
<script lang="ts" setup>
/*global PropType, Indexable */
defineProps({
metricsRows: {
type: Array as PropType<{ width?: number; value: string; label: string }[]>,
default: () => [],
},
data: { type: Array as PropType<Indexable[]>, default: () => [] },
});
</script>

101
src/views/settings/data.ts Normal file
View File

@ -0,0 +1,101 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const ClusterNodeRow = [
{
label: "Host",
value: "host",
},
{
label: "Port",
value: "port",
width: 180,
},
{
label: "Self",
value: "self",
width: 180,
},
];
export const SettingsTabs = [
{
label: "General",
value: "general",
},
{
label: "TTL",
value: "ttl",
},
{
label: "Cluster Nodes",
value: "clusterNodes",
},
{
label: "Dump Effective Configurations",
value: "dumpEffectiveConfigurations",
},
];
export const HotAndWarmOpt = ["day", "hour", "minute", "normal", "trace", "zipkinTrace", "log", "browserErrorLog"];
export const MetricsTTLRow = [
{
label: "Type",
value: "type",
width: 260,
},
{
label: "Day",
value: HotAndWarmOpt[0],
},
{
label: "Hour",
value: HotAndWarmOpt[1],
},
{
label: "Minute",
value: HotAndWarmOpt[2],
},
];
export const RecordsTTLRow = [
{
label: "Type",
value: "type",
width: 260,
},
{
label: "Normal",
value: HotAndWarmOpt[3],
},
{
label: "Trace",
value: HotAndWarmOpt[4],
},
{
label: "Zipkin Trace",
value: HotAndWarmOpt[5],
},
{
label: "Log",
value: HotAndWarmOpt[6],
},
{
label: "Browser Error Log",
value: HotAndWarmOpt[7],
},
];

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

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

View File

@ -75,6 +75,11 @@ export default ({ mode }: ConfigEnv): UserConfig => {
target: `${VITE_SW_PROXY_TARGET || "http://127.0.0.1:12800"}`,
changeOrigin: true,
},
"/api": {
target: `${VITE_SW_PROXY_TARGET || "http://127.0.0.1:12800"}`,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
build: {