mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-09-20 13:09:27 +00:00
Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d65c18bd38 | ||
![]() |
a5b0acda06 | ||
![]() |
e251626374 | ||
![]() |
ed0ec0ac1f | ||
![]() |
1945f23419 | ||
![]() |
d10f4ca0cc | ||
![]() |
a5073dd3d4 | ||
![]() |
ddcc49cb42 | ||
![]() |
ae63538baf | ||
![]() |
d9f819d143 | ||
![]() |
3c8b316b76 | ||
![]() |
6b2b6a5dd2 | ||
![]() |
4e00073ec2 | ||
![]() |
8f179f00a2 | ||
![]() |
fe6e853c57 | ||
![]() |
f664e786ac | ||
![]() |
b6f57aa54e | ||
![]() |
afb70a371b | ||
![]() |
c35bdce399 | ||
![]() |
5419a69700 | ||
![]() |
065337c344 | ||
![]() |
21fe455fd6 | ||
![]() |
88dbee311c | ||
![]() |
9001a96128 |
@@ -17,35 +17,10 @@
|
||||
|
||||
module.exports = {
|
||||
ignores: [(commit) => commit.includes("init")],
|
||||
extends: ["@commitlint/config-conventional"],
|
||||
rules: {
|
||||
"body-leading-blank": [2, "always"],
|
||||
"footer-leading-blank": [1, "always"],
|
||||
"header-max-length": [2, "always", 108],
|
||||
"subject-empty": [2, "never"],
|
||||
"type-empty": [2, "never"],
|
||||
"subject-case": [0],
|
||||
"type-enum": [
|
||||
2,
|
||||
"always",
|
||||
[
|
||||
"feat",
|
||||
"fix",
|
||||
"perf",
|
||||
"style",
|
||||
"docs",
|
||||
"test",
|
||||
"refactor",
|
||||
"build",
|
||||
"ci",
|
||||
"chore",
|
||||
"revert",
|
||||
"wip",
|
||||
"workflow",
|
||||
"types",
|
||||
"release",
|
||||
"merge",
|
||||
],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
3437
package-lock.json
generated
3437
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@@ -18,7 +18,7 @@
|
||||
"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.6.0",
|
||||
"axios": "^1.7.5",
|
||||
"d3": "^7.3.0",
|
||||
"d3-flame-graph": "^4.1.3",
|
||||
"d3-tip": "^0.9.1",
|
||||
@@ -65,20 +65,20 @@
|
||||
"postcss-scss": "^4.0.2",
|
||||
"prettier": "^2.7.1",
|
||||
"sass": "^1.56.1",
|
||||
"start-server-and-test": "^1.15.2",
|
||||
"start-server-and-test": "^2.0.5",
|
||||
"stylelint": "15.9.0",
|
||||
"stylelint-config-html": "^1.0.0",
|
||||
"stylelint-config-prettier": "9.0.4",
|
||||
"stylelint-config-standard": "^33.0.0",
|
||||
"stylelint-order": "^6.0.3",
|
||||
"typescript": "~4.7.4",
|
||||
"unplugin-auto-import": "^0.7.0",
|
||||
"unplugin-vue-components": "^0.19.2",
|
||||
"unplugin-auto-import": "^0.18.2",
|
||||
"unplugin-vue-components": "^0.27.3",
|
||||
"vite": "^4.5.3",
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vitest": "^0.25.6",
|
||||
"vue-tsc": "^1.0.12"
|
||||
"vue-tsc": "^1.8.27"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
@@ -98,10 +98,7 @@
|
||||
"{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": [
|
||||
"prettier --write"
|
||||
],
|
||||
"package.json": [
|
||||
"prettier --write"
|
||||
],
|
||||
"*.md": [
|
||||
"package.json, *.md": [
|
||||
"prettier --write"
|
||||
]
|
||||
}
|
||||
|
16
src/assets/icons/cilium.svg
Normal file
16
src/assets/icons/cilium.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<!-- 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="16" height="16" viewBox="20 0 70 72" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="m49.72 45.923-5.505-9.69 5.505-9.69h10.974l5.506 9.69-5.506 9.69H49.72ZM49.72 69.367l-5.505-9.69 5.505-9.689h10.974l5.506 9.69-5.506 9.69H49.72ZM49.72 22.477l-5.505-9.689 5.505-9.69h10.974l5.506 9.69-5.506 9.69H49.72ZM70.06 57.644l-5.506-9.69 5.506-9.69h10.974l5.506 9.69-5.506 9.69H70.06ZM70.06 34.2l-5.506-9.69 5.506-9.69h10.974l5.506 9.69-5.506 9.69H70.06ZM29.357 57.644l-5.506-9.69 5.506-9.69h10.974l5.506 9.69-5.506 9.69H29.357ZM29.357 34.2l-5.506-9.69 5.506-9.69h10.974l5.506 9.69-5.506 9.69H29.357Z" stroke="#141A1F" stroke-width="2.771"/><path d="M10.784 95.947c1.026.007 " fill="#141A1F"/></svg>
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src/assets/img/technologies/SOLONMVC.png
Normal file
BIN
src/assets/img/technologies/SOLONMVC.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
BIN
src/assets/img/tools/CILIUM_SERVICE.png
Normal file
BIN
src/assets/img/tools/CILIUM_SERVICE.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
@@ -28,8 +28,8 @@ limitations under the License. -->
|
||||
:filterable="filterable"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value || ''"
|
||||
v-for="(item, index) in options"
|
||||
:key="`${item.value}${index}`"
|
||||
:label="item.label || ''"
|
||||
:value="item.value || ''"
|
||||
:disabled="item.disabled || false"
|
||||
|
@@ -46,3 +46,14 @@ export const Alarm = {
|
||||
}
|
||||
}`,
|
||||
};
|
||||
export const AlarmTagKeys = {
|
||||
variable: "$duration: Duration!",
|
||||
query: `
|
||||
tagKeys: queryAlarmTagAutocompleteKeys(duration: $duration)`,
|
||||
};
|
||||
|
||||
export const AlarmTagValues = {
|
||||
variable: "$tagKey: String!, $duration: Duration!",
|
||||
query: `
|
||||
tagValues: queryAlarmTagAutocompleteValues(tagKey: $tagKey, duration: $duration)`,
|
||||
};
|
||||
|
@@ -15,6 +15,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Alarm } from "../fragments/alarm";
|
||||
import { Alarm, AlarmTagKeys, AlarmTagValues } from "../fragments/alarm";
|
||||
|
||||
export const queryAlarms = `query queryAlarms(${Alarm.variable}) {${Alarm.query}}`;
|
||||
export const queryAlarmTagValues = `query queryTagValues(${AlarmTagValues.variable}) {${AlarmTagValues.query}}`;
|
||||
export const queryAlarmTagKeys = `query queryTagKeys(${AlarmTagKeys.variable}) {${AlarmTagKeys.query}}`;
|
||||
|
@@ -14,6 +14,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export const MaximumEntities = 20;
|
||||
|
||||
export enum sizeEnum {
|
||||
XS = "XS",
|
||||
|
@@ -14,7 +14,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { RespFields } from "./data";
|
||||
import { RespFields, MaximumEntities } from "./data";
|
||||
import { EntityType, ExpressionResultType } from "@/views/dashboard/data";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
@@ -24,31 +24,27 @@ import type { MetricConfigOpt } from "@/types/dashboard";
|
||||
import type { Instance, Endpoint, Service } from "@/types/selector";
|
||||
import type { Node, Call } from "@/types/topology";
|
||||
|
||||
export async function useExpressionsQueryProcessor(config: Indexable) {
|
||||
function expressionsGraphqlPods() {
|
||||
export async function useDashboardQueryProcessor(configList: Indexable[]) {
|
||||
function expressionsGraphql(config: Indexable, idx: number) {
|
||||
if (!(config.metrics && config.metrics[0])) {
|
||||
return;
|
||||
}
|
||||
const appStore = useAppStoreWithOut();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const selectorStore = useSelectorStore();
|
||||
|
||||
if (!selectorStore.currentService && dashboardStore.entity !== "All") {
|
||||
return;
|
||||
}
|
||||
const conditions: Recordable = {
|
||||
duration: appStore.durationTime,
|
||||
};
|
||||
const variables: string[] = [`$duration: Duration!`];
|
||||
const conditions: Recordable = {};
|
||||
const variables: string[] = [];
|
||||
const isRelation = ["ServiceRelation", "ServiceInstanceRelation", "EndpointRelation", "ProcessRelation"].includes(
|
||||
dashboardStore.entity,
|
||||
);
|
||||
if (isRelation && !selectorStore.currentDestService) {
|
||||
return;
|
||||
}
|
||||
const fragment = config.metrics.map((name: string, index: number) => {
|
||||
variables.push(`$expression${index}: String!`, `$entity${index}: Entity!`);
|
||||
conditions[`expression${index}`] = name;
|
||||
if (idx === 0) {
|
||||
variables.push(`$entity: Entity!`);
|
||||
const entity = {
|
||||
serviceName: dashboardStore.entity === "All" ? undefined : selectorStore.currentService.value,
|
||||
normal: dashboardStore.entity === "All" ? undefined : selectorStore.currentService.normal,
|
||||
@@ -76,19 +72,21 @@ export async function useExpressionsQueryProcessor(config: Indexable) {
|
||||
? selectorStore.currentDestProcess && selectorStore.currentDestProcess.value
|
||||
: undefined,
|
||||
};
|
||||
conditions[`entity${index}`] = entity;
|
||||
conditions[`entity`] = entity;
|
||||
}
|
||||
const fragment = config.metrics.map((name: string, index: number) => {
|
||||
variables.push(`$expression${idx}${index}: String!`);
|
||||
conditions[`expression${idx}${index}`] = name;
|
||||
|
||||
return `expression${index}: execExpression(expression: $expression${index}, entity: $entity${index}, duration: $duration)${RespFields.execExpression}`;
|
||||
return `expression${idx}${index}: execExpression(expression: $expression${idx}${index}, entity: $entity, duration: $duration)${RespFields.execExpression}`;
|
||||
});
|
||||
const queryStr = `query queryData(${variables}) {${fragment}}`;
|
||||
|
||||
return {
|
||||
queryStr,
|
||||
variables,
|
||||
fragment,
|
||||
conditions,
|
||||
};
|
||||
}
|
||||
|
||||
function expressionsSource(resp: { errors: string; data: Indexable }) {
|
||||
function expressionsSource(config: Indexable, resp: { errors: string; data: Indexable | any }) {
|
||||
if (resp.errors) {
|
||||
ElMessage.error(resp.errors);
|
||||
return { source: {}, tips: [], typesOfMQE: [] };
|
||||
@@ -97,6 +95,10 @@ export async function useExpressionsQueryProcessor(config: Indexable) {
|
||||
ElMessage.error("The query is wrong");
|
||||
return { source: {}, tips: [], typesOfMQE: [] };
|
||||
}
|
||||
if (resp.data.error) {
|
||||
ElMessage.error(resp.data.error);
|
||||
return { source: {}, tips: [], typesOfMQE: [] };
|
||||
}
|
||||
const tips: string[] = [];
|
||||
const source: { [key: string]: unknown } = {};
|
||||
const keys = Object.keys(resp.data);
|
||||
@@ -133,30 +135,76 @@ export async function useExpressionsQueryProcessor(config: Indexable) {
|
||||
|
||||
return { source, tips, typesOfMQE };
|
||||
}
|
||||
|
||||
const params = await expressionsGraphqlPods();
|
||||
if (!params) {
|
||||
return { source: {}, tips: [], typesOfMQE: [] };
|
||||
async function fetchMetrics(configArr: any) {
|
||||
const appStore = useAppStoreWithOut();
|
||||
const variables: string[] = [`$duration: Duration!`];
|
||||
let fragments = "";
|
||||
let conditions: Recordable = {
|
||||
duration: appStore.durationTime,
|
||||
};
|
||||
for (let i = 0; i < configArr.length; i++) {
|
||||
const params = await expressionsGraphql(configArr[i], i);
|
||||
if (params) {
|
||||
fragments += params?.fragment;
|
||||
conditions = { ...conditions, ...params.conditions };
|
||||
variables.push(...params.variables);
|
||||
}
|
||||
|
||||
}
|
||||
if (!fragments) {
|
||||
return { 0: { source: {}, tips: [], typesOfMQE: [] } };
|
||||
}
|
||||
const queryStr = `query queryData(${variables}) {${fragments}}`;
|
||||
const dashboardStore = useDashboardStore();
|
||||
const json = await dashboardStore.fetchMetricValue(params);
|
||||
const json = await dashboardStore.fetchMetricValue({
|
||||
queryStr,
|
||||
conditions,
|
||||
});
|
||||
if (json.errors) {
|
||||
ElMessage.error(json.errors);
|
||||
return { source: {}, tips: [], typesOfMQE: [] };
|
||||
return { 0: { source: {}, tips: [], typesOfMQE: [] } };
|
||||
}
|
||||
try {
|
||||
const data = expressionsSource(json);
|
||||
const pageData: Recordable = {};
|
||||
|
||||
return data;
|
||||
for (let i = 0; i < configArr.length; i++) {
|
||||
const resp: any = {};
|
||||
for (let m = 0; m < configArr[i].metrics.length; m++) {
|
||||
resp[`expression${i}${m}`] = json.data[`expression${i}${m}`];
|
||||
}
|
||||
const data = expressionsSource(configArr[i], { ...json, data: resp });
|
||||
const id = configArr[i].id;
|
||||
pageData[id] = data;
|
||||
}
|
||||
return pageData;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return { source: {}, tips: [], typesOfMQE: [] };
|
||||
return { 0: { source: {}, tips: [], typesOfMQE: [] } };
|
||||
}
|
||||
}
|
||||
function chunkArray(array: any[], chunkSize: number) {
|
||||
const result = [];
|
||||
for (let i = 0; i < array.length; i += chunkSize) {
|
||||
result.push(array.slice(i, i + chunkSize));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const partArr = chunkArray(configList, 6);
|
||||
const promiseArr = partArr.map((d: Array<Indexable>) => fetchMetrics(d));
|
||||
const responseList = await Promise.all(promiseArr);
|
||||
let resp = {};
|
||||
for (const item of responseList) {
|
||||
resp = {
|
||||
...resp,
|
||||
...item,
|
||||
};
|
||||
}
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
export async function useExpressionsQueryPodsMetrics(
|
||||
pods: Array<(Instance | Endpoint | Service) & Indexable>,
|
||||
allPods: Array<(Instance | Endpoint | Service) & Indexable>,
|
||||
config: {
|
||||
expressions: string[];
|
||||
subExpressions: string[];
|
||||
@@ -164,7 +212,7 @@ export async function useExpressionsQueryPodsMetrics(
|
||||
},
|
||||
scope: string,
|
||||
) {
|
||||
function expressionsGraphqlPods() {
|
||||
function expressionsGraphqlPods(pods: Array<(Instance | Endpoint | Service) & Indexable>) {
|
||||
const metrics: string[] = [];
|
||||
const subMetrics: string[] = [];
|
||||
config.expressions = config.expressions || [];
|
||||
@@ -196,18 +244,22 @@ export async function useExpressionsQueryPodsMetrics(
|
||||
variables.push(`$entity${index}: Entity!`);
|
||||
conditions[`entity${index}`] = entity;
|
||||
const f = metrics.map((name: string, idx: number) => {
|
||||
variables.push(`$expression${index}${idx}: String!`);
|
||||
conditions[`expression${index}${idx}`] = name;
|
||||
if (index === 0) {
|
||||
variables.push(`$expression${idx}: String!`);
|
||||
conditions[`expression${idx}`] = name;
|
||||
}
|
||||
let str = "";
|
||||
if (config.subExpressions[idx]) {
|
||||
variables.push(`$subExpression${index}${idx}: String!`);
|
||||
conditions[`subExpression${index}${idx}`] = config.subExpressions[idx];
|
||||
str = `subexpression${index}${idx}: execExpression(expression: $subExpression${index}${idx}, entity: $entity${index}, duration: $duration)${RespFields.execExpression}`;
|
||||
if (index === 0) {
|
||||
variables.push(`$subExpression${idx}: String!`);
|
||||
conditions[`subExpression${idx}`] = config.subExpressions[idx];
|
||||
}
|
||||
str = `subexpression${index}${idx}: execExpression(expression: $subExpression${idx}, entity: $entity${index}, duration: $duration)${RespFields.execExpression}`;
|
||||
}
|
||||
|
||||
return (
|
||||
str +
|
||||
`expression${index}${idx}: execExpression(expression: $expression${index}${idx}, entity: $entity${index}, duration: $duration)${RespFields.execExpression}`
|
||||
`expression${index}${idx}: execExpression(expression: $expression${idx}, entity: $entity${index}, duration: $duration)${RespFields.execExpression}`
|
||||
);
|
||||
});
|
||||
return f;
|
||||
@@ -218,7 +270,10 @@ export async function useExpressionsQueryPodsMetrics(
|
||||
return { queryStr, conditions };
|
||||
}
|
||||
|
||||
function expressionsPodsSource(resp: { errors: string; data: Indexable }): Indexable {
|
||||
function expressionsPodsSource(
|
||||
resp: { errors: string; data: Indexable },
|
||||
pods: Array<(Instance | Endpoint | Service) & Indexable>,
|
||||
): Indexable {
|
||||
if (resp.errors) {
|
||||
ElMessage.error(resp.errors);
|
||||
return {};
|
||||
@@ -300,17 +355,39 @@ export async function useExpressionsQueryPodsMetrics(
|
||||
return { data, names, subNames, metricConfigArr, metricTypesArr, expressionsTips, subExpressionsTips };
|
||||
}
|
||||
|
||||
async function fetchPodsExpressionValues(pods: Array<(Instance | Endpoint | Service) & Indexable>) {
|
||||
const dashboardStore = useDashboardStore();
|
||||
const params = await expressionsGraphqlPods();
|
||||
const params = await expressionsGraphqlPods(pods);
|
||||
|
||||
const json = await dashboardStore.fetchMetricValue(params);
|
||||
|
||||
if (json.errors) {
|
||||
ElMessage.error(json.errors);
|
||||
return {};
|
||||
}
|
||||
const expressionParams = expressionsPodsSource(json);
|
||||
const expressionParams = expressionsPodsSource(json, pods);
|
||||
|
||||
return expressionParams;
|
||||
}
|
||||
|
||||
const result = [];
|
||||
for (let i = 0; i < allPods.length; i += MaximumEntities) {
|
||||
result.push(allPods.slice(i, i + MaximumEntities));
|
||||
}
|
||||
const promiseArr = result.map((d: Array<(Instance | Endpoint | Service) & Indexable>) =>
|
||||
fetchPodsExpressionValues(d),
|
||||
);
|
||||
const responseList = await Promise.all(promiseArr);
|
||||
let resp: Indexable = { data: [], expressionsTips: [], subExpressionsTips: [] };
|
||||
for (const item of responseList) {
|
||||
resp = {
|
||||
...item,
|
||||
data: [...resp.data, ...item.data],
|
||||
expressionsTips: [...resp.expressionsTips, ...item.expressionsTips],
|
||||
subExpressionsTips: [...resp.subExpressionsTips, ...item.subExpressionsTips],
|
||||
};
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
export function useQueryTopologyExpressionsProcessor(metrics: string[], instances: (Call | Node)[]) {
|
||||
|
@@ -284,7 +284,8 @@ const msg = {
|
||||
errorInfo: "Error Info",
|
||||
stack: "Stack",
|
||||
serviceVersion: "Service Version",
|
||||
errorPage: "Error Page",
|
||||
pagePath: "Page Path",
|
||||
errorUrl: "Error Url",
|
||||
category: "Category",
|
||||
grade: "Grade",
|
||||
relatedTraceLogs: "Related Logs",
|
||||
@@ -300,7 +301,7 @@ const msg = {
|
||||
viewLogs: "View Logs",
|
||||
logsTagsTip: `Only tags defined in the core/default/searchableLogsTags are searchable.
|
||||
Check more details on the Configuration Vocabulary page`,
|
||||
keywordsOfContentLogTips: "Current storage of SkyWalking OAP server does not support this.",
|
||||
keywordsOfContentLogTips: "Current storage of SkyWalking OAP server does not support this",
|
||||
setEvent: "Set Event",
|
||||
viewAttributes: "View",
|
||||
attributes: "Attributes",
|
||||
@@ -355,7 +356,7 @@ const msg = {
|
||||
addKeywordsOfContent: "Please input a keyword of content",
|
||||
addExcludingKeywordsOfContent: "Please input a keyword of excluding content",
|
||||
noticeTag: "Please press Enter after inputting a tag(key=value).",
|
||||
conditionNotice: "Notice: Please press Enter after inputting a key of content, exclude key of content(key=value).",
|
||||
conditionNotice: "Notice: Please press Enter after inputting a key of content, exclude key of content(key=value)",
|
||||
language: "Language",
|
||||
save: "Save",
|
||||
editStrategy: "Edit Policies",
|
||||
|
@@ -283,7 +283,8 @@ const msg = {
|
||||
errorInfo: "Info Error",
|
||||
stack: "Pila",
|
||||
serviceVersion: "Versión Servicio",
|
||||
errorPage: "Página de Error",
|
||||
pagePath: "Página de Error",
|
||||
errorUrl: "Ruta de Error",
|
||||
category: "Categoría",
|
||||
grade: "Grado",
|
||||
relatedTraceLogs: "Registro de Datos Relacionados",
|
||||
@@ -299,7 +300,7 @@ const msg = {
|
||||
viewLogs: "Ver Registro de Datos",
|
||||
logsTagsTip: `Solamente etiquetas definidas en core/default/searchableLogsTags pueden ser buscadas.
|
||||
Más información en la página de Vocabulario de Configuración`,
|
||||
keywordsOfContentLogTips: "El almacenamiento actual del servidor SkyWalking OAP no lo soporta.",
|
||||
keywordsOfContentLogTips: "El almacenamiento actual del servidor SkyWalking OAP no lo soporta",
|
||||
setEvent: "Establecer Evento",
|
||||
viewAttributes: "Ver",
|
||||
serviceEvents: "Eventos Servico",
|
||||
|
@@ -125,6 +125,14 @@ const titles = {
|
||||
self_observability_satellite: "Satellite",
|
||||
self_observability_satellite_desc:
|
||||
"Satellite: an open-source agent designed for the cloud-native infrastructures, which provides a low-cost, high-efficient, and more secure way to collect telemetry data. It is the recommended load balancer for telemetry collecting.",
|
||||
self_observability_java_agent: "SkyWalking Java Agent",
|
||||
self_observability_java_agent_desc:
|
||||
"The self observability of SkyWalking Java Agent, which provides the abilities to measure the tracing performance and error statistics of plugins.",
|
||||
cilium: "Cilium",
|
||||
cilium_desc:
|
||||
"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.",
|
||||
};
|
||||
|
||||
export default titles;
|
||||
|
@@ -126,6 +126,14 @@ const titles = {
|
||||
self_observability_satellite: "Satellite",
|
||||
self_observability_satellite_desc:
|
||||
"Satellite: an open-source agent designed for the cloud-native infrastructures, which provides a low-cost, high-efficient, and more secure way to collect telemetry data. It is the recommended load balancer for telemetry collecting.",
|
||||
self_observability_java_agent: "SkyWalking Java Agent",
|
||||
self_observability_java_agent_desc:
|
||||
"The self observability of SkyWalking Java Agent, which provides the abilities to measure the tracing performance and error statistics of plugins.",
|
||||
cilium: "Cilium",
|
||||
cilium_desc:
|
||||
"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.",
|
||||
};
|
||||
|
||||
export default titles;
|
||||
|
@@ -111,6 +111,12 @@ const titles = {
|
||||
self_observability_satellite: "Satellite",
|
||||
self_observability_satellite_desc:
|
||||
"Satellite:为云原生基础设施设计的开源代理,提供了一种低成本、高效、更安全的遥测数据收集方式。它是遥测采集的推荐负载均衡器。",
|
||||
self_observability_java_agent: "SkyWalking Java Agent",
|
||||
self_observability_java_agent_desc: "SkyWalking Java Agent 自监控提供了对 agent 插件的性能追踪和错误统计。",
|
||||
cilium: "Cilium",
|
||||
cilium_desc: "Cilium是Kubernetes上的CNI插件,提供基于eBPF的网络、安全和负载均衡。",
|
||||
cilium_service: "Cilium服务",
|
||||
cilium_service_desc: "通过Cilium Hubble收集的遥测数据观察服务。",
|
||||
};
|
||||
|
||||
export default titles;
|
||||
|
@@ -281,7 +281,8 @@ const msg = {
|
||||
errorInfo: "错误信息",
|
||||
stack: "堆栈",
|
||||
serviceVersion: "服务版本",
|
||||
errorPage: "错误页面",
|
||||
pagePath: "错误页面",
|
||||
errorUrl: "错误路径",
|
||||
category: "类别",
|
||||
grade: "等级",
|
||||
relatedTraceLogs: "相关的日志",
|
||||
|
@@ -15,6 +15,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { createApp } from "vue";
|
||||
import { ElLoading } from "element-plus";
|
||||
import App from "./App.vue";
|
||||
import { store } from "./store";
|
||||
import components from "@/components";
|
||||
@@ -23,6 +24,11 @@ import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import "./styles/index.ts";
|
||||
import "virtual:svg-icons-register";
|
||||
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: "Loading...",
|
||||
background: "rgba(0, 0, 0, 0.8)",
|
||||
});
|
||||
const app = createApp(App);
|
||||
const appStore = useAppStoreWithOut();
|
||||
|
||||
@@ -34,7 +40,7 @@ mountApp();
|
||||
async function mountApp() {
|
||||
await appStore.getActivateMenus();
|
||||
await appStore.queryOAPTimeInfo();
|
||||
|
||||
const router = await import("./router");
|
||||
app.use(router.default).mount("#app");
|
||||
loading.close();
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ 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";
|
||||
|
||||
interface AlarmState {
|
||||
loading: boolean;
|
||||
@@ -35,7 +36,9 @@ export const alarmStore = defineStore({
|
||||
}),
|
||||
actions: {
|
||||
async getAlarms(params: Recordable) {
|
||||
this.loading = true;
|
||||
const res: AxiosResponse = await graphql.query("queryAlarms").params(params);
|
||||
this.loading = false;
|
||||
if (res.data.errors) {
|
||||
return res.data;
|
||||
}
|
||||
@@ -45,6 +48,20 @@ export const alarmStore = defineStore({
|
||||
}
|
||||
return res.data;
|
||||
},
|
||||
async getAlarmTagKeys() {
|
||||
const res: AxiosResponse = await graphql
|
||||
.query("queryAlarmTagKeys")
|
||||
.params({ duration: useAppStoreWithOut().durationTime });
|
||||
|
||||
return res.data;
|
||||
},
|
||||
async getAlarmTagValues(tagKey: string) {
|
||||
const res: AxiosResponse = await graphql
|
||||
.query("queryAlarmTagValues")
|
||||
.params({ tagKey, duration: useAppStoreWithOut().durationTime });
|
||||
|
||||
return res.data;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
59
src/types/auto-imports.d.ts
vendored
59
src/types/auto-imports.d.ts
vendored
@@ -1,52 +1,9 @@
|
||||
// Generated by 'unplugin-auto-import'
|
||||
// We suggest you to commit this file into source control
|
||||
declare global {
|
||||
const computed: typeof import('vue')['computed']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||
const defineComponent: typeof import('vue')['defineComponent']
|
||||
const effectScope: typeof import('vue')['effectScope']
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const h: typeof import('vue')['h']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const markRaw: typeof import('vue')['markRaw']
|
||||
const nextTick: typeof import('vue')['nextTick']
|
||||
const onActivated: typeof import('vue')['onActivated']
|
||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||
const onMounted: typeof import('vue')['onMounted']
|
||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
const readonly: typeof import('vue')['readonly']
|
||||
const ref: typeof import('vue')['ref']
|
||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const unref: typeof import('vue')['unref']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
const useCssVars: typeof import('vue')['useCssVars']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
const watch: typeof import('vue')['watch']
|
||||
const watchEffect: typeof import('vue')['watchEffect']
|
||||
}
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
export {}
|
||||
declare global {
|
||||
|
||||
}
|
||||
|
16
src/types/components.d.ts
vendored
16
src/types/components.d.ts
vendored
@@ -1,9 +1,11 @@
|
||||
// generated by unplugin-vue-components
|
||||
// We suggest you to commit this file into source control
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
import '@vue/runtime-core'
|
||||
export {}
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
DateCalendar: typeof import('./../components/DateCalendar.vue')['default']
|
||||
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
|
||||
@@ -42,7 +44,6 @@ declare module '@vue/runtime-core' {
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
Graph: typeof import('./../components/Graph.vue')['default']
|
||||
Icon: typeof import('./../components/Icon.vue')['default']
|
||||
Loading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||
Radio: typeof import('./../components/Radio.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
@@ -51,6 +52,7 @@ declare module '@vue/runtime-core' {
|
||||
Tags: typeof import('./../components/Tags.vue')['default']
|
||||
TimePicker: typeof import('./../components/TimePicker.vue')['default']
|
||||
}
|
||||
export interface ComponentCustomProperties {
|
||||
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
||||
|
15
src/types/ebpf.d.ts
vendored
15
src/types/ebpf.d.ts
vendored
@@ -77,6 +77,21 @@ export type StackElement = {
|
||||
rateOfRoot?: string;
|
||||
rateOfParent: string;
|
||||
};
|
||||
export type TraceProfilingElement = {
|
||||
id: string;
|
||||
originId: string;
|
||||
name: string;
|
||||
parentId: string;
|
||||
codeSignature: string;
|
||||
count: number;
|
||||
stackType: string;
|
||||
value: number;
|
||||
children?: TraceProfilingElement[];
|
||||
rateOfRoot?: string;
|
||||
rateOfParent: string;
|
||||
duration: number;
|
||||
durationChildExcluded: number;
|
||||
};
|
||||
export type AnalyzationTrees = {
|
||||
id: string;
|
||||
parentId: string;
|
||||
|
24
src/utils/flameGraph.ts
Normal file
24
src/utils/flameGraph.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export function treeForeach(tree: any, func: (node: any) => void) {
|
||||
for (const data of tree) {
|
||||
data.children && treeForeach(data.children, func);
|
||||
func(data);
|
||||
}
|
||||
return tree;
|
||||
}
|
@@ -13,7 +13,7 @@ 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="timeline-table clear">
|
||||
<div class="timeline-table clear" v-loading="alarmStore.loading">
|
||||
<div v-for="(i, index) in alarmStore.alarms" :key="index" class="clear timeline-item">
|
||||
<div class="g-sm-3 grey sm hide-xs time-line tr">
|
||||
{{ dateFormat(parseInt(i.startTime)) }}
|
||||
@@ -132,7 +132,7 @@ limitations under the License. -->
|
||||
currentDetail.value = item;
|
||||
currentEvents.value = item.events;
|
||||
alarmTags.value = currentDetail.value.tags.map((d: { key: string; value: string }) => {
|
||||
return `${d.key} = ${d.value}`;
|
||||
return `${d.key}=${d.value}`;
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -21,15 +21,7 @@ limitations under the License. -->
|
||||
<span class="remove-icon" @click="removeTags(index)">×</span>
|
||||
</span>
|
||||
</span>
|
||||
<el-input
|
||||
v-if="type === 'ALARM'"
|
||||
size="small"
|
||||
v-model="tags"
|
||||
class="trace-new-tag"
|
||||
@change="addLabels"
|
||||
:placeholder="t('addTags')"
|
||||
/>
|
||||
<el-popover v-else trigger="click" :visible="visible" width="300px">
|
||||
<el-popover trigger="click" :visible="visible" width="300px">
|
||||
<template #reference>
|
||||
<el-input
|
||||
size="small"
|
||||
@@ -47,7 +39,7 @@ limitations under the License. -->
|
||||
</span>
|
||||
</div>
|
||||
</el-popover>
|
||||
<span class="tags-tip" :class="type !== 'ALARM' && tagArr.length ? 'link-tips' : ''">
|
||||
<span class="tags-tip" :class="tagArr.length ? 'link-tips' : ''">
|
||||
<a
|
||||
target="blank"
|
||||
href="https://github.com/apache/skywalking/blob/master/docs/en/setup/backend/configuration-vocabulary.md"
|
||||
@@ -68,6 +60,7 @@ limitations under the License. -->
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useTraceStore } from "@/store/modules/trace";
|
||||
import { useLogStore } from "@/store/modules/log";
|
||||
import { useAlarmStore } from "@/store/modules/alarm";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
|
||||
@@ -79,6 +72,7 @@ limitations under the License. -->
|
||||
const traceStore = useTraceStore();
|
||||
const logStore = useLogStore();
|
||||
const appStore = useAppStoreWithOut();
|
||||
const alarmStore = useAlarmStore();
|
||||
const { t } = useI18n();
|
||||
const tags = ref<string>("");
|
||||
const tagsList = ref<string[]>([]);
|
||||
@@ -121,10 +115,18 @@ limitations under the License. -->
|
||||
let resp: Recordable = {};
|
||||
if (props.type === "TRACE") {
|
||||
resp = await traceStore.getTagKeys();
|
||||
} else {
|
||||
}
|
||||
if (props.type === "LOG") {
|
||||
resp = await logStore.getLogTagKeys();
|
||||
}
|
||||
|
||||
if (props.type === "ALARM") {
|
||||
resp = await alarmStore.getAlarmTagKeys();
|
||||
}
|
||||
|
||||
if (!resp.data) {
|
||||
return;
|
||||
}
|
||||
if (resp.errors) {
|
||||
ElMessage.error(resp.errors);
|
||||
return;
|
||||
@@ -140,10 +142,17 @@ limitations under the License. -->
|
||||
let resp: Recordable = {};
|
||||
if (props.type === "TRACE") {
|
||||
resp = await traceStore.getTagValues(param);
|
||||
} else {
|
||||
}
|
||||
if (props.type === "LOG") {
|
||||
resp = await logStore.getLogTagValues(param);
|
||||
}
|
||||
if (props.type === "ALARM") {
|
||||
resp = await alarmStore.getAlarmTagValues(param);
|
||||
}
|
||||
|
||||
if (!resp.data) {
|
||||
return;
|
||||
}
|
||||
if (resp.errors) {
|
||||
ElMessage.error(resp.errors);
|
||||
return;
|
||||
|
@@ -15,9 +15,9 @@ limitations under the License. -->
|
||||
<template>
|
||||
<div class="content">
|
||||
<div class="header">
|
||||
<span>{{ decodeURIComponent(title) }}</span>
|
||||
<span>{{ title }}</span>
|
||||
<div class="tips" v-show="tips">
|
||||
<el-tooltip :content="decodeURIComponent(tips) || ''">
|
||||
<el-tooltip :content="tips || ''">
|
||||
<span>
|
||||
<Icon iconName="info_outline" size="sm" />
|
||||
</span>
|
||||
@@ -53,7 +53,7 @@ limitations under the License. -->
|
||||
import { useRoute } from "vue-router";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useExpressionsQueryProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import { useDashboardQueryProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import graphs from "./graphs";
|
||||
import { EntityType } from "./data";
|
||||
import timeFormat from "@/utils/timeFormat";
|
||||
@@ -128,12 +128,16 @@ limitations under the License. -->
|
||||
}
|
||||
async function queryMetrics() {
|
||||
loading.value = true;
|
||||
const params = await useExpressionsQueryProcessor({
|
||||
const metrics: { [key: string]: { source: { [key: string]: unknown }; typesOfMQE: string[] } } =
|
||||
await useDashboardQueryProcessor([
|
||||
{
|
||||
metrics: config.value.expressions || [],
|
||||
metricConfig: config.value.metricConfig || [],
|
||||
subExpressions: config.value.subExpressions || [],
|
||||
});
|
||||
|
||||
id: config.value.i,
|
||||
},
|
||||
]);
|
||||
const params = metrics[config.value.i];
|
||||
loading.value = false;
|
||||
source.value = params.source || {};
|
||||
typesOfMQE.value = params.typesOfMQE;
|
||||
|
@@ -110,7 +110,7 @@ limitations under the License. -->
|
||||
ExpressionResultType,
|
||||
} from "@/views/dashboard/data";
|
||||
import Icon from "@/components/Icon.vue";
|
||||
import { useExpressionsQueryProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import { useDashboardQueryProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import type { DashboardItem, MetricConfigOpt } from "@/types/dashboard";
|
||||
import Standard from "./Standard.vue";
|
||||
@@ -240,12 +240,13 @@ limitations under the License. -->
|
||||
}
|
||||
|
||||
async function queryMetricsWithExpressions() {
|
||||
const { expressions, metricConfig } = dashboardStore.selectedGrid;
|
||||
const { expressions, metricConfig, i } = dashboardStore.selectedGrid;
|
||||
if (!(expressions && expressions[0])) {
|
||||
return emit("update", {});
|
||||
}
|
||||
|
||||
const params: Indexable = (await useExpressionsQueryProcessor({ ...states, metricConfig })) || {};
|
||||
const metrics: Indexable = (await useDashboardQueryProcessor([{ ...states, metricConfig, id: i }])) || {};
|
||||
const params = metrics[i];
|
||||
states.tips = params.tips || [];
|
||||
states.metricTypes = params.typesOfMQE || [];
|
||||
dashboardStore.selectWidget({
|
||||
|
@@ -113,6 +113,7 @@ limitations under the License. -->
|
||||
:data="item"
|
||||
:activeIndex="`${data.i}-${activeTabIndex}-${item.i}`"
|
||||
:needQuery="needQuery"
|
||||
:metricsValues="metricsValues"
|
||||
/>
|
||||
</grid-item>
|
||||
</grid-layout>
|
||||
@@ -129,13 +130,17 @@ limitations under the License. -->
|
||||
import controls from "./tab";
|
||||
import { dragIgnoreFrom, WidgetType } from "../data";
|
||||
import copy from "@/utils/copy";
|
||||
import { useExpressionsQueryProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import { useDashboardQueryProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
|
||||
const props = {
|
||||
data: {
|
||||
type: Object as PropType<LayoutConfig>,
|
||||
default: () => ({ children: [] }),
|
||||
},
|
||||
metricsValues: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({}),
|
||||
},
|
||||
active: { type: Boolean, default: false },
|
||||
};
|
||||
export default defineComponent({
|
||||
@@ -259,8 +264,10 @@ limitations under the License. -->
|
||||
if (!metrics.length) {
|
||||
return;
|
||||
}
|
||||
const params: { [key: string]: any } = (await useExpressionsQueryProcessor({ metrics })) || {};
|
||||
const values: { [key: string]: any } =
|
||||
(await useDashboardQueryProcessor([{ metrics, id: props.data.i }])) || {};
|
||||
for (const child of tabsProps.children || []) {
|
||||
const params = values[props.data.i];
|
||||
if (params.source[child.expression || ""]) {
|
||||
child.enable =
|
||||
!!Number(params.source[child.expression || ""]) &&
|
||||
|
@@ -75,11 +75,10 @@ limitations under the License. -->
|
||||
import type { LayoutConfig } from "@/types/dashboard";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import graphs from "../graphs";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useExpressionsQueryProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import { EntityType, ListChartTypes } from "../data";
|
||||
import { useDashboardQueryProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import { ListChartTypes } from "../data";
|
||||
import type { EventParams } from "@/types/dashboard";
|
||||
import getDashboard from "@/hooks/useDashboardsSession";
|
||||
|
||||
@@ -88,6 +87,10 @@ limitations under the License. -->
|
||||
type: Object as PropType<LayoutConfig>,
|
||||
default: () => ({ widget: {}, graph: {} }),
|
||||
},
|
||||
metricsValues: {
|
||||
type: Object as PropType<{ [key: string]: { source: { [key: string]: unknown }; typesOfMQE: string[] } }>,
|
||||
default: () => ({}),
|
||||
},
|
||||
activeIndex: { type: String, default: "" },
|
||||
needQuery: { type: Boolean, default: false },
|
||||
};
|
||||
@@ -105,23 +108,20 @@ limitations under the License. -->
|
||||
const { data } = toRefs(props);
|
||||
const appStore = useAppStoreWithOut();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const selectorStore = useSelectorStore();
|
||||
const graph = computed(() => props.data.graph || {});
|
||||
const widget = computed(() => props.data.widget || {});
|
||||
const isList = computed(() => ListChartTypes.includes((props.data.graph && props.data.graph.type) || ""));
|
||||
const typesOfMQE = ref<string[]>([]);
|
||||
|
||||
if ((props.needQuery || !dashboardStore.currentDashboard.id) && !isList.value) {
|
||||
queryMetrics();
|
||||
}
|
||||
|
||||
async function queryMetrics() {
|
||||
loading.value = true;
|
||||
const e = {
|
||||
const config = {
|
||||
metrics: props.data.expressions || [],
|
||||
metricConfig: props.data.metricConfig || [],
|
||||
id: props.data.i,
|
||||
};
|
||||
const params = (await useExpressionsQueryProcessor(e)) || {};
|
||||
const metrics: { [key: string]: { source: { [key: string]: unknown }; typesOfMQE: string[] } } =
|
||||
(await useDashboardQueryProcessor([config])) || {};
|
||||
const params = metrics[data.value.i];
|
||||
loading.value = false;
|
||||
state.source = params.source || {};
|
||||
typesOfMQE.value = params.typesOfMQE;
|
||||
@@ -160,7 +160,7 @@ limitations under the License. -->
|
||||
dashboardStore.selectWidget(props.data);
|
||||
}
|
||||
watch(
|
||||
() => props.data.expressions,
|
||||
() => props.data,
|
||||
() => {
|
||||
if (!dashboardStore.selectedGrid) {
|
||||
return;
|
||||
@@ -169,54 +169,19 @@ limitations under the License. -->
|
||||
return;
|
||||
}
|
||||
const chart = dashboardStore.selectedGrid.graph || {};
|
||||
if (ListChartTypes.includes(chart.type) || isList.value) {
|
||||
if (ListChartTypes.includes(chart.type)) {
|
||||
return;
|
||||
}
|
||||
queryMetrics();
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => [selectorStore.currentService, selectorStore.currentDestService],
|
||||
() => props.metricsValues,
|
||||
() => {
|
||||
if (isList.value) {
|
||||
return;
|
||||
}
|
||||
if ([EntityType[0].value, EntityType[4].value].includes(dashboardStore.entity)) {
|
||||
queryMetrics();
|
||||
}
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => [selectorStore.currentPod, selectorStore.currentDestPod],
|
||||
() => {
|
||||
if ([EntityType[0].value, EntityType[7].value, EntityType[8].value].includes(dashboardStore.entity)) {
|
||||
return;
|
||||
}
|
||||
if (isList.value) {
|
||||
return;
|
||||
}
|
||||
queryMetrics();
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => [selectorStore.currentProcess, selectorStore.currentDestProcess],
|
||||
() => {
|
||||
if (isList.value) {
|
||||
return;
|
||||
}
|
||||
if ([EntityType[7].value, EntityType[8].value].includes(dashboardStore.entity)) {
|
||||
queryMetrics();
|
||||
}
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => appStore.durationTime,
|
||||
() => {
|
||||
if (isList.value) {
|
||||
return;
|
||||
}
|
||||
if (dashboardStore.entity === EntityType[1].value) {
|
||||
queryMetrics();
|
||||
const params = props.metricsValues[data.value.i];
|
||||
if (params) {
|
||||
state.source = params.source || {};
|
||||
typesOfMQE.value = params.typesOfMQE;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@@ -67,9 +67,10 @@ limitations under the License. -->
|
||||
<el-pagination
|
||||
class="pagination flex-h"
|
||||
layout="prev, pager, next"
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:total="pods.length"
|
||||
@current-change="changePage"
|
||||
:total="searchText ? pods.filter((d: any) => d.label.includes(searchText)).length : pods.length"
|
||||
@current-change="handleCurrentChange"
|
||||
@prev-click="changePage"
|
||||
@next-click="changePage"
|
||||
/>
|
||||
@@ -122,6 +123,7 @@ limitations under the License. -->
|
||||
const dashboardStore = useDashboardStore();
|
||||
const chartLoading = ref<boolean>(false);
|
||||
const instances = ref<Instance[]>([]); // current instances
|
||||
const currentPage = ref<number>(1);
|
||||
const pageSize = 10;
|
||||
const searchText = ref<string>("");
|
||||
const colMetrics = ref<string[]>([]);
|
||||
@@ -212,19 +214,27 @@ limitations under the License. -->
|
||||
);
|
||||
}
|
||||
|
||||
function changePage(pageIndex: number) {
|
||||
instances.value = pods.value.filter((d: unknown, index: number) => {
|
||||
if (index >= (pageIndex - 1) * pageSize && index < pageIndex * pageSize) {
|
||||
return d;
|
||||
function changePage() {
|
||||
let podList = pods.value;
|
||||
if (searchText.value) {
|
||||
podList = pods.value.filter((d: { label: string }) => d.label.includes(searchText.value));
|
||||
}
|
||||
});
|
||||
instances.value = podList.filter(
|
||||
(_, index: number) => index >= (currentPage.value - 1) * pageSize && index < currentPage.value * pageSize,
|
||||
);
|
||||
queryInstanceMetrics(instances.value);
|
||||
}
|
||||
|
||||
function handleCurrentChange(val: number) {
|
||||
currentPage.value = val;
|
||||
changePage();
|
||||
}
|
||||
|
||||
function searchList() {
|
||||
const searchInstances = pods.value.filter((d: { label: string }) => d.label.includes(searchText.value));
|
||||
instances.value = searchInstances.filter((d: unknown, index: number) => index < pageSize);
|
||||
instances.value = searchInstances.filter((_, index: number) => index < pageSize);
|
||||
queryInstanceMetrics(instances.value);
|
||||
currentPage.value = 1;
|
||||
}
|
||||
|
||||
watch(
|
||||
|
@@ -60,9 +60,10 @@ limitations under the License. -->
|
||||
<el-pagination
|
||||
class="pagination flex-h"
|
||||
layout="prev, pager, next"
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:total="selectorStore.services.length"
|
||||
@current-change="changePage"
|
||||
:total="searchText ? sortServices.filter((d: any) => d.label.includes(searchText)).length : sortServices.length"
|
||||
@current-change="handleCurrentChange"
|
||||
@prev-click="changePage"
|
||||
@next-click="changePage"
|
||||
/>
|
||||
@@ -112,6 +113,7 @@ limitations under the License. -->
|
||||
const dashboardStore = useDashboardStore();
|
||||
const appStore = useAppStoreWithOut();
|
||||
const chartLoading = ref<boolean>(false);
|
||||
const currentPage = ref<number>(1);
|
||||
const pageSize = 10;
|
||||
const services = ref<Service[]>([]);
|
||||
const colMetrics = ref<string[]>([]);
|
||||
@@ -247,19 +249,28 @@ limitations under the License. -->
|
||||
}
|
||||
return { rowspan: groups.value[param.row.group], colspan: 1 };
|
||||
}
|
||||
function changePage(pageIndex: number) {
|
||||
const arr = sortServices.value.filter((d: Service, index: number) => {
|
||||
if (index >= (pageIndex - 1) * pageSize && index < pageSize * pageIndex) {
|
||||
function changePage() {
|
||||
let services = sortServices.value;
|
||||
if (searchText.value) {
|
||||
services = sortServices.value.filter((d: { label: string }) => d.label.includes(searchText.value));
|
||||
}
|
||||
const arr = services.filter((d: Service, index: number) => {
|
||||
if (index >= (currentPage.value - 1) * pageSize && index < pageSize * currentPage.value) {
|
||||
return d;
|
||||
}
|
||||
});
|
||||
|
||||
setServices(arr);
|
||||
}
|
||||
function handleCurrentChange(val: number) {
|
||||
currentPage.value = val;
|
||||
changePage();
|
||||
}
|
||||
function searchList() {
|
||||
const searchServices = sortServices.value.filter((d: { label: string }) => d.label.includes(searchText.value));
|
||||
const services = searchServices.filter((d: unknown, index: number) => index < pageSize);
|
||||
const services = searchServices.filter((_, index: number) => index < pageSize);
|
||||
setServices(services);
|
||||
currentPage.value = 1;
|
||||
}
|
||||
|
||||
watch(
|
||||
|
@@ -14,12 +14,15 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License. -->
|
||||
<template>
|
||||
<grid-layout
|
||||
v-if="dashboardStore.layout.length"
|
||||
v-model:layout="dashboardStore.layout"
|
||||
:col-num="24"
|
||||
:row-height="10"
|
||||
:is-draggable="dashboardStore.editMode"
|
||||
:is-resizable="dashboardStore.editMode"
|
||||
v-if="dashboardStore.layout.length"
|
||||
v-loading.fullscreen.lock="loading"
|
||||
element-loading-text="Loading..."
|
||||
element-loading-background="rgba(122, 122, 122, 0.8)"
|
||||
>
|
||||
<grid-item
|
||||
v-for="item in dashboardStore.layout"
|
||||
@@ -33,33 +36,39 @@ limitations under the License. -->
|
||||
:class="{ active: dashboardStore.activedGridItem === item.i }"
|
||||
:drag-ignore-from="dragIgnoreFrom"
|
||||
>
|
||||
<component :is="item.type" :data="item" />
|
||||
<component :is="item.type" :data="item" :metricsValues="metricsValues" />
|
||||
</grid-item>
|
||||
</grid-layout>
|
||||
<div class="no-data-tips" v-else>{{ t("noWidget") }}</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, onBeforeUnmount } from "vue";
|
||||
import { defineComponent, onBeforeUnmount, watch, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import type { LayoutConfig } from "@/types/dashboard";
|
||||
import controls from "../controls/index";
|
||||
import { dragIgnoreFrom } from "../data";
|
||||
import { dragIgnoreFrom, ListChartTypes, WidgetType } from "../data";
|
||||
import { useDashboardQueryProcessor } from "@/hooks/useExpressionsProcessor";
|
||||
import { EntityType } from "../data";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Layout",
|
||||
components: { ...controls },
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStoreWithOut();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const selectorStore = useSelectorStore();
|
||||
const metricsValues = ref();
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
function clickGrid(item: LayoutConfig, event: Event) {
|
||||
dashboardStore.activeGridItem(item.i);
|
||||
dashboardStore.selectWidget(item);
|
||||
if (
|
||||
item.type === "Tab" &&
|
||||
item.type === WidgetType.Tab &&
|
||||
!["operations", "tab-layout"].includes((event.target as HTMLDivElement)?.className) &&
|
||||
(event.target as HTMLDivElement)?.classList[2] !== "icon-tool" &&
|
||||
(event.target as HTMLDivElement)?.nodeName !== "use"
|
||||
@@ -67,6 +76,47 @@ limitations under the License. -->
|
||||
dashboardStore.setActiveTabIndex(0);
|
||||
}
|
||||
}
|
||||
async function queryMetrics() {
|
||||
const widgets = [];
|
||||
|
||||
for (const item of dashboardStore.layout) {
|
||||
const isList = ListChartTypes.includes(item.type || "");
|
||||
if (item.type === WidgetType.Widget && !isList) {
|
||||
widgets.push(item);
|
||||
}
|
||||
if (item.type === WidgetType.Tab) {
|
||||
const index = isNaN(item.activedTabIndex) ? 0 : item.activedTabIndex;
|
||||
widgets.push(...item.children[index].children);
|
||||
}
|
||||
}
|
||||
const configList = widgets.map((d: LayoutConfig) => ({
|
||||
metrics: d.expressions || [],
|
||||
metricConfig: d.metricConfig || [],
|
||||
id: d.i,
|
||||
}));
|
||||
if (!widgets.length) {
|
||||
return {};
|
||||
}
|
||||
loading.value = true;
|
||||
metricsValues.value = (await useDashboardQueryProcessor(configList)) || {};
|
||||
loading.value = false;
|
||||
}
|
||||
async function queryTabsMetrics() {
|
||||
const configList = dashboardStore.currentTabItems
|
||||
.filter((item: LayoutConfig) => item.type === WidgetType.Widget && !ListChartTypes.includes(item.type || ""))
|
||||
.map((d: LayoutConfig) => ({
|
||||
metrics: d.expressions || [],
|
||||
metricConfig: d.metricConfig || [],
|
||||
id: d.i,
|
||||
}));
|
||||
if (!configList.length) {
|
||||
return {};
|
||||
}
|
||||
loading.value = true;
|
||||
metricsValues.value = (await useDashboardQueryProcessor(configList)) || {};
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
dashboardStore.setLayout([]);
|
||||
selectorStore.setCurrentService(null);
|
||||
@@ -74,11 +124,53 @@ limitations under the License. -->
|
||||
dashboardStore.setEntity("");
|
||||
dashboardStore.setConfigPanel(false);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => [selectorStore.currentService, selectorStore.currentDestService],
|
||||
() => {
|
||||
if ([EntityType[0].value, EntityType[4].value].includes(dashboardStore.entity)) {
|
||||
queryMetrics();
|
||||
}
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => [selectorStore.currentPod, selectorStore.currentDestPod],
|
||||
() => {
|
||||
if ([EntityType[0].value, EntityType[7].value, EntityType[8].value].includes(dashboardStore.entity)) {
|
||||
return;
|
||||
}
|
||||
queryMetrics();
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => [selectorStore.currentProcess, selectorStore.currentDestProcess],
|
||||
() => {
|
||||
if ([EntityType[7].value, EntityType[8].value].includes(dashboardStore.entity)) {
|
||||
queryMetrics();
|
||||
}
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => appStore.durationTime,
|
||||
() => {
|
||||
if (dashboardStore.entity === EntityType[1].value) {
|
||||
queryMetrics();
|
||||
}
|
||||
},
|
||||
);
|
||||
watch(
|
||||
() => dashboardStore.currentTabItems,
|
||||
() => {
|
||||
queryTabsMetrics();
|
||||
},
|
||||
);
|
||||
return {
|
||||
dashboardStore,
|
||||
clickGrid,
|
||||
t,
|
||||
dragIgnoreFrom,
|
||||
metricsValues,
|
||||
loading,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@@ -520,7 +520,7 @@ limitations under the License. -->
|
||||
instanceId: selectorStore.currentPod.id,
|
||||
});
|
||||
if (setPod) {
|
||||
const process = params.processId || selectorStore.processes[0].id;
|
||||
const process = selectorStore.currentProcess?.id || params.processId || selectorStore.processes[0].id;
|
||||
const currentProcess = selectorStore.processes.find((d: { id: string }) => d.id === process);
|
||||
if (currentProcess) {
|
||||
selectorStore.setCurrentProcess(currentProcess);
|
||||
@@ -538,7 +538,8 @@ limitations under the License. -->
|
||||
isRelation: true,
|
||||
});
|
||||
if (setPod) {
|
||||
const destProcess = params.destProcessId || selectorStore.destProcesses[0].id;
|
||||
const destProcess =
|
||||
selectorStore.currentDestProcess?.id || params.destProcessId || selectorStore.destProcesses[0].id;
|
||||
const currentDestProcess = selectorStore.destProcesses.find((d: { id: string }) => d.id === destProcess);
|
||||
if (!currentDestProcess) {
|
||||
states.currentDestProcess = "";
|
||||
@@ -570,7 +571,7 @@ limitations under the License. -->
|
||||
states.currentDestPod = "";
|
||||
return;
|
||||
}
|
||||
const destPod = params.destPodId || selectorStore.destPods[0].id;
|
||||
const destPod = selectorStore.currentDestPod?.id || params.destPodId || selectorStore.destPods[0].id;
|
||||
const currentDestPod = selectorStore.destPods.find((d: { id: string }) => d.id === destPod);
|
||||
if (!currentDestPod) {
|
||||
states.currentDestPod = "";
|
||||
@@ -602,7 +603,7 @@ limitations under the License. -->
|
||||
states.currentPod = "";
|
||||
return;
|
||||
}
|
||||
const pod = params.podId || selectorStore.pods[0].id;
|
||||
const pod = selectorStore.currentPod?.id || params.podId || selectorStore.pods[0].id;
|
||||
const currentPod = selectorStore.pods.find((d: { id: string }) => d.id === pod);
|
||||
if (!currentPod) {
|
||||
selectorStore.setCurrentPod(null);
|
||||
|
@@ -28,6 +28,7 @@ limitations under the License. -->
|
||||
import type { StackElement } from "@/types/ebpf";
|
||||
import { AggregateTypes } from "./data";
|
||||
import "d3-flame-graph/dist/d3-flamegraph.css";
|
||||
import { treeForeach } from "@/utils/flameGraph";
|
||||
|
||||
/*global Nullable, defineProps*/
|
||||
const props = defineProps({
|
||||
@@ -180,14 +181,6 @@ limitations under the License. -->
|
||||
return res;
|
||||
}
|
||||
|
||||
function treeForeach(tree: StackElement[], func: (node: StackElement) => void) {
|
||||
for (const data of tree) {
|
||||
data.children && treeForeach(data.children, func);
|
||||
func(data);
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => ebpfStore.analyzeTrees,
|
||||
() => {
|
||||
|
@@ -67,11 +67,18 @@ limitations under the License. -->
|
||||
</div>
|
||||
<ConditionTags :type="'LOG'" @update="updateTags" />
|
||||
</div>
|
||||
<div class="row tips" v-show="!isBrowser">
|
||||
<div v-show="!isBrowser">
|
||||
<div class="row tips">
|
||||
<b>{{ t("conditionNotice") }}</b>
|
||||
<el-tooltip :content="t('keywordsOfContentLogTips')">
|
||||
<span v-show="!logStore.supportQueryLogsByKeywords">
|
||||
<Icon iconName="help" />
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="flex-h" v-show="!isBrowser">
|
||||
<div class="mr-5" v-show="logStore.supportQueryLogsByKeywords">
|
||||
<div v-show="logStore.supportQueryLogsByKeywords">
|
||||
<div class="flex-h">
|
||||
<div class="mr-5">
|
||||
<span class="mr-5 grey">{{ t("keywordsOfContent") }}:</span>
|
||||
<span class="log-tags">
|
||||
<span class="selected" v-for="(item, index) in keywordsOfContent" :key="`keywordsOfContent${index}`">
|
||||
@@ -87,7 +94,7 @@ limitations under the License. -->
|
||||
@change="addLabels('keywordsOfContent')"
|
||||
/>
|
||||
</div>
|
||||
<div class="mr-5" v-show="logStore.supportQueryLogsByKeywords">
|
||||
<div class="mr-5">
|
||||
<span class="grey mr-5"> {{ t("excludingKeywordsOfContent") }}: </span>
|
||||
<span class="log-tags">
|
||||
<span
|
||||
@@ -106,11 +113,8 @@ limitations under the License. -->
|
||||
v-model="excludingContentStr"
|
||||
@change="addLabels('excludingKeywordsOfContent')"
|
||||
/>
|
||||
<el-tooltip :content="t('keywordsOfContentLogTips')">
|
||||
<span v-show="!logStore.supportQueryLogsByKeywords">
|
||||
<Icon iconName="help" class="mr-5" />
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@@ -42,14 +42,14 @@ limitations under the License. -->
|
||||
@closed="showDetail = false"
|
||||
:title="t('logDetail')"
|
||||
>
|
||||
<LogDetail :currentLog="currentLog" />
|
||||
<LogDetail :currentLog="currentLog" :logTemplate="type === 'browser' ? BrowserLogConstants : ServiceLogDetail" />
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { ServiceLogConstants, BrowserLogConstants } from "./data";
|
||||
import { ServiceLogConstants, BrowserLogConstants, ServiceLogDetail } from "./data";
|
||||
import LogBrowser from "./LogBrowser.vue";
|
||||
import LogService from "./LogService.vue";
|
||||
import LogDetail from "./LogDetail.vue";
|
||||
|
@@ -21,7 +21,6 @@ limitations under the License. -->
|
||||
:class="['log', ['message', 'stack'].includes(item.label) ? 'max-item' : '']"
|
||||
>
|
||||
<span v-if="item.label === 'time'">{{ dateFormat(data.time) }}</span>
|
||||
<span v-else-if="item.label === 'errorUrl'">{{ data.pagePath }}</span>
|
||||
<el-tooltip v-else :content="data[item.label] || '-'">
|
||||
<span>
|
||||
{{ data[item.label] || "-" }}
|
||||
@@ -35,7 +34,7 @@ limitations under the License. -->
|
||||
import { BrowserLogConstants } from "./data";
|
||||
import { dateFormat } from "@/utils/dateFormat";
|
||||
|
||||
/*global defineProps, defineEmits, NodeListOf */
|
||||
/* global defineProps, defineEmits */
|
||||
const props = defineProps({
|
||||
data: { type: Object as any, default: () => ({}) },
|
||||
});
|
||||
@@ -44,13 +43,6 @@ limitations under the License. -->
|
||||
const logItem = ref<any>(null);
|
||||
|
||||
function showSelectSpan() {
|
||||
const items: NodeListOf<any> = document.querySelectorAll(".log-item");
|
||||
|
||||
for (const item of items) {
|
||||
item.style.background = "#fff";
|
||||
}
|
||||
|
||||
logItem.value.style.background = "rgba(0, 0, 0, 0.1)";
|
||||
emit("select", props.data);
|
||||
}
|
||||
</script>
|
||||
@@ -62,15 +54,15 @@ limitations under the License. -->
|
||||
}
|
||||
|
||||
.log-item.selected {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
background: rgb(0 0 0 / 4%);
|
||||
}
|
||||
|
||||
.log-item:not(.level0):hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
background: rgb(0 0 0 / 4%);
|
||||
}
|
||||
|
||||
.log-item:hover {
|
||||
background: rgba(0, 0, 0, 0.04) !important;
|
||||
background: rgb(0 0 0 / 4%) !important;
|
||||
}
|
||||
|
||||
.log-item > div {
|
||||
|
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License. -->
|
||||
<template>
|
||||
<div class="log-detail">
|
||||
<div class="mb-10 clear rk-flex" v-for="(item, index) in ServiceLogDetail" :key="index">
|
||||
<div class="mb-10 clear rk-flex" v-for="(item, index) in logTemplate" :key="index">
|
||||
<span class="g-sm-4 grey">{{ t(item.value) }}:</span>
|
||||
<span v-if="['timestamp', 'time'].includes(item.label)" class="g-sm-8 mb-10">
|
||||
{{ dateFormat(currentLog[item.label]) }}
|
||||
@@ -38,11 +38,11 @@ limitations under the License. -->
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { dateFormat } from "@/utils/dateFormat";
|
||||
import { formatJson } from "@/utils/formatJson";
|
||||
import { ServiceLogDetail } from "./data";
|
||||
|
||||
/*global defineProps */
|
||||
const props = defineProps({
|
||||
currentLog: { type: Object as PropType<any>, default: () => ({}) },
|
||||
logTemplate: { type: Object as PropType<any>, default: () => ({}) },
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const logTags = computed(() => {
|
||||
@@ -50,7 +50,7 @@ limitations under the License. -->
|
||||
return [];
|
||||
}
|
||||
return props.currentLog.tags.map((d: { key: string; value: string }) => {
|
||||
return `${d.key} = ${d.value}`;
|
||||
return `${d.key}=${d.value}`;
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -30,7 +30,7 @@ limitations under the License. -->
|
||||
<Icon iconName="merge" />
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<span v-else>{{ data[item.label] }}</span>
|
||||
<span v-else v-html="highlightKeywords(data[item.label])"></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -42,6 +42,8 @@ limitations under the License. -->
|
||||
import type { LayoutConfig } from "@/types/dashboard";
|
||||
import { dateFormat } from "@/utils/dateFormat";
|
||||
import { WidgetType } from "@/views/dashboard/data";
|
||||
import { useLogStore } from "@/store/modules/log";
|
||||
const logStore = useLogStore();
|
||||
|
||||
/*global defineProps, defineEmits, Recordable */
|
||||
const props = defineProps({
|
||||
@@ -58,6 +60,11 @@ limitations under the License. -->
|
||||
}
|
||||
return (props.data.tags.find((d: { key: string; value: string }) => d.key === "level") || {}).value || "";
|
||||
});
|
||||
const highlightKeywords = (data: string) => {
|
||||
const keywords = Object.values(logStore.conditions.keywordsOfContent || {});
|
||||
const regex = new RegExp(keywords.join("|"), "gi");
|
||||
return data.replace(regex, (match) => `<span style="color: red">${match}</span>`);
|
||||
};
|
||||
|
||||
function selectLog(label: string, value: string) {
|
||||
if (label === "traceId") {
|
||||
|
@@ -74,8 +74,8 @@ export const BrowserLogConstants = [
|
||||
value: "serviceVersion",
|
||||
},
|
||||
{
|
||||
label: "errorUrl",
|
||||
value: "errorPage",
|
||||
label: "pagePath",
|
||||
value: "pagePath",
|
||||
},
|
||||
{
|
||||
label: "time",
|
||||
@@ -87,6 +87,10 @@ export const BrowserLogConstants = [
|
||||
// drag: true,
|
||||
method: 350,
|
||||
},
|
||||
{
|
||||
label: "errorUrl",
|
||||
value: "errorUrl",
|
||||
},
|
||||
{
|
||||
label: "stack",
|
||||
value: "stack",
|
||||
|
@@ -19,9 +19,11 @@ limitations under the License. -->
|
||||
<SegmentList />
|
||||
</div>
|
||||
<div class="item">
|
||||
<SpanTree @loading="loadTrees" />
|
||||
<SpanTree @loading="loadTrees" @displayMode="setDisplayMode" />
|
||||
<div class="thread-stack">
|
||||
<div id="graph-stack" ref="graph" v-show="displayMode == 'flame'" />
|
||||
<StackTable
|
||||
v-show="displayMode == 'tree'"
|
||||
v-if="profileStore.analyzeTrees.length"
|
||||
:data="profileStore.analyzeTrees"
|
||||
:highlightTop="profileStore.highlightTop"
|
||||
@@ -34,19 +36,175 @@ limitations under the License. -->
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
/*global Nullable*/
|
||||
import { ref, watch } from "vue";
|
||||
import TaskList from "./components/TaskList.vue";
|
||||
import SegmentList from "./components/SegmentList.vue";
|
||||
import SpanTree from "./components/SpanTree.vue";
|
||||
import StackTable from "./components/Stack/Index.vue";
|
||||
import { useProfileStore } from "@/store/modules/profile";
|
||||
|
||||
import type { TraceProfilingElement } from "@/types/ebpf";
|
||||
import { flamegraph } from "d3-flame-graph";
|
||||
import * as d3 from "d3";
|
||||
import d3tip from "d3-tip";
|
||||
import { treeForeach } from "@/utils/flameGraph";
|
||||
const stackTree = ref<Nullable<TraceProfilingElement>>(null);
|
||||
const selectStack = ref<Nullable<TraceProfilingElement>>(null);
|
||||
const graph = ref<Nullable<HTMLDivElement>>(null);
|
||||
const flameChart = ref<any>(null);
|
||||
const min = ref<number>(1);
|
||||
const max = ref<number>(1);
|
||||
const loading = ref<boolean>(false);
|
||||
const displayMode = ref<string>("tree");
|
||||
const profileStore = useProfileStore();
|
||||
|
||||
function loadTrees(l: boolean) {
|
||||
loading.value = l;
|
||||
}
|
||||
function setDisplayMode(mode: string) {
|
||||
displayMode.value = mode;
|
||||
}
|
||||
|
||||
function drawGraph() {
|
||||
if (flameChart.value) {
|
||||
flameChart.value.destroy();
|
||||
}
|
||||
if (!profileStore.analyzeTrees.length) {
|
||||
return (stackTree.value = null);
|
||||
}
|
||||
const root: TraceProfilingElement = {
|
||||
parentId: "0",
|
||||
originId: "1",
|
||||
name: "Virtual Root",
|
||||
children: [],
|
||||
value: 0,
|
||||
id: "1",
|
||||
codeSignature: "Virtual Root",
|
||||
count: 0,
|
||||
stackType: "",
|
||||
rateOfRoot: "",
|
||||
rateOfParent: "",
|
||||
duration: 0,
|
||||
durationChildExcluded: 0,
|
||||
};
|
||||
countRange();
|
||||
for (const tree of profileStore.analyzeTrees) {
|
||||
const ele = processTree(tree.elements);
|
||||
root.children && root.children.push(ele);
|
||||
}
|
||||
const param = (root.children || []).reduce(
|
||||
(prev: number[], curr: TraceProfilingElement) => {
|
||||
prev[0] += curr.value;
|
||||
prev[1] += curr.count;
|
||||
return prev;
|
||||
},
|
||||
[0, 0],
|
||||
);
|
||||
root.value = param[0];
|
||||
root.count = param[1];
|
||||
stackTree.value = root;
|
||||
const width = (graph.value && graph.value.getBoundingClientRect().width) || 0;
|
||||
const w = width < 800 ? 802 : width;
|
||||
flameChart.value = flamegraph()
|
||||
.width(w - 15)
|
||||
.cellHeight(18)
|
||||
.transitionDuration(750)
|
||||
.minFrameSize(1)
|
||||
.transitionEase(d3.easeCubic as any)
|
||||
.sort(true)
|
||||
.title("")
|
||||
.selfValue(false)
|
||||
.inverted(true)
|
||||
.onClick((d: { data: TraceProfilingElement }) => {
|
||||
selectStack.value = d.data;
|
||||
})
|
||||
.setColorMapper((d, originalColor) => (d.highlight ? "#6aff8f" : originalColor));
|
||||
const tip = (d3tip as any)()
|
||||
.attr("class", "d3-tip")
|
||||
.direction("s")
|
||||
.html((d: { data: TraceProfilingElement } & { parent: { data: TraceProfilingElement } }) => {
|
||||
const name = d.data.name.replace("<", "<").replace(">", ">");
|
||||
const dumpCount = `<div class="mb-5">Dump Count: ${d.data.count}</div>`;
|
||||
const duration = `<div class="mb-5">Duration: ${d.data.duration} ns</div>`;
|
||||
const durationChildExcluded = `<div class="mb-5">DurationChildExcluded: ${d.data.durationChildExcluded} ns</div>`;
|
||||
const rateOfParent =
|
||||
(d.parent &&
|
||||
`<div class="mb-5">Percentage Of Selected: ${
|
||||
((d.data.count / ((selectStack.value && selectStack.value.count) || root.count)) * 100).toFixed(3) + "%"
|
||||
}</div>`) ||
|
||||
"";
|
||||
const rateOfRoot = `<div class="mb-5">Percentage Of Root: ${
|
||||
((d.data.count / root.count) * 100).toFixed(3) + "%"
|
||||
}</div>`;
|
||||
return `<div class="mb-5 name">CodeSignature: ${name}</div>${dumpCount}${duration}${durationChildExcluded}${rateOfParent}${rateOfRoot}`;
|
||||
})
|
||||
.style("max-width", "400px");
|
||||
flameChart.value.tooltip(tip);
|
||||
d3.select("#graph-stack").datum(stackTree.value).call(flameChart.value);
|
||||
}
|
||||
|
||||
function countRange() {
|
||||
const list = [];
|
||||
for (const tree of profileStore.analyzeTrees) {
|
||||
for (const ele of tree.elements) {
|
||||
list.push(ele.count);
|
||||
}
|
||||
}
|
||||
max.value = Math.max(...list);
|
||||
min.value = Math.min(...list);
|
||||
}
|
||||
|
||||
function processTree(arr: TraceProfilingElement[]) {
|
||||
const copyArr = JSON.parse(JSON.stringify(arr));
|
||||
const obj: any = {};
|
||||
let res = null;
|
||||
for (const item of copyArr) {
|
||||
item.parentId = String(Number(item.parentId) + 1);
|
||||
item.originId = String(Number(item.id) + 1);
|
||||
item.name = item.codeSignature;
|
||||
delete item.id;
|
||||
obj[item.originId] = item;
|
||||
}
|
||||
const scale = d3.scaleLinear().domain([min.value, max.value]).range([1, 200]);
|
||||
|
||||
for (const item of copyArr) {
|
||||
if (item.parentId === "1") {
|
||||
const val = Number(scale(item.count).toFixed(4));
|
||||
res = item;
|
||||
res.value = val;
|
||||
}
|
||||
for (const key in obj) {
|
||||
if (item.originId === obj[key].parentId) {
|
||||
const val = Number(scale(obj[key].count).toFixed(4));
|
||||
|
||||
obj[key].value = val;
|
||||
if (item.children) {
|
||||
item.children.push(obj[key]);
|
||||
} else {
|
||||
item.children = [obj[key]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
treeForeach([res], (node: TraceProfilingElement) => {
|
||||
if (node.children) {
|
||||
let val = 0;
|
||||
for (const child of node.children) {
|
||||
val = child.value + val;
|
||||
}
|
||||
node.value = node.value < val ? val : node.value;
|
||||
}
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => profileStore.analyzeTrees,
|
||||
() => {
|
||||
drawGraph();
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
@@ -78,4 +236,22 @@ limitations under the License. -->
|
||||
overflow: hidden;
|
||||
height: calc(50% - 20px);
|
||||
}
|
||||
|
||||
#graph-stack {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tip {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: red;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.name {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
</style>
|
||||
|
@@ -19,12 +19,20 @@ limitations under the License. -->
|
||||
<el-input class="input mr-10 ml-5" readonly :value="profileStore.currentSegment.traceId" size="small" />
|
||||
<Selector
|
||||
size="small"
|
||||
:value="mode"
|
||||
:options="ProfileMode"
|
||||
placeholder="Select a mode"
|
||||
:value="dataMode"
|
||||
:options="ProfileDataMode"
|
||||
placeholder="Please select a profile data mode"
|
||||
@change="spanModeChange"
|
||||
class="mr-10"
|
||||
/>
|
||||
<Selector
|
||||
size="small"
|
||||
:value="displayMode"
|
||||
:options="ProfileDisplayMode"
|
||||
placeholder="Please select a profile display mode"
|
||||
@change="selectDisplayMode"
|
||||
class="mr-10"
|
||||
/>
|
||||
<el-button type="primary" size="small" :disabled="!profileStore.currentSpan.profiled" @click="analyzeProfile()">
|
||||
{{ t("analyze") }}
|
||||
</el-button>
|
||||
@@ -49,13 +57,14 @@ limitations under the License. -->
|
||||
import type { Span } from "@/types/trace";
|
||||
import type { Option } from "@/types/app";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { ProfileMode } from "./data";
|
||||
import { ProfileDataMode, ProfileDisplayMode } from "./data";
|
||||
|
||||
/* global defineEmits*/
|
||||
const emits = defineEmits(["loading"]);
|
||||
const emits = defineEmits(["loading", "displayMode"]);
|
||||
const { t } = useI18n();
|
||||
const profileStore = useProfileStore();
|
||||
const mode = ref<string>("include");
|
||||
const dataMode = ref<string>("include");
|
||||
const displayMode = ref<string>("tree");
|
||||
const message = ref<string>("");
|
||||
const timeRange = ref<Array<{ start: number; end: number }>>([]);
|
||||
|
||||
@@ -64,10 +73,15 @@ limitations under the License. -->
|
||||
}
|
||||
|
||||
function spanModeChange(item: Option[]) {
|
||||
mode.value = item[0].value;
|
||||
dataMode.value = item[0].value;
|
||||
updateTimeRange();
|
||||
}
|
||||
|
||||
function selectDisplayMode(item: Option[]) {
|
||||
displayMode.value = item[0].value;
|
||||
emits("displayMode", displayMode.value);
|
||||
}
|
||||
|
||||
async function analyzeProfile() {
|
||||
if (!profileStore.currentSpan.profiled) {
|
||||
ElMessage.info("It's a un-profiled span");
|
||||
@@ -92,7 +106,7 @@ limitations under the License. -->
|
||||
}
|
||||
|
||||
function updateTimeRange() {
|
||||
if (mode.value === "include") {
|
||||
if (dataMode.value === "include") {
|
||||
timeRange.value = [
|
||||
{
|
||||
start: profileStore.currentSpan.startTime,
|
||||
@@ -158,7 +172,7 @@ limitations under the License. -->
|
||||
|
||||
.profile-trace-detail-wrapper {
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-bottom: 1px solid rgb(0 0 0 / 10%);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
@@ -14,10 +14,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export const ProfileMode: any[] = [
|
||||
export const ProfileDataMode: any[] = [
|
||||
{ label: "Include Children", value: "include" },
|
||||
{ label: "Exclude Children", value: "exclude" },
|
||||
];
|
||||
export const ProfileDisplayMode: any[] = [
|
||||
{ label: "Tree Graph", value: "tree" },
|
||||
{ label: "Flame Graph", value: "flame" },
|
||||
];
|
||||
export const NewTaskField = {
|
||||
service: { key: "", label: "None" },
|
||||
monitorTime: { key: "0", label: "monitor now" },
|
||||
|
@@ -141,9 +141,10 @@ function findMostFrequent(arr: Call[]) {
|
||||
|
||||
return maxItem;
|
||||
}
|
||||
export function computeLevels(calls: Call[], nodeList: Node[], levels: any[]) {
|
||||
export function computeLevels(calls: Call[], nodeList: Node[], arr: Node[][]) {
|
||||
const levels: Node[][] = [];
|
||||
const node = findMostFrequent(calls);
|
||||
const nodes = JSON.parse(JSON.stringify(nodeList)).sort((a: Node, b: Node) => {
|
||||
let nodes = JSON.parse(JSON.stringify(nodeList)).sort((a: Node, b: Node) => {
|
||||
if (a.name.toLowerCase() < b.name.toLowerCase()) {
|
||||
return -1;
|
||||
}
|
||||
@@ -158,23 +159,23 @@ export function computeLevels(calls: Call[], nodeList: Node[], levels: any[]) {
|
||||
key = nodes.findIndex((n: Node) => n.id === node.id);
|
||||
}
|
||||
levels.push([nodes[key]]);
|
||||
nodes.splice(key, 1);
|
||||
nodes = nodes.filter((_: unknown, index: number) => index !== key);
|
||||
for (const level of levels) {
|
||||
const a = [];
|
||||
for (const l of level) {
|
||||
for (const n of calls) {
|
||||
if (n.target === l.id) {
|
||||
const i = nodes.findIndex((d: Node) => d.id === n.source);
|
||||
if (i > -1) {
|
||||
if (i > -1 && nodes[i]) {
|
||||
a.push(nodes[i]);
|
||||
nodes.splice(i, 1);
|
||||
nodes = nodes.filter((_: unknown, index: number) => index !== i);
|
||||
}
|
||||
}
|
||||
if (n.source === l.id) {
|
||||
const i = nodes.findIndex((d: Node) => d.id === n.target);
|
||||
if (i > -1) {
|
||||
if (i > -1 && nodes[i]) {
|
||||
a.push(nodes[i]);
|
||||
nodes.splice(i, 1);
|
||||
nodes = nodes.filter((_: unknown, index: number) => index !== i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,13 +184,22 @@ export function computeLevels(calls: Call[], nodeList: Node[], levels: any[]) {
|
||||
levels.push(a);
|
||||
}
|
||||
}
|
||||
const list = levels.length > arr.length ? levels : arr;
|
||||
const subList = levels.length > arr.length ? arr : levels;
|
||||
arr = list.map((subArray: Node[], index: number) => {
|
||||
if (subList[index]) {
|
||||
return subArray.concat(subList[index]);
|
||||
} else {
|
||||
return subArray;
|
||||
}
|
||||
});
|
||||
|
||||
if (nodes.length) {
|
||||
const ids = nodes.map((d: Node) => d.id);
|
||||
const links = calls.filter((item: Call) => ids.includes(item.source) || ids.includes(item.target));
|
||||
const list = computeLevels(links, nodes, []);
|
||||
levels = list.map((subArrayA, index) => subArrayA.concat(levels[index]));
|
||||
arr = computeLevels(links, nodes, arr);
|
||||
}
|
||||
return levels;
|
||||
return arr;
|
||||
}
|
||||
export function changeNode(d: { x: number; y: number }, currentNode: Nullable<Node>, layout: any, radius: number) {
|
||||
if (!currentNode) {
|
||||
@@ -229,7 +239,7 @@ export function changeNode(d: { x: number; y: number }, currentNode: Nullable<No
|
||||
}
|
||||
export function hierarchy(levels: Node[][], calls: Call[], radius: number) {
|
||||
// precompute level depth
|
||||
levels.forEach((l: Node[], i: number) => l.forEach((n: any) => n && (n.level = i)));
|
||||
levels.forEach((l: Node[], i: number) => l.forEach((n: Node) => n && (n.level = i)));
|
||||
|
||||
const nodes: Node[] = levels.reduce((a, x) => a.concat(x), []);
|
||||
// layout
|
||||
|
@@ -110,7 +110,7 @@ limitations under the License. -->
|
||||
<div>
|
||||
<span class="grey title">Tags:</span>
|
||||
<div class="mb-5" v-for="(tag, index) in currentEvent.tags || []" :key="index" style="white-space: pre-wrap">
|
||||
{{ tag.key + "=" + tag.value }};
|
||||
{{ `${tag.key}=${tag.value}` }};
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -54,11 +54,11 @@ limitations under the License. -->
|
||||
const result: StatisticsSpan[] = [];
|
||||
const map = traceTable.changeStatisticsTree(data);
|
||||
map.forEach((nodes, nodeKey) => {
|
||||
const nodeKeyData = nodeKey.split(":");
|
||||
const lastColonIndex = nodeKey.lastIndexOf(":");
|
||||
result.push(
|
||||
getSpanGroupData(nodes, {
|
||||
endpointName: nodeKeyData[0],
|
||||
type: nodeKeyData[1],
|
||||
endpointName: nodeKey.slice(0, lastColonIndex),
|
||||
type: nodeKey.slice(lastColonIndex + 1),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
@@ -218,14 +218,14 @@ export default class ListGraph {
|
||||
.append("text")
|
||||
.attr("x", 13)
|
||||
.attr("y", 5)
|
||||
.attr("fill", appStore.theme === Themes.Dark ? "#666" : "#E54C17")
|
||||
.attr("fill", `#e54c17`)
|
||||
.html((d: Recordable) => (d.data.isError ? "◉" : ""));
|
||||
nodeEnter
|
||||
.append("text")
|
||||
.attr("class", "node-text")
|
||||
.attr("x", 35)
|
||||
.attr("y", -6)
|
||||
.attr("fill", appStore.theme === Themes.Dark ? "#eee" : "#333")
|
||||
.attr("fill", (d: Recordable) => (d.data.isError ? `#e54c17` : appStore.theme === Themes.Dark ? "#eee" : "#333"))
|
||||
.html((d: Recordable) => {
|
||||
if (d.data.label === "TRACE_ROOT") {
|
||||
return "";
|
||||
|
@@ -37,7 +37,6 @@ export default ({ mode }: ConfigEnv): UserConfig => {
|
||||
vueJsx(),
|
||||
monacoEditorPlugin({}),
|
||||
AutoImport({
|
||||
imports: ["vue"],
|
||||
resolvers: [ElementPlusResolver()],
|
||||
dts: path.resolve(__dirname, "./src/types/auto-imports.d.ts"),
|
||||
}),
|
||||
|
Reference in New Issue
Block a user