resolved conflicts

This commit is contained in:
Peter Olu 2022-07-09 16:08:54 -07:00
commit f18e5c7149
117 changed files with 3771 additions and 2413 deletions

75
package-lock.json generated
View File

@ -21,6 +21,7 @@
"element-plus": "^2.0.2",
"jquery": "^3.6.0",
"lodash": "^4.17.21",
"monaco-editor": "^0.27.0",
"pinia": "^2.0.5",
"sockjs": "^0.3.24",
"sockjs-client": "^1.6.0",
@ -60,6 +61,7 @@
"eslint-plugin-vue": "^7.0.0",
"husky": "^7.0.4",
"lint-staged": "^12.1.3",
"monaco-editor-webpack-plugin": "^4.1.2",
"node-sass": "^6.0.1",
"postcss-html": "^1.3.0",
"postcss-scss": "^4.0.2",
@ -11233,9 +11235,10 @@
}
},
"node_modules/eventsource": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz",
"integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.1.tgz",
"integrity": "sha512-qV5ZC0h7jYIAOhArFJgSfdyz6rALJyb270714o7ZtNnw2WSJ+eexhKtE0O8LYPRsHZHf2osHKZBxGPvm3kPkCA==",
"dev": true,
"dependencies": {
"original": "^1.0.0"
},
@ -18371,6 +18374,38 @@
"node": "*"
}
},
"node_modules/monaco-editor": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.27.0.tgz",
"integrity": "sha512-UhwP78Wb8w0ZSYoKXQNTV/0CHObp6NS3nCt51QfKE6sKyBo5PBsvuDOHoI2ooBakc6uIwByRLHVeT7+yXQe2fQ=="
},
"node_modules/monaco-editor-webpack-plugin": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-4.1.2.tgz",
"integrity": "sha512-snmHecygICKT0UlHhva+Cs2WaLPpxy3111xbvInhjjTr5m0xQTFHlmJ2QQDcB14Vzmm7f07uc1TtbvOpmL50BA==",
"dev": true,
"dependencies": {
"loader-utils": "^2.0.0"
},
"peerDependencies": {
"monaco-editor": "0.25.x || 0.26.x || 0.27.x",
"webpack": "^4.5.0 || 5.x"
}
},
"node_modules/monaco-editor-webpack-plugin/node_modules/loader-utils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz",
"integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==",
"dev": true,
"dependencies": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
},
"engines": {
"node": ">=8.9.0"
}
},
"node_modules/move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@ -37989,9 +38024,10 @@
"dev": true
},
"eventsource": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz",
"integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.1.tgz",
"integrity": "sha512-qV5ZC0h7jYIAOhArFJgSfdyz6rALJyb270714o7ZtNnw2WSJ+eexhKtE0O8LYPRsHZHf2osHKZBxGPvm3kPkCA==",
"dev": true,
"requires": {
"original": "^1.0.0"
}
@ -43595,6 +43631,33 @@
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
},
"monaco-editor": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.27.0.tgz",
"integrity": "sha512-UhwP78Wb8w0ZSYoKXQNTV/0CHObp6NS3nCt51QfKE6sKyBo5PBsvuDOHoI2ooBakc6uIwByRLHVeT7+yXQe2fQ=="
},
"monaco-editor-webpack-plugin": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-4.1.2.tgz",
"integrity": "sha512-snmHecygICKT0UlHhva+Cs2WaLPpxy3111xbvInhjjTr5m0xQTFHlmJ2QQDcB14Vzmm7f07uc1TtbvOpmL50BA==",
"dev": true,
"requires": {
"loader-utils": "^2.0.0"
},
"dependencies": {
"loader-utils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz",
"integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
}
}
},
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",

View File

@ -23,6 +23,7 @@
"element-plus": "^2.0.2",
"jquery": "^3.6.0",
"lodash": "^4.17.21",
"monaco-editor": "^0.27.0",
"pinia": "^2.0.5",
"sockjs": "^0.3.24",
"sockjs-client": "^1.6.0",
@ -62,6 +63,7 @@
"eslint-plugin-vue": "^7.0.0",
"husky": "^7.0.4",
"lint-staged": "^12.1.3",
"monaco-editor-webpack-plugin": "^4.1.2",
"node-sass": "^6.0.1",
"postcss-html": "^1.3.0",
"postcss-scss": "^4.0.2",

View File

@ -13,11 +13,13 @@ 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>
<router-view :key="$route.fullPath" />
<router-view />
</template>
<style>
#app {
color: var(--spp-white);
height: 100%;
overflow: auto;
min-width: 1024px;
}
</style>

View File

@ -0,0 +1,15 @@
<!-- 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 t="1655799536378" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9286" width="48" height="48"><path d="M563.2 614.4v51.2c0 30.72-20.48 51.2-51.2 51.2s-51.2-20.48-51.2-51.2v-51.2H409.6c-30.72 0-51.2-20.48-51.2-51.2s20.48-51.2 51.2-51.2h51.2V460.8c0-30.72 20.48-51.2 51.2-51.2s51.2 20.48 51.2 51.2v51.2h51.2c30.72 0 51.2 20.48 51.2 51.2s-20.48 51.2-51.2 51.2h-51.2z m51.2-563.2c158.72 15.36 281.6 143.36 281.6 307.2v512c0 56.32-46.08 102.4-102.4 102.4h-563.2c-56.32 0-102.4-46.08-102.4-102.4V153.6c0-56.32 46.08-102.4 102.4-102.4H614.4z m163.84 230.4c-25.6-61.44-76.8-107.52-138.24-122.88v71.68c0 30.72 20.48 51.2 51.2 51.2h87.04zM537.6 153.6h-256c-30.72 0-51.2 20.48-51.2 51.2v614.4c0 30.72 20.48 51.2 51.2 51.2h460.8c30.72 0 51.2-20.48 51.2-51.2V384h-153.6c-56.32 0-102.4-46.08-102.4-102.4V153.6z" fill="#707070" p-id="9287"></path></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View 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 t="1654161407133" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1721" width="16" height="16"><path d="M804.224 86.144c0-19.264-15.616-34.944-34.944-34.944l-734.336 0c-19.264 0-34.944 15.68-34.944 34.944l0 82.048 804.224 0 0-82.048zM738.88 602.432c0 47.072-38.176 85.248-85.248 85.248s-85.248-38.176-85.248-85.248c0-47.072 38.176-85.248 85.248-85.248s85.248 38.176 85.248 85.248zM804.992 264.64l0-62.976-804.224 0 0 665.408c0 18.56 14.656 33.472 32.96 34.56l402.24 0c61.12 44.544 136.192 71.168 217.664 71.168 204.544 0 370.368-165.824 370.368-370.368 0-150.592-89.984-279.936-219.008-337.792zM412.096 298.24l30.528 0c-10.624 7.36-20.8 15.36-30.528 23.744l0-23.744zM63.04 298.24l153.024 0 0 153.024-153.024 0 0-153.024zM216.064 805.056l-153.024 0 0-153.024 153.024 0 0 153.024zM219.136 631.232l-153.024 0 0-153.024 153.024 0 0 153.024zM237.568 805.056l0-153.024 49.408 0c7.488 55.936 27.264 107.904 56.832 153.024l-106.24 0zM284.672 631.232l-44.032 0 0-153.024 64.384 0c-13.824 38.848-21.824 80.576-21.824 124.224 0 9.728 0.768 19.264 1.472 28.8zM390.592 341.76c-31.168 31.424-56.512 68.544-74.88 109.44l-78.144 0 0-152.96 153.024 0 0 43.52zM899.136 638.4l-63.36 12.864c-4.288 16.064-10.688 31.296-18.816 45.376l35.712 53.888-50.944 50.944-53.888-35.712c-14.08 8.128-29.312 14.528-45.376 18.816l-12.864 63.36-72 0-12.864-63.36c-16.064-4.288-31.296-10.688-45.376-18.816l-53.888 35.712-50.944-50.944 35.712-53.888c-8.128-14.08-14.528-29.312-18.816-45.376l-63.36-12.864 0-72 63.36-12.864c4.352-16.064 10.688-31.296 18.816-45.312l-35.712-53.952 50.944-50.944 53.888 35.776c14.08-8.128 29.312-14.464 45.376-18.816l12.864-63.36 72 0 12.864 63.36c16.064 4.288 31.296 10.688 45.376 18.816l53.888-35.712 50.944 50.944-35.712 53.824c8.128 14.08 14.528 29.312 18.816 45.376l63.36 12.864 0 72z" p-id="1722" fill="#707070"></path></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,15 @@
<!-- 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 t="1655695739627" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2218" width="48" height="48"><path d="M173.292308 177.230769C86.646154 265.846154 39.384615 382.030769 39.384615 504.123077 39.384615 531.692308 61.046154 551.384615 86.646154 551.384615s47.261538-21.661538 47.261538-47.261538c-1.969231-96.492308 37.415385-189.046154 106.338462-257.969231s163.446154-106.338462 257.969231-106.338461c27.569231 0 47.261538-21.661538 47.261538-47.261539 0-27.569231-21.661538-47.261538-47.261538-47.261538C378.092308 43.323077 259.938462 90.584615 173.292308 177.230769z m57.107692 326.892308c0 27.569231 19.692308 47.261538 47.261538 47.261538s47.261538-21.661538 47.261539-47.261538c0-45.292308 17.723077-90.584615 51.2-122.092308 33.476923-33.476923 76.8-49.230769 122.092308-51.2 27.569231 0 47.261538-21.661538 47.261538-47.261538 0-27.569231-19.692308-47.261538-47.261538-47.261539-70.892308 0-139.815385 27.569231-191.015385 76.8-7.876923 9.846154-80.738462 82.707692-76.8 191.015385z m665.6-204.8c-17.723077-23.630769-41.353846-51.2-45.292308-55.138462-5.907692-3.938462-13.784615-7.876923-21.661538-7.876923-7.876923 0-15.753846 1.969231-19.692308 7.876923L610.461538 441.107692c-47.261538-39.384615-118.153846-37.415385-163.446153 7.876923-45.292308 45.292308-47.261538 116.184615-7.876923 163.446154l-191.015385 191.015385c-5.907692 5.907692-9.846154 13.784615-9.846154 21.661538 0 9.846154 3.938462 19.692308 11.815385 25.6l53.16923 39.384616c72.861538 57.107692 163.446154 88.615385 259.938462 88.615384 232.369231 0 421.415385-189.046154 421.415385-421.415384 0-94.523077-33.476923-185.107692-88.615385-257.969231z" p-id="2219" fill="#707070"></path></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -247,7 +247,7 @@ limitations under the License. -->
(state.showMinutes = state.showSeconds = false)
"
:class="{ on: state.showHours }"
>{{ state.hour || dd }}</a
>{{ dd(state.hour) }}</a
>
<span>:</span>
<a
@ -257,7 +257,7 @@ limitations under the License. -->
(state.showHours = state.showSeconds = false)
"
:class="{ on: state.showMinutes }"
>{{ state.minute || dd }}</a
>{{ dd(state.minute) }}</a
>
<span v-show="state.m !== 'D'">
<span>:</span>
@ -268,7 +268,7 @@ limitations under the License. -->
(state.showHours = state.showMinutes = false)
"
:class="{ on: state.showSeconds }"
>{{ state.second || dd }}</a
>{{ dd(state.second) }}</a
>
</span>
</div>
@ -464,7 +464,6 @@ const status = (
const minutes = time.getMinutes();
const seconds = time.getSeconds();
const milliseconds = time.getMilliseconds();
const dd = (t: number) => `0${t}`.slice(-2);
const map: { [key: string]: string | number } = {
YYYY: year,
MM: dd(month + 1),

View File

@ -53,9 +53,6 @@ const available = computed(
(Array.isArray(props.option.series.data) && props.option.series.data[0])
);
onMounted(async () => {
if (!available.value) {
return;
}
await setOptions(props.option);
chartRef.value && addResizeListener(unref(chartRef), resize);
setTimeout(() => {

View File

@ -23,19 +23,31 @@ export enum TimeType {
export const Languages = [
{ label: "English", value: "en" },
{ label: "Chinese", value: "zh" },
{ label: "Spanish", value: "es" },
];
export const RoutesMap: { [key: string]: string } = {
GeneralServices: "GENERAL",
GeneralServicesActiveTabIndex: "GENERAL",
Database: "VIRTUAL_DATABASE",
DatabaseActiveTabIndex: "VIRTUAL_DATABASE",
MeshServices: "MESH",
MeshServicesActiveTabIndex: "MESH",
ControlPanel: "MESH_CP",
ControlPanelActiveTabIndex: "MESH_CP",
DataPanel: "MESH_DP",
DataPanelActiveTabIndex: "MESH_DP",
Linux: "OS_LINUX",
SkyWalkingServer: "SO11Y_OAP",
SkyWalkingServerActiveTabIndex: "SO11Y_OAP",
SatelliteActiveTabIndex: "SO11Y_SATELLITE",
Satellite: "SO11Y_SATELLITE",
Functions: "FAAS",
FunctionsActiveTabIndex: "FAAS",
Browser: "BROWSER",
BrowserActiveTabIndex: "BROWSER",
KubernetesCluster: "K8S",
KubernetesClusterActiveTabIndex: "K8S",
KubernetesService: "K8S_SERVICE",
KubernetesServiceActiveTabIndex: "K8S_SERVICE",
};

View File

@ -45,6 +45,5 @@ export const Alarm = {
endTime
}
}
total
}`,
};

View File

@ -0,0 +1,36 @@
/**
* 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 queryContainers = {
variable: "$condition: OndemandContainergQueryCondition!",
query: `
containers: listContainers(condition: $condition) {
errorReason
containers
}`,
};
export const queryStreamingLogs = {
variable: "$condition: OndemandLogQueryCondition",
query: `
logs: ondemandPodLogs(condition: $condition) {
errorReason
logs {
content
}
}`,
};

View File

@ -48,9 +48,9 @@ export const queryEBPFTasks = {
}`,
};
export const queryEBPFSchedules = {
variable: "$taskId: ID!, $duration: Duration!",
variable: "$taskId: ID!",
query: `
eBPFSchedules: queryEBPFProfilingSchedules(taskId: $taskId, duration: $duration) {
eBPFSchedules: queryEBPFProfilingSchedules(taskId: $taskId) {
scheduleId
taskId
process {
@ -60,7 +60,6 @@ export const queryEBPFSchedules = {
serviceName
instanceId
instanceName
layer
agentId
detectType
attributes {
@ -76,9 +75,9 @@ export const queryEBPFSchedules = {
export const analysisEBPFResult = {
variable:
"$scheduleIdList: [ID!]!, $timeRanges: [EBPFProfilingAnalyzeTimeRange!]!",
"$scheduleIdList: [ID!]!, $timeRanges: [EBPFProfilingAnalyzeTimeRange!]!, $aggregateType: EBPFProfilingAnalyzeAggregateType",
query: `
analysisEBPFResult: analysisEBPFProfilingResult(scheduleIdList: $scheduleIdList, timeRanges: $timeRanges) {
analysisEBPFResult: analysisEBPFProfilingResult(scheduleIdList: $scheduleIdList, timeRanges: $timeRanges, aggregateType: $aggregateType) {
tip
trees {
elements {

View File

@ -35,6 +35,5 @@ export const FetchEvents = {
startTime
endTime
}
total
}`,
};

View File

@ -30,7 +30,6 @@ export const QueryBrowserErrorLogs = {
stack
grade
}
total
}`,
};
@ -54,7 +53,6 @@ export const QueryServiceLogs = {
value
}
}
total
}`,
};
@ -63,3 +61,15 @@ export const QueryLogsByKeywords = {
query: `
support: supportQueryLogsByKeywords`,
};
export const LogTagKeys = {
variable: "$duration: Duration!",
query: `
tagKeys: queryLogTagAutocompleteKeys(duration: $duration)`,
};
export const LogTagValues = {
variable: "$tagKey: String!, $duration: Duration!",
query: `
tagValues: queryLogTagAutocompleteValues(tagKey: $tagKey, duration: $duration)`,
};

View File

@ -41,7 +41,6 @@ export const Instances = {
label: name
language
instanceUUID
layer
attributes {
name
value
@ -83,7 +82,6 @@ export const getInstance = {
label: name
language
instanceUUID
layer
attributes {
name
value

View File

@ -27,7 +27,6 @@ export const Traces = {
isError
traceIds
}
total
}`,
};
@ -74,3 +73,14 @@ export const TraceSpans = {
}
`,
};
export const TraceTagKeys = {
variable: "$duration: Duration!",
query: `
tagKeys: queryTraceTagAutocompleteKeys(duration: $duration)`,
};
export const TraceTagValues = {
variable: "$tagKey: String!, $duration: Duration!",
query: `
tagValues: queryTraceTagAutocompleteValues(tagKey: $tagKey, duration: $duration)`,
};

View File

@ -26,6 +26,7 @@ import * as profile from "./query/profile";
import * as alarm from "./query/alarm";
import * as event from "./query/event";
import * as ebpf from "./query/ebpf";
import * as demandLog from "./query/demand-log";
const query: { [key: string]: string } = {
...app,
@ -38,6 +39,7 @@ const query: { [key: string]: string } = {
...alarm,
...event,
...ebpf,
...demandLog,
};
class Graphql {
private queryData = "";

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.
*/
import { queryContainers, queryStreamingLogs } from "../fragments/demand-log";
export const fetchContainers = `query listContainers(${queryContainers.variable}) {${queryContainers.query}}`;
export const fetchDemandPodLogs = `query ondemandPodLogs(${queryStreamingLogs.variable}) {${queryStreamingLogs.query}}`;

View File

@ -19,9 +19,13 @@ import {
QueryBrowserErrorLogs,
QueryServiceLogs,
QueryLogsByKeywords,
LogTagValues,
LogTagKeys,
} from "../fragments/log";
export const queryBrowserErrorLogs = `query queryBrowserErrorLogs(${QueryBrowserErrorLogs.variable}) {
${QueryBrowserErrorLogs.query}}`;
export const queryServiceLogs = `query queryLogs(${QueryServiceLogs.variable}) {${QueryServiceLogs.query}}`;
export const queryLogsByKeywords = `query queryLogsByKeywords {${QueryLogsByKeywords.query}}`;
export const queryLogTagValues = `query queryTagValues(${LogTagValues.variable}) {${LogTagValues.query}}`;
export const queryLogTagKeys = `query queryTagKeys(${LogTagKeys.variable}) {${LogTagKeys.query}}`;

View File

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

View File

@ -79,7 +79,11 @@ export function useECharts(
if (!el || !unref(el)) {
return;
}
const { width, height } = el.getBoundingClientRect();
if (!width || !height) {
return;
}
chartInstance = echarts.init(el, t);
const { removeEvent } = useEventListener({
el: window,

View File

@ -331,7 +331,7 @@ export function useQueryTopologyMetrics(metrics: string[], ids: string[]) {
}
function calculateExp(
arr: { value: number }[],
config: { calculation: string }
config: { calculation?: string }
): (number | string)[] {
const sum = arr
.map((d: { value: number }) => d.value)
@ -356,7 +356,7 @@ function calculateExp(
export function aggregation(
val: number,
config: { calculation: string }
config: { calculation?: string }
): number | string {
let data: number | string = Number(val);

View File

@ -13,7 +13,11 @@ 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="nav-bar flex-h" :class="{ dark: theme === 'dark' }" v-if="$route.query['portal'] !== 'true'">
<div
class="nav-bar flex-h"
:class="{ dark: theme === 'dark' }"
v-if="$route.query['portal'] !== 'true'"
>
<div class="title">{{ appStore.pageTitle || t(pageName) }}</div>
<div class="app-config">
<span class="red" v-show="timeRange">{{ t("timeTips") }}</span>
@ -94,9 +98,6 @@ watch(
}
);
async function getVersion() {
if (appStore.version) {
return;
}
const res = await appStore.fetchVersion();
if (res.errors) {
ElMessage.error(res.errors);

View File

@ -41,7 +41,7 @@ limitations under the License. -->
<el-icon class="menu-icons" :style="{ marginRight: '12px' }">
<Icon size="lg" :iconName="menu.meta.icon" />
</el-icon>
<span :class="isCollapse ? 'collapse' : ''">
<span class="title" :class="isCollapse ? 'collapse' : ''">
{{ t(menu.meta.title) }}
</span>
</router-link>
@ -55,9 +55,9 @@ limitations under the License. -->
<router-link
class="items"
:to="m.path"
:exact="m.meta.exact || false"
:exact="(m.meta && m.meta.exact) || false"
>
<span>{{ t(m.meta.title) }}</span>
<span class="title">{{ m.meta && t(m.meta.title) }}</span>
</router-link>
</el-menu-item>
</el-menu-item-group>
@ -82,7 +82,7 @@ limitations under the License. -->
:to="menu.children[0].path"
:exact="menu.meta.exact"
>
<span>{{ t(menu.meta.title) }}</span>
<span class="title">{{ t(menu.meta.title) }}</span>
</router-link>
</template>
</el-menu-item>
@ -109,8 +109,9 @@ import { computed, ref } from "vue";
import { useRouter, RouteRecordRaw, useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
import Icon from "@/components/Icon.vue";
import { useAppStoreWithOut } from "@/store/modules/app";
const { path, query } = useRoute();
const { query } = useRoute();
// eslint-disable-next-line no-undef
let portalStyle = reactive({});
@ -126,13 +127,19 @@ if (query["portal"] === "true") {
const isPortalView = computed(() => {
return query["portal"] === "true";
});
const appStore = useAppStoreWithOut();
const { t } = useI18n();
const name = ref<any>(String(useRouter().currentRoute.value.name));
const name = ref<string>(String(useRouter().currentRoute.value.name));
const theme = ["VirtualMachine", "Kubernetes"].includes(name.value || "")
? ref("light")
: ref("black");
const routes = ref<any>(useRouter().options.routes);
const isCollapse = ref(query["portal"] === "true");
const routes = ref<RouteRecordRaw[] | any>(useRouter().options.routes);
if (/Android|webOS|iPhone|iPod|iPad|BlackBerry/i.test(navigator.userAgent)) {
appStore.setIsMobile(true);
} else {
appStore.setIsMobile(false);
}
const isCollapse = ref(appStore.isMobile ? true : false);
const controlMenu = () => {
isCollapse.value = !isCollapse.value;
};
@ -216,4 +223,11 @@ span.collapse {
width: 100%;
height: 60px;
}
.title {
display: inline-block;
max-width: 110px;
text-overflow: ellipsis;
overflow: hidden;
}
</style>

View File

@ -17,10 +17,12 @@
import { createI18n } from "vue-i18n";
import zh from "./lang/zh";
import en from "./lang/en";
import es from "./lang/es";
const messages = {
en,
zh,
es,
};
const savedLanguage = window.localStorage.getItem("language");

View File

@ -134,8 +134,15 @@ const msg = {
taskId: "Task ID",
triggerType: "Trigger Type",
targetType: "Target Type",
ebpfTip: "Don't have process could profiling",
ebpfTip: "Don't have a process for profiling",
processSelect: "Click to select processes",
container: "Container",
limit: "Limit",
page: "Page",
interval: "Refresh Interval",
pause: "Pause",
begin: "Start",
seconds: "Seconds",
hourTip: "Select Hour",
minuteTip: "Select Minute",
secondTip: "Select Second",
@ -154,7 +161,7 @@ const msg = {
dashboard: "Dashboard",
topology: "Topology",
trace: "Trace",
alarm: "Alarms",
alarm: "Alerting",
auto: "Auto",
reload: "Reload",
version: "Version",
@ -327,7 +334,7 @@ const msg = {
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 tag, key of content, exclude key of content(key=value).",
"Notice: Please press Enter after inputting a key of content, exclude key of content(key=value).",
language: "Language",
};
export default msg;

342
src/locales/lang/es.ts Normal file
View File

@ -0,0 +1,342 @@
/**
* 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 msg = {
general: "Servicio General",
services: "Servicios",
service: "Servicio",
traces: "Trazas",
metrics: "Métricas",
serviceMesh: "Malla de Servicios",
infrastructure: "Infraestructura",
virtualMachine: "Máquina Virtual",
dashboardNew: "Nuevo Panel",
dashboardList: "Listado Paneles",
logs: "Logs",
events: "Eventos",
alerts: "Alertas",
settings: "Ajustes",
dashboards: "Paneles",
profiles: "Perfiles",
database: "Base de Datos",
serviceName: "Nombre Servicio",
technologies: "Tecnologías",
generalServicePanel: "Panel Servicio General",
health: "Salud",
groupName: "Nombre Grupo",
topologies: "Topologías",
dataPanel: "Plano de Datos",
controlPanel: "Plano de Control",
eventList: "Listado Eventos",
newDashboard: "Crear panel nuevo",
dashboardEdit: "Editar el panel",
edit: "Editar",
delete: "Eliminar",
confirm: "Confirmar",
layer: "Capa",
endpoint: "Endpoint",
instance: "Instancia",
create: "Crear",
loading: "Cargando",
selectVisualization: "Visualiza tus métricas",
visualization: "Visualizaciones",
graphStyles: "Estilo de gráficas",
widgetOptions: "Opciones widget",
standardOptions: "Opciones estandar",
max: "Máx",
min: "Mín",
plus: "Más",
minus: "Menoss",
multiply: "Multiplcar",
divide: "Dividir",
convertToMilliseconds: "Convertir Unix Timestamp(milisegundos)",
convertToSeconds: "Convertir Unix Timestamp(segundos)",
smooth: "Suabe",
showSymbol: "Mostrar Símbolo",
step: "Paso",
showValues: "Mostrar Valores",
fontSize: "Tamaño Fuente",
showBackground: "Mostrar Fondo",
areaOpacity: "Opacidad Área",
editGraph: "Editar Opciones",
dashboardName: "Selecciona Nombre del Panel",
linkDashboard: "Nombre del panel relacionado con llamadas de la topología",
linkServerMetrics:
"Métricas de servidor relacionadas con llamadas de la topología",
linkClientMetrics:
"Métricas de cliente relacionadas con llamadas de la topología",
nodeDashboard: "Nombre del panel relacionado con nodos de la topología",
nodeMetrics: "Mêtricas relacionas con nodos de la topología",
instanceDashboard: "Nombre del panel relacionado con instancias de servicio",
endpointDashboard: "Nombre del panel relacionado con endpoints",
callSettings: "Ajustes Llamada",
nodeSettings: "Ajustes Nodo",
conditions: "Condiciones",
legendSettings: "Ajustes Leyenda",
setLegend: "Poner Leyenda",
backgroundColors: "Colores Fondo",
fontColors: "Colores Fuente",
iconTheme: "Tema Iconos",
default: "Por Defecto",
topSlow: "Top 5 lentos",
topChildren: "Top 5 hijos",
taskList: "Listado Tareas",
sampledTraces: "Trazas Muestreadas",
editTab: "Habilitar edición nombre pestanyas",
label: "Nombre Servicio",
id: "ID Servicio",
setRoot: "Ponerlo a raíz",
setNormal: "Ponerlo a normal",
export: "Exportar Plantilla Panel",
import: "Importar Plantilla Panel",
yes: "Sí",
no: "No",
tableHeaderCol1: "Nombre de la primera columna de la tabla",
tableHeaderCol2: "Nombre de la segunda columna de la tabla",
showXAxis: "Mostrar Eje X",
showYAxis: "Mostrar Eje Y",
nameError: "El nombre del panel no puede ser duplicado",
showGroup: "Mostrar Grupo",
noRoot: "Por favor ponga la raíz del panel",
noWidget: "Por favor añada widgets.",
rename: "Renombrar",
deleteTitle: "¿Está seguro que quiere eliminarlo?",
rootTitle: "¿Está seguro que quiere establecerlo?",
selfObservability: "Autoobservabilidad",
satellite: "Satéllite",
skyWalkingServer: "Servidor SkyWalking",
functions: "Funciones",
browser: "Navegador",
linux: "Linux",
editWarning: "Estás entrando en modo edición",
viewWarning: "Estás entrando en modo visualización",
virtualDatabase: "Base de Datos Virtual",
reloadDashboards: "Recargar Panel",
kubernetesService: "Servicio",
kubernetesCluster: "Cluster",
kubernetes: "Kubernetes",
textUrl: "Hipervínculo de Texto",
textAlign: "Alineación de Texto",
metricLabel: "Etiqueta de Métrica",
showUnit: "Mostrar Unidad",
noGraph: "Ningún Gráfico",
taskId: "ID Tarea",
triggerType: "Tipo de Disparador",
targetType: "Tipo de Objetivo",
ebpfTip: "Le falta el proceso para perfilar",
processSelect: "Click para seleccionar proceso",
page: "Página",
interval: "Intervalo de actualización",
pause: "Pausa",
begin: "Inicio",
seconds: "Segundos",
hourTip: "Seleccione Hora",
minuteTip: "Seleccione Minuto",
secondTip: "Seleccione Segundo",
second: "s",
yearSuffix: "Año",
monthsHead: "Ene_Feb_Mar_Abr_May_Jun_Jul_Ago_Set_Oct_Nov_Dic",
months: "Ene_Feb_Mar_Abr_May_Jun_Jul_Ago_Set_Oct_Nov_Dic",
weeks: "Lun_Mar_Mier_Jue_Vie_Sáb_Dom",
hello: "Hola",
helloMessage: "Bienvenido de vuelta, Apache SkyWalking APM System !",
username: "Usuario",
password: "Contraseña",
title: "Título",
width: "Ancho",
height: "Alto",
dashboard: "Panel",
topology: "Topología",
trace: "Traza",
alarm: "Recordatorio en curso",
auto: "Auto",
reload: "Recargar",
version: "Versión",
copy: "Copiar",
reset: "Resetear",
apply: "Aplicar",
template: "Plantilla",
cancel: "Cancelar",
createTab: "Crear Pestanya",
tabName: "Nombre de la Pestaña",
detectPoint: "Detectar Punto",
name: "Nombre",
types: "Tipos",
all: "Todo",
endpoints: "Endpoints",
cache: "Cache",
serviceinstance: "InstanciaServicio",
databaseaccess: "AccesoBaseDeDatos",
servicerelation: "RelaciónServicio",
serviceinstancerelation: "RelaciónInstanciaServicio",
endpointrelation: "RelaciónEndpoint",
status: "Estado",
endpointName: "Nombre Endpoint",
search: "Buscar",
clear: "Limpiar",
more: "Más",
traceID: "ID Traza",
range: "Rango",
timeRange: "Rango de Tiempo",
duration: "Duración",
startTime: "Hora Inicio",
start: "Incio",
spans: "Lapso",
spanInfo: "Info Lapso",
spanType: "Tipo de Lapso",
time: "Tiempo",
tags: "Etiquetas",
component: "Componente",
table: "Tabla",
list: "Lista",
tree: "Árbol",
filterScope: "Alcance de Filtro",
searchKeyword: "Palabra Clave",
quarterHourCutTip: "Últimos 15 mins",
halfHourCutTip: "Últimos 30 mins",
hourCutTip: "Última 1 hora",
dayCutTip: "Último 1 día",
weekCutTip: "Última 1 semana",
monthCutTip: "Última 1 mes",
serverZone: "Zona Horaria Servidor OAP",
exportImage: "Exportar imagen",
object: "Objecto",
profile: "Perfil",
newTask: "Nueva Tarea",
monitorTime: "Tiempo Monitorización",
monitorDuration: "Duración Monitorización",
minThreshold: "Mínn Umbral Duración",
dumpPeriod: "Volcar Periodo",
createTask: "Crear Tarea",
maxSamplingCount: "Máx Cantidad Mostreo",
analyze: "Analizar",
noData: "Ningún Dato",
taskInfo: "Información Tarea",
task: "Tarea",
operationType: "Tipo Operación",
operationTime: "Tiempo Operación",
taskView: "Ver Tarea",
includeChildren: "Incluir Hijos",
excludeChildren: "Excluir Hijos",
view: "Ver",
timeTips: "Intervalo de tiempo no puede excedir 60 dias",
entityType: "Tipo Entidad",
maxItemNum: "Máx número artículos",
unknownMetrics: "Métrica desconocida",
labels: "Etiquetas",
aggregation: "Cálculo",
unit: "Unidad",
labelsIndex: "Subíndice Etiqueta",
group: "Grupo Servicio",
browserView: "Navegador",
sortOrder: "Orden de clasificación",
chartType: "Tipo Gráfico",
currentDepth: "Profundidad actual",
showDepth: "Mostrar Selector Profundidad",
defaultDepth: "Profundidad Por Defecto",
traceTagsTip: `Solamente etiquetas definidas en core/default/searchableTracesTags pueden ser buscadas.
Más información en la página de Vocabulario de Configuración`,
logTagsTip: `Solamente etiquetas definidas en core/default/searchableLogsTags pueden ser buscadas.
Más información en la página de Vocabulario de Configuración`,
alarmTagsTip: `Solamente etiquetas definidas en core/default/searchableAlarmTags pueden ser buscadas.
Más información en la página de Vocabulario de Configuración`,
tagsLink: "Página de Vocabulario de Configuración",
addTag: "Por favor introduzca una etiqueta",
log: "Registro de Datos",
logCategory: "Categoría Registro de Datos",
errorCatalog: "Catálogo de Errores",
logDetail: "Detalle Registro de Datos",
timeReload: "Aviso: El intervalo de tiempo tiene que ser mayor que 0",
errorInfo: "Info Error",
stack: "Pila",
serviceVersion: "Versión Servicio",
errorPage: "Página de Error",
category: "Categoría",
grade: "Grado",
relatedTraceLogs: "Registro de Datos Relacionados",
setConditions: "Más Condiciones",
metricName: "Seleccionar Nombre Métrica",
keywordsOfContent: "Claves de Contenido",
excludingKeywordsOfContent: "Excluir Claves de Contenido",
return: "Volver",
isError: "Error",
contentType: "Tipo de Contenido",
content: "Contenido",
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.",
setEvent: "Establecer Evento",
viewAttributes: "Ver",
serviceEvents: "Eventos Servico",
select: "Seleccionar",
eventID: "ID Evento",
eventName: "Nombre Evento",
endTime: "Hora Finalización",
instanceEvents: "Eventos Instancia",
endpointEvents: "Eventos Endpoint",
enableEvents: "Habilitar Eventos",
disableEvents: "Deshabilitar Eventos",
eventSeries: "Serie de Eventos",
eventsType: "Tipo de Evento",
eventsMessage: "Mensaje del Evento",
eventsParameters: "Parámetro del Evento",
eventDetail: "Detalle del Evento",
value: "Valor",
show: "Mostrar",
hide: "Oculatr",
statistics: "Estadísticas",
message: "Mensaje",
tooltipsContent: "Contenido de Información de Herramienta",
alarmDetail: "Detalle Alarma",
scope: "Alcance",
destService: "Servicio Destinación",
destServiceInstance: "Instancia Servicio Destinación",
destEndpoint: "Endpoint Destinación",
eventSource: "Fuente Envento",
modalTitle: "Inspección",
selectRedirectPage:
"Quiere inspeccionar las Trazas or Registros de datos del servicio %s?",
logAnalysis: "Lenguaje de Análisis de Registro de Datos",
logDataBody: "Contenido del Registro de Datos",
addType: "Por favor introduzca un tipo",
traceContext: "Registro de datos con contexto de traza",
traceSegmentId: "ID Segmento Traza",
spanId: "ID Lapso",
inputTraceSegmentId: "Por favor introduzca el ID del segmento de la traza",
inputSpanId: "Por favor introduzca el ID del lapso",
inputTraceId: "Por favor introduzca el ID de la traza",
dsl: "Entrada de guión para LAL",
logContentType: "Tipo del registro de datos",
logRespContent: "Contenido Registro de Datos",
analysis: "Análisis",
waitLoading: "Cargando",
dslEmpty: "Entrada de guión de LAL no puede estar vacio",
logContentEmpty: "El contenido del registro de datos no puede estar vacio.",
debug: "Debugar",
addTraceID: "Por favor introduzca el ID de la traza",
addTags: "Por favor introduzaca una etiqueta",
addKeywordsOfContent: "Por favor introduzca una clave de contenido",
addExcludingKeywordsOfContent:
"Por favor introduzca una clave excluyente de contenido",
noticeTag:
"Por favor presione Intro después de introducir una etiqueta(clave=valor).",
conditionNotice:
"Aviso: Por favor presione Intro después de introducir una clave de contenido, excluir clave de contenido(clave=valor).",
language: "Lenguaje",
};
export default msg;

View File

@ -134,6 +134,13 @@ const msg = {
targetType: "目标类型",
processSelect: "点击选择进程",
ebpfTip: "没有进程可以分析",
container: "容器",
limit: "范围",
page: "页面",
interval: "刷新间隔时间",
pause: "暂停",
begin: "开始",
seconds: "秒",
hourTip: "选择小时",
minuteTip: "选择分钟",
secondTip: "选择秒数",
@ -327,7 +334,7 @@ const msg = {
addExcludingKeywordsOfContent: "请输入一个内容不包含的关键词",
noticeTag: "请输入一个标签(key=value)之后回车",
conditionNotice:
"请输入一个标签、内容关键词或者内容不包含的关键词(key=value)之后回车",
"请输入一个内容关键词或者内容不包含的关键词(key=value)之后回车",
language: "语言",
};
export default msg;

View File

@ -20,11 +20,18 @@ import router from "./router";
import { store } from "./store";
import components from "@/components";
import i18n from "./locales";
import { useAppStoreWithOut } from "@/store/modules/app";
import "./styles/index.ts";
const app = createApp(App);
const appStore = useAppStoreWithOut();
app.use(components);
app.use(i18n);
app.use(store);
app.use(router).mount("#app");
mountApp();
async function mountApp() {
await appStore.queryOAPTimeInfo();
app.use(router).mount("#app");
}

View File

@ -30,13 +30,13 @@ export const routesAlarm: Array<RouteRecordRaw> = [
component: Layout,
children: [
{
path: "/alarm",
path: "/alerting",
name: "Alarm",
meta: {
exact: false,
},
component: () =>
import(/* webpackChunkName: "alarms" */ "@/views/Alarm.vue"),
import(/* webpackChunkName: "alerting" */ "@/views/Alarm.vue"),
},
],
},

View File

@ -33,12 +33,20 @@ export const routesBrowser: Array<RouteRecordRaw> = [
name: "Browser",
meta: {
title: "browser",
headPath: "/browser",
exact: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/browser/tab/:activeTabIndex",
name: "BrowserActiveTabIndex",
meta: {
notShow: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},
];

View File

@ -53,32 +53,79 @@ export const routesDashboard: Array<RouteRecordRaw> = [
exact: false,
},
},
{
path: "",
redirect: "/dashboard/:layerId/:entity/:name",
name: "Create",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
meta: {
notShow: true,
},
children: [
{
path: "/dashboard/:layerId/:entity/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "Create",
name: "CreateChild",
},
{
path: "/dashboard/:layerId/:entity/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "CreateActiveTabIndex",
},
],
},
{
path: "",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "View",
redirect: "/dashboard/:layerId/:entity/:serviceId/:name",
meta: {
title: "dashboardEdit",
exact: false,
notShow: true,
},
},
children: [
{
path: "/dashboard/:layerId/:entity/:serviceId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "View",
name: "ViewChild",
},
{
path: "/dashboard/:layerId/:entity/:serviceId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewActiveTabIndex",
},
],
},
{
path: "",
redirect:
"/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewServiceRelation",
meta: {
title: "dashboardEdit",
exact: false,
notShow: true,
},
},
children: [
{
path: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name",
component: () =>
@ -86,12 +133,29 @@ export const routesDashboard: Array<RouteRecordRaw> = [
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewServiceRelation",
},
{
path: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewServiceRelationActiveTabIndex",
},
],
},
{
path: "",
redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewPod",
meta: {
title: "dashboardEdit",
exact: false,
notShow: true,
},
},
children: [
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:name",
component: () =>
@ -99,12 +163,30 @@ export const routesDashboard: Array<RouteRecordRaw> = [
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewPod",
},
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewPodActiveTabIndex",
},
],
},
{
path: "",
redirect:
"/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewPodRelation",
meta: {
title: "dashboardEdit",
exact: false,
notShow: true,
},
},
children: [
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name",
component: () =>
@ -112,11 +194,16 @@ export const routesDashboard: Array<RouteRecordRaw> = [
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewPodRelation",
meta: {
title: "dashboardEdit",
exact: true,
notShow: true,
},
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewPodRelationActiveTabIndex",
},
],
},
],
},

View File

@ -34,12 +34,20 @@ export const routesDatabase: Array<RouteRecordRaw> = [
name: "Database",
meta: {
title: "virtualDatabase",
headPath: "/database",
exact: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/database/tab/:activeTabIndex",
name: "DatabaseActiveTabIndex",
meta: {
notShow: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},
];

View File

@ -32,13 +32,17 @@ export const routesFunctions: Array<RouteRecordRaw> = [
path: "/functions",
name: "Functions",
meta: {
title: "functions",
headPath: "/functions",
exact: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/functions/tab/:activeTabIndex",
name: "FunctionsActiveTabIndex",
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},
];

View File

@ -33,8 +33,15 @@ export const routesGen: Array<RouteRecordRaw> = [
path: "/general",
name: "GeneralServices",
meta: {
title: "services",
headPath: "/general/service",
exact: true,
},
component: () =>
import(/* webpackChunkName: "layers" */ "@/views/Layer.vue"),
},
{
path: "/general/tab/:activeTabIndex",
name: "GeneralServicesActiveTabIndex",
meta: {
exact: true,
},
component: () =>

View File

@ -20,7 +20,6 @@ import { routesMesh } from "./serviceMesh";
import { routesDatabase } from "./database";
import { routesInfra } from "./infrastructure";
import { routesDashboard } from "./dashboard";
import { routesEvent } from "./event";
import { routesSetting } from "./setting";
import { routesAlarm } from "./alarm";
import { routesSelf } from "./selfObservability";
@ -39,7 +38,6 @@ const routes: Array<RouteRecordRaw> = [
...routesSelf,
...routesDashboard,
...routesAlarm,
...routesEvent,
...routesSetting,
];

View File

@ -39,6 +39,16 @@ export const routesInfra: Array<RouteRecordRaw> = [
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/linux/tab/:activeTabIndex",
name: "LinuxActiveTabIndex",
meta: {
title: "linux",
notShow: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
// {
// path: "/infrastructure/vm",
// name: "VirtualMachine",

View File

@ -33,20 +33,42 @@ export const routesK8s: Array<RouteRecordRaw> = [
path: "/kubernetes/cluster",
name: "KubernetesCluster",
meta: {
notShow: false,
title: "kubernetesCluster",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/kubernetes/cluster/tab/:activeTabIndex",
name: "KubernetesClusterActiveTabIndex",
meta: {
notShow: true,
title: "kubernetesClusterActiveTabIndex",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/kubernetes/service",
name: "KubernetesService",
meta: {
notShow: false,
title: "kubernetesService",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/kubernetes/service/tab/:activeTabIndex",
name: "KubernetesServiceActiveTabIndex",
meta: {
notShow: true,
title: "kubernetesServiceActiveTabIndex",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},
];

View File

@ -34,7 +34,15 @@ export const routesSelf: Array<RouteRecordRaw> = [
name: "SkyWalkingServer",
meta: {
title: "skyWalkingServer",
headPath: "/mesh/services",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/self/skyWalkingServer/tab/:activeTabIndex",
name: "SkyWalkingServerActiveTabIndex",
meta: {
notShow: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
@ -44,7 +52,15 @@ export const routesSelf: Array<RouteRecordRaw> = [
name: "Satellite",
meta: {
title: "satellite",
headPath: "/mesh/controlPanel",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/self/satellite/tab/:activeTabIndex",
name: "SatelliteActiveTabIndex",
meta: {
notShow: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),

View File

@ -33,8 +33,17 @@ export const routesMesh: Array<RouteRecordRaw> = [
path: "/mesh/services",
name: "MeshServices",
meta: {
notShow: false,
title: "services",
headPath: "/mesh/services",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mesh/services/tab/:activeTabIndex",
name: "MeshServicesActiveTabIndex",
meta: {
notShow: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
@ -43,8 +52,17 @@ export const routesMesh: Array<RouteRecordRaw> = [
path: "/mesh/controlPanel",
name: "ControlPanel",
meta: {
notShow: false,
title: "controlPanel",
headPath: "/mesh/controlPanel",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mesh/controlPanel/tab/:activeTabIndex",
name: "ControlPanelActiveTabIndex",
meta: {
notShow: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
@ -53,10 +71,21 @@ export const routesMesh: Array<RouteRecordRaw> = [
path: "/mesh/dataPanel",
name: "DataPanel",
meta: {
notShow: false,
title: "dataPanel",
headPath: "/mesh/dataPanel",
},
component: () => import("@/views/Layer.vue"),
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mesh/dataPanel/tab/:activeTabIndex",
name: "DataPanelActiveTabIndex",
meta: {
notShow: true,
title: "dataPanelActiveTabIndex",
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
],
},

View File

@ -33,6 +33,8 @@ interface AppState {
autoRefresh: boolean;
pageTitle: string;
version: string;
isMobile: boolean;
reloadTimer: Nullable<any>;
}
export const appStore = defineStore({
@ -51,6 +53,8 @@ export const appStore = defineStore({
autoRefresh: false,
pageTitle: "",
version: "",
isMobile: false,
reloadTimer: null,
}),
getters: {
duration(): Duration {
@ -122,6 +126,9 @@ export const appStore = defineStore({
this.utcHour = utcHour;
this.utc = `${utcHour}:${utcMin}`;
},
setIsMobile(mode: boolean) {
this.isMobile = mode;
},
setEventStack(funcs: (() => void)[]): void {
this.eventStack = funcs;
},
@ -169,6 +176,9 @@ export const appStore = defineStore({
this.version = res.data.data.version;
return res.data;
},
setReloadTimer(timer: any): void {
this.reloadTimer = timer;
},
},
});
export function useAppStoreWithOut(): any {

View File

@ -20,10 +20,8 @@ import { LayoutConfig } from "@/types/dashboard";
import graphql from "@/graphql";
import query from "@/graphql/fetch";
import { DashboardItem } from "@/types/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { NewControl, TextConfig } from "../data";
import { Duration } from "@/types/app";
import { AxiosResponse } from "axios";
import { ElMessage } from "element-plus";
import { useI18n } from "vue-i18n";
@ -35,7 +33,6 @@ interface DashboardState {
entity: string;
layerId: string;
activedGridItem: string;
durationTime: Duration;
selectorStore: any;
showTopology: boolean;
fullView: boolean;
@ -56,7 +53,6 @@ export const dashboardStore = defineStore({
entity: "",
layerId: "",
activedGridItem: "",
durationTime: useAppStoreWithOut().durationTime,
selectorStore: useSelectorStore(),
showTopology: false,
showLogTools: false,
@ -123,12 +119,7 @@ export const dashboardStore = defineStore({
: 3,
};
}
if (
type === "Trace" ||
type === "Profile" ||
type === "Log" ||
type === "Ebpf"
) {
if (["Trace", "Profile", "Log", "DemandLog", "Ebpf"].includes(type)) {
newItem.h = 36;
}
if (type === "Text") {
@ -183,7 +174,7 @@ export const dashboardStore = defineStore({
showDepth: true,
};
}
if (type === "Trace" || type === "Profile" || type === "Log") {
if (["Trace", "Profile", "Log", "DemandLog", "Ebpf"].includes(type)) {
newItem.h = 32;
}
if (type === "Text") {

View File

@ -0,0 +1,126 @@
/**
* 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 { Instance } from "@/types/selector";
import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { Conditions, Log } from "@/types/demand-log";
interface DemandLogState {
containers: Instance[];
instances: Instance[];
conditions: Conditions;
selectorStore: any;
logs: Log[];
loadLogs: boolean;
message: string;
total: number;
}
export const demandLogStore = defineStore({
id: "demandLog",
state: (): DemandLogState => ({
containers: [{ label: "", value: "" }],
instances: [{ value: "", label: "" }],
conditions: {
container: "",
serviceInstanceId: "",
duration: useAppStoreWithOut().durationTime,
},
selectorStore: useSelectorStore(),
logs: [],
loadLogs: false,
message: "",
total: 0,
}),
actions: {
setLogCondition(data: Conditions) {
this.conditions = { ...this.conditions, ...data };
},
setLogs(logs: Log[], message?: string) {
this.logs = logs;
this.message = message || "";
},
async getInstances(id: string) {
const serviceId = this.selectorStore.currentService
? this.selectorStore.currentService.id
: id;
const res: AxiosResponse = await graphql.query("queryInstances").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
});
if (res.data.errors) {
return res.data;
}
this.instances = res.data.data.pods || [];
return res.data;
},
async getContainers(serviceInstanceId: string) {
if (!serviceInstanceId) {
return new Promise((resolve) =>
resolve({ errors: "No service instance" })
);
}
const condition = {
serviceInstanceId,
};
const res: AxiosResponse = await graphql
.query("fetchContainers")
.params({ condition });
if (res.data.errors) {
return res.data;
}
if (res.data.data.containers.errorReason) {
this.containers = [{ label: "", value: "" }];
return res.data;
}
this.containers = res.data.data.containers.containers.map((d: string) => {
return { label: d, value: d };
});
return res.data;
},
async getDemandLogs() {
this.loadLogs = true;
const res: AxiosResponse = await graphql
.query("fetchDemandPodLogs")
.params({ condition: this.conditions });
this.loadLogs = false;
if (res.data.errors) {
return res.data;
}
if (res.data.data.logs.errorReason) {
this.setLogs("", res.data.data.logs.errorReason);
return res.data;
}
this.total = res.data.data.logs.logs.length;
const logs = res.data.data.logs.logs
.map((d: Log) => d.content)
.join("\n");
this.setLogs(logs);
return res.data;
},
},
});
export function useDemandLogStore(): any {
return demandLogStore(store);
}

View File

@ -15,21 +15,18 @@
* limitations under the License.
*/
import { defineStore } from "pinia";
import { Duration, Option } from "@/types/app";
import { Option } from "@/types/app";
import {
EBPFTaskCreationRequest,
EBPFProfilingSchedule,
EBPFTaskList,
AnalyzationTrees,
} from "@/types/ebpf";
import { Trace, Span } from "@/types/trace";
import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
interface EbpfStore {
durationTime: Duration;
taskList: EBPFTaskList[];
eBPFSchedules: EBPFProfilingSchedule[];
currentSchedule: EBPFProfilingSchedule | Record<string, never>;
@ -37,12 +34,13 @@ interface EbpfStore {
labels: Option[];
couldProfiling: boolean;
tip: string;
selectedTask: Recordable<EBPFTaskList>;
aggregateType: string;
}
export const ebpfStore = defineStore({
id: "eBPF",
state: (): EbpfStore => ({
durationTime: useAppStoreWithOut().durationTime,
taskList: [],
eBPFSchedules: [],
currentSchedule: {},
@ -50,14 +48,19 @@ export const ebpfStore = defineStore({
labels: [{ value: "", label: "" }],
couldProfiling: false,
tip: "",
selectedTask: {},
aggregateType: "COUNT",
}),
actions: {
setCurrentSpan(span: Span) {
this.currentSpan = span;
setSelectedTask(task: EBPFTaskList) {
this.selectedTask = task;
},
setCurrentSchedule(s: Trace) {
setCurrentSchedule(s: EBPFProfilingSchedule) {
this.currentSchedule = s;
},
setAnalyzeTrees(tree: AnalyzationTrees[]) {
this.analyzeTrees = tree;
},
async getCreateTaskData(serviceId: string) {
const res: AxiosResponse = await graphql
.query("getCreateTaskData")
@ -85,6 +88,9 @@ export const ebpfStore = defineStore({
return res.data;
},
async getTaskList(serviceId: string) {
if (!serviceId) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql
.query("getEBPFTasks")
.params({ serviceId });
@ -93,18 +99,20 @@ export const ebpfStore = defineStore({
if (res.data.errors) {
return res.data;
}
this.taskList = res.data.data.queryEBPFTasks.reverse() || [];
this.taskList = res.data.data.queryEBPFTasks || [];
if (!this.taskList.length) {
return res.data;
}
this.getEBPFSchedules({ taskId: this.taskList[0].taskId });
return res.data;
},
async getEBPFSchedules(params: { taskId: string; duration?: Duration }) {
const duration = useAppStoreWithOut().durationTime;
async getEBPFSchedules(params: { taskId: string }) {
if (!params.taskId) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql
.query("getEBPFSchedules")
.params({ ...params, duration });
.params({ ...params });
if (res.data.errors) {
this.eBPFSchedules = [];
@ -116,14 +124,22 @@ export const ebpfStore = defineStore({
this.eBPFSchedules = eBPFSchedules;
if (!eBPFSchedules.length) {
this.eBPFSchedules = [];
}
this.analyzeTrees = [];
}
return res.data;
},
async getEBPFAnalyze(params: {
scheduleIdList: string[];
timeRanges: Array<{ start: number; end: number }>;
aggregateType: string;
}) {
this.aggregateType = params.aggregateType;
if (!params.scheduleIdList.length) {
return new Promise((resolve) => resolve({}));
}
if (!params.timeRanges.length) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql
.query("getEBPFResult")
.params(params);
@ -142,7 +158,7 @@ export const ebpfStore = defineStore({
this.analyzeTrees = [];
return res.data;
}
this.analyzeTrees = analysisEBPFResult.trees[0].elements;
this.analyzeTrees = analysisEBPFResult.trees;
return res.data;
},
},

View File

@ -19,17 +19,16 @@ import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
import { Event, QueryEventCondition } from "@/types/events";
import { Instance, Endpoint, Service } from "@/types/selector";
import { Instance, Endpoint } from "@/types/selector";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
interface eventState {
loading: boolean;
events: Event[];
total: number;
services: Service[];
instances: Instance[];
endpoints: Endpoint[];
condition: QueryEventCondition | any;
condition: Nullable<QueryEventCondition>;
}
export const eventStore = defineStore({
@ -37,34 +36,18 @@ export const eventStore = defineStore({
state: (): eventState => ({
loading: false,
events: [],
total: 0,
services: [{ value: "", label: "All" }],
instances: [{ value: "", label: "All" }],
endpoints: [{ value: "", label: "All" }],
condition: {
time: useAppStoreWithOut().durationTime,
paging: { pageNum: 1, pageSize: 15, needTotal: true },
},
condition: null,
}),
actions: {
setEventCondition(data: any) {
this.condition = { ...this.condition, ...data };
setEventCondition(data: QueryEventCondition) {
this.condition = data;
},
async getServices(layer: string) {
if (!layer) {
this.services = [{ value: "", label: "All" }];
return new Promise((resolve) => resolve([]));
}
const res: AxiosResponse = await graphql.query("queryServices").params({
layer,
});
if (res.data.errors) {
return res.data;
}
this.services = res.data.data.services;
return res.data;
},
async getInstances(serviceId: string) {
async getInstances() {
const serviceId = useSelectorStore().currentService
? useSelectorStore().currentService.id
: "";
const res: AxiosResponse = await graphql.query("queryInstances").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
@ -78,7 +61,13 @@ export const eventStore = defineStore({
];
return res.data;
},
async getEndpoints(serviceId: string) {
async getEndpoints() {
const serviceId = useSelectorStore().currentService
? useSelectorStore().currentService.id
: "";
if (!serviceId) {
return;
}
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
serviceId,
duration: useAppStoreWithOut().durationTime,
@ -94,9 +83,12 @@ export const eventStore = defineStore({
},
async getEvents() {
this.loading = true;
const res: AxiosResponse = await graphql
.query("queryEvents")
.params({ condition: this.condition });
const res: AxiosResponse = await graphql.query("queryEvents").params({
condition: {
...this.condition,
time: useAppStoreWithOut().durationTime,
},
});
this.loading = false;
if (res.data.errors) {
return res.data;
@ -115,7 +107,6 @@ export const eventStore = defineStore({
return item;
}
);
this.total = res.data.data.fetchEvents.total;
}
return res.data;
},

View File

@ -15,7 +15,6 @@
* limitations under the License.
*/
import { defineStore } from "pinia";
import { Duration } from "@/types/app";
import { Instance, Endpoint, Service } from "@/types/selector";
import { ServiceLogColumn, BrowserLogColumn } from "@/types/log-column";
import { ServiceLogConstants, BrowserLogConstants } from "../data";
@ -33,11 +32,9 @@ interface LogState {
serviceLogColumn: ServiceLogColumn[];
browserLogColumn: BrowserLogColumn[];
conditions: any;
durationTime: Duration;
selectorStore: any;
supportQueryLogsByKeywords: boolean;
logs: any[];
logsTotal: number;
loadLogs: boolean;
}
@ -49,15 +46,13 @@ export const logStore = defineStore({
endpoints: [{ value: "0", label: "All" }],
conditions: {
queryDuration: useAppStoreWithOut().durationTime,
paging: { pageNum: 1, pageSize: 15, needTotal: true },
paging: { pageNum: 1, pageSize: 15 },
},
serviceLogColumn: [...ServiceLogConstants],
browserLogColumn: [...BrowserLogConstants],
supportQueryLogsByKeywords: true,
durationTime: useAppStoreWithOut().durationTime,
selectorStore: useSelectorStore(),
logs: [],
logsTotal: 0,
loadLogs: false,
}),
actions: {
@ -83,6 +78,12 @@ export const logStore = defineStore({
setLogCondition(data: any) {
this.conditions = { ...this.conditions, ...data };
},
resetCondition() {
this.conditions = {
queryDuration: useAppStoreWithOut().durationTime,
paging: { pageNum: 1, pageSize: 15 },
};
},
async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({
layer,
@ -99,7 +100,7 @@ export const logStore = defineStore({
: id;
const res: AxiosResponse = await graphql.query("queryInstances").params({
serviceId,
duration: this.durationTime,
duration: useAppStoreWithOut().durationTime,
});
if (res.data.errors) {
@ -117,7 +118,7 @@ export const logStore = defineStore({
: id;
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
serviceId,
duration: this.durationTime,
duration: useAppStoreWithOut().durationTime,
keyword: keyword || "",
});
if (res.data.errors) {
@ -159,7 +160,6 @@ export const logStore = defineStore({
}
this.logs = res.data.data.queryLogs.logs;
this.logsTotal = res.data.data.queryLogs.total;
return res.data;
},
async getBrowserLogs() {
@ -173,7 +173,20 @@ export const logStore = defineStore({
return res.data;
}
this.logs = res.data.data.queryBrowserErrorLogs.logs;
this.logsTotal = res.data.data.queryBrowserErrorLogs.total;
return res.data;
},
async getLogTagKeys() {
const res: AxiosResponse = await graphql
.query("queryLogTagKeys")
.params({ duration: useAppStoreWithOut().durationTime });
return res.data;
},
async getLogTagValues(tagKey: string) {
const res: AxiosResponse = await graphql
.query("queryLogTagValues")
.params({ tagKey, duration: useAppStoreWithOut().durationTime });
return res.data;
},
},

View File

@ -15,7 +15,6 @@
* limitations under the License.
*/
import { defineStore } from "pinia";
import { Duration } from "@/types/app";
import { Endpoint } from "@/types/selector";
import {
TaskListItem,
@ -33,7 +32,6 @@ import { useAppStoreWithOut } from "@/store/modules/app";
interface ProfileState {
endpoints: Endpoint[];
taskEndpoints: Endpoint[];
durationTime: Duration;
condition: { serviceId: string; endpointName: string };
taskList: TaskListItem[];
segmentList: Trace[];
@ -50,7 +48,6 @@ export const profileStore = defineStore({
state: (): ProfileState => ({
endpoints: [{ value: "", label: "All" }],
taskEndpoints: [{ value: "", label: "All" }],
durationTime: useAppStoreWithOut().durationTime,
condition: { serviceId: "", endpointName: "" },
taskList: [],
segmentList: [],
@ -80,7 +77,7 @@ export const profileStore = defineStore({
async getEndpoints(serviceId: string, keyword?: string) {
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
serviceId,
duration: this.durationTime,
duration: useAppStoreWithOut().durationTime,
keyword: keyword || "",
});
if (res.data.errors) {
@ -92,7 +89,7 @@ export const profileStore = defineStore({
async getTaskEndpoints(serviceId: string, keyword?: string) {
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
serviceId,
duration: this.durationTime,
duration: useAppStoreWithOut().durationTime,
keyword: keyword || "",
});
if (res.data.errors) {
@ -122,6 +119,9 @@ export const profileStore = defineStore({
return res.data;
},
async getSegmentList(params: { taskID: string }) {
if (!params.taskID) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql
.query("getProfileTaskSegmentList")
.params(params);
@ -148,6 +148,9 @@ export const profileStore = defineStore({
return res.data;
},
async getSegmentSpans(params: { segmentId: string }) {
if (!params.segmentId) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql
.query("queryProfileSegment")
.params(params);
@ -161,7 +164,13 @@ export const profileStore = defineStore({
this.analyzeTrees = [];
return res.data;
}
this.segmentSpans = segment.spans;
this.segmentSpans = segment.spans.map((d: SegmentSpan) => {
return {
...d,
segmentId: this.currentSegment.segmentId,
traceId: this.currentSegment.traceIds[0],
};
});
if (!(segment.spans && segment.spans.length)) {
this.analyzeTrees = [];
return res.data;
@ -174,6 +183,12 @@ export const profileStore = defineStore({
segmentId: string;
timeRanges: Array<{ start: number; end: number }>;
}) {
if (!params.segmentId) {
return new Promise((resolve) => resolve({}));
}
if (!params.timeRanges.length) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql
.query("getProfileAnalyze")
.params(params);

View File

@ -15,13 +15,11 @@
* limitations under the License.
*/
import { defineStore } from "pinia";
import { Duration } from "@/types/app";
import { Service, Instance, Endpoint } from "@/types/selector";
import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
interface SelectorState {
services: Service[];
destServices: Service[];
@ -31,7 +29,6 @@ interface SelectorState {
currentDestService: Nullable<Service>;
currentDestPod: Nullable<Instance | Endpoint>;
destPods: Array<Instance | Endpoint>;
durationTime: Duration;
}
export const selectorStore = defineStore({
@ -45,7 +42,6 @@ export const selectorStore = defineStore({
currentPod: null,
currentDestService: null,
currentDestPod: null,
durationTime: useAppStoreWithOut().durationTime,
}),
actions: {
setCurrentService(service: Nullable<Service>) {
@ -86,7 +82,7 @@ export const selectorStore = defineStore({
}
const res: AxiosResponse = await graphql.query("queryInstances").params({
serviceId,
duration: this.durationTime,
duration: useAppStoreWithOut().durationTime,
});
if (!res.data.errors) {
if (param && param.isRelation) {
@ -112,7 +108,7 @@ export const selectorStore = defineStore({
}
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
serviceId,
duration: this.durationTime,
duration: useAppStoreWithOut().durationTime,
keyword: params.keyword || "",
limit: params.limit,
});

View File

@ -15,7 +15,6 @@
* limitations under the License.
*/
import { defineStore } from "pinia";
import { Duration } from "@/types/app";
import { Instance, Endpoint, Service } from "@/types/selector";
import { Trace, Span } from "@/types/trace";
import { store } from "@/store";
@ -28,19 +27,10 @@ interface TraceState {
instances: Instance[];
endpoints: Endpoint[];
traceList: Trace[];
activeFilter: string;
displayMode: string;
currentView: string;
traceTotal: number;
traceSpans: Span[];
currentTrace: Trace | any;
conditions: any;
traceSpanLogs: any[];
traceSpanLogsTotal: number;
// traceListErrors: string;
// traceSpanErrors: string;
// traceSpanLogErrors: string;
durationTime: Duration;
selectorStore: any;
}
@ -50,27 +40,21 @@ export const traceStore = defineStore({
services: [{ value: "0", label: "All" }],
instances: [{ value: "0", label: "All" }],
endpoints: [{ value: "0", label: "All" }],
displayMode: "List",
currentView: "traceList",
activeFilter: "",
traceList: [],
traceSpans: [],
traceTotal: 0,
currentTrace: {},
conditions: {
queryDuration: useAppStoreWithOut().durationTime,
traceState: "ALL",
queryOrder: "BY_START_TIME",
paging: { pageNum: 1, pageSize: 15, needTotal: true },
paging: { pageNum: 1, pageSize: 20 },
},
traceSpanLogs: [],
traceSpanLogsTotal: 0,
durationTime: useAppStoreWithOut().durationTime,
selectorStore: useSelectorStore(),
}),
actions: {
setTraceCondition(data: any) {
this.condition = { ...this.condition, ...data };
this.conditions = { ...this.conditions, ...data };
},
setDisplayMode(data: string) {
this.displayMode = data;
@ -104,7 +88,7 @@ export const traceStore = defineStore({
: id;
const res: AxiosResponse = await graphql.query("queryInstances").params({
serviceId: serviceId,
duration: this.durationTime,
duration: useAppStoreWithOut().durationTime,
});
if (res.data.errors) {
@ -119,7 +103,7 @@ export const traceStore = defineStore({
: id;
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
serviceId,
duration: this.durationTime,
duration: useAppStoreWithOut().durationTime,
keyword: keyword || "",
});
if (res.data.errors) {
@ -131,12 +115,11 @@ export const traceStore = defineStore({
async getTraces() {
const res: AxiosResponse = await graphql
.query("queryTraces")
.params({ condition: this.condition });
.params({ condition: this.conditions });
if (res.data.errors) {
return res.data;
}
if (!res.data.data.data.traces.length) {
this.traceTotal = 0;
this.traceList = [];
this.setCurrentTrace({});
this.setTraceSpans([]);
@ -149,7 +132,6 @@ export const traceStore = defineStore({
});
return d;
});
this.traceTotal = res.data.data.data.total;
this.setCurrentTrace(res.data.data.data.traces[0] || {});
return res.data;
},
@ -169,11 +151,23 @@ export const traceStore = defineStore({
.params(params);
if (res.data.errors) {
this.traceSpanLogs = [];
this.traceSpanLogsTotal = 0;
return res.data;
}
this.traceSpanLogs = res.data.data.queryLogs.logs || [];
this.traceSpanLogsTotal = res.data.data.queryLogs.total;
return res.data;
},
async getTagKeys() {
const res: AxiosResponse = await graphql
.query("queryTraceTagKeys")
.params({ duration: useAppStoreWithOut().durationTime });
return res.data;
},
async getTagValues(tagKey: string) {
const res: AxiosResponse = await graphql
.query("queryTraceTagValues")
.params({ tagKey, duration: useAppStoreWithOut().durationTime });
return res.data;
},
},

View File

@ -15,10 +15,7 @@
* limitations under the License.
*/
.show-xs,
.show-sm,
.show-md,
.show-lg {
.show-xs {
display: none !important;
}
@media (max-width: 767px) {
@ -30,145 +27,6 @@
display: none !important;
}
}
@media (min-width: 768px) and (max-width: 1023px) {
.show-sm {
display: block !important;
}
.hide-sm {
display: none !important;
}
}
@media (min-width: 1024px) and (max-width: 1279px) {
.show-md {
display: block !important;
}
.hide-md {
display: none !important;
}
}
@media (min-width: 1280px) {
.show-lg {
display: block !important;
}
.hide-lg {
display: none !important;
}
}
.g-xs-1,
.g-xs-2,
.g-xs-3,
.g-xs-4,
.g-xs-5,
.g-xs-6,
.g-xs-7,
.g-xs-8,
.g-xs-9,
.g-xs-10,
.g-xs-11,
.g-xs-12 {
float: left;
min-height: 1px;
}
.g-xs-12 {
width: 100%;
}
.g-xs-11 {
width: 91.666%;
}
.g-xs-10 {
width: 83.333%;
}
.g-xs-9 {
width: 75%;
}
.g-xs-8 {
width: 66.666%;
}
.g-xs-7 {
width: 58.333%;
}
.g-xs-6 {
width: 50%;
}
.g-xs-5 {
width: 41.666%;
}
.g-xs-4 {
width: 33.333%;
}
.g-xs-3 {
width: 25%;
}
.g-xs-2 {
width: 16.666%;
}
.g-xs-1 {
width: 8.333%;
}
.g-xs-space-12 {
margin-left: 100%;
}
.g-xs-space-11 {
margin-left: 91.666%;
}
.g-xs-space-10 {
margin-left: 83.333%;
}
.g-xs-space-9 {
margin-left: 75%;
}
.g-xs-space-8 {
margin-left: 66.666%;
}
.g-xs-space-7 {
margin-left: 58.333%;
}
.g-xs-space-6 {
margin-left: 50%;
}
.g-xs-space-5 {
margin-left: 41.666%;
}
.g-xs-space-4 {
margin-left: 33.333%;
}
.g-xs-space-3 {
margin-left: 25%;
}
.g-xs-space-2 {
margin-left: 16.666%;
}
.g-xs-space-1 {
margin-left: 8.333%;
}
@media (min-width: 768px) {
.g-sm-1,
.g-sm-2,
@ -233,278 +91,4 @@
.g-sm-1 {
width: 8.333%;
}
.g-sm-space-12 {
margin-left: 100%;
}
.g-sm-space-11 {
margin-left: 91.666%;
}
.g-sm-space-10 {
margin-left: 83.333%;
}
.g-sm-space-9 {
margin-left: 75%;
}
.g-sm-space-8 {
margin-left: 66.666%;
}
.g-sm-space-7 {
margin-left: 58.333%;
}
.g-sm-space-6 {
margin-left: 50%;
}
.g-sm-space-5 {
margin-left: 41.666%;
}
.g-sm-space-4 {
margin-left: 33.333%;
}
.g-sm-space-3 {
margin-left: 25%;
}
.g-sm-space-2 {
margin-left: 16.666%;
}
.g-sm-space-1 {
margin-left: 8.333%;
}
}
@media (min-width: 1024px) {
.g-md-1,
.g-md-2,
.g-md-3,
.g-md-4,
.g-md-5,
.g-md-6,
.g-md-7,
.g-md-8,
.g-md-9,
.g-md-10,
.g-md-11,
.g-md-12 {
float: left;
min-height: 1px;
}
.g-md-12 {
width: 100%;
}
.g-md-11 {
width: 91.666%;
}
.g-md-10 {
width: 83.333%;
}
.g-md-9 {
width: 75%;
}
.g-md-8 {
width: 66.666%;
}
.g-md-7 {
width: 58.333%;
}
.g-md-6 {
width: 50%;
}
.g-md-5 {
width: 41.666%;
}
.g-md-4 {
width: 33.333%;
}
.g-md-3 {
width: 25%;
}
.g-md-2 {
width: 16.666%;
}
.g-md-1 {
width: 8.333%;
}
.g-md-space-12 {
margin-left: 100%;
}
.g-md-space-11 {
margin-left: 91.666%;
}
.g-md-space-10 {
margin-left: 83.333%;
}
.g-md-space-9 {
margin-left: 75%;
}
.g-md-space-8 {
margin-left: 66.666%;
}
.g-md-space-7 {
margin-left: 58.333%;
}
.g-md-space-6 {
margin-left: 50%;
}
.g-md-space-5 {
margin-left: 41.666%;
}
.g-md-space-4 {
margin-left: 33.333%;
}
.g-md-space-3 {
margin-left: 25%;
}
.g-md-space-2 {
margin-left: 16.666%;
}
.g-md-space-1 {
margin-left: 8.333%;
}
}
@media (min-width: 1280px) {
.g-lg-1,
.g-lg-2,
.g-lg-3,
.g-lg-4,
.g-lg-5,
.g-lg-6,
.g-lg-7,
.g-lg-8,
.g-lg-9,
.g-lg-10,
.g-lg-11,
.g-lg-12 {
float: left;
min-height: 1px;
}
.g-lg-12 {
width: 100%;
}
.g-lg-11 {
width: 91.666%;
}
.g-lg-10 {
width: 83.333%;
}
.g-lg-9 {
width: 75%;
}
.g-lg-8 {
width: 66.666%;
}
.g-lg-7 {
width: 58.333%;
}
.g-lg-6 {
width: 50%;
}
.g-lg-5 {
width: 41.666%;
}
.g-lg-4 {
width: 33.333%;
}
.g-lg-3 {
width: 25%;
}
.g-lg-2 {
width: 16.666%;
}
.g-lg-1 {
width: 8.333%;
}
.g-lg-space-12 {
margin-left: 100%;
}
.g-lg-space-11 {
margin-left: 91.666%;
}
.g-lg-space-10 {
margin-left: 83.333%;
}
.g-lg-space-9 {
margin-left: 75%;
}
.g-lg-space-8 {
margin-left: 66.666%;
}
.g-lg-space-7 {
margin-left: 58.333%;
}
.g-lg-space-6 {
margin-left: 50%;
}
.g-lg-space-5 {
margin-left: 41.666%;
}
.g-lg-space-4 {
margin-left: 33.333%;
}
.g-lg-space-3 {
margin-left: 25%;
}
.g-lg-space-2 {
margin-left: 16.666%;
}
.g-lg-space-1 {
margin-left: 8.333%;
}
}

View File

View File

@ -56,15 +56,15 @@
}
.blue {
color: #6897BB;
color: #6897bb;
}
.purple {
color: #9876AA;
color: #9876aa;
}
.yellow {
color: #FFC66D;
color: #ffc66d;
}
.grey {
@ -84,15 +84,15 @@
}
.bg-blue {
background-color: #6897BB;
background-color: #6897bb;
}
.bg-purple {
background-color: #9876AA;
background-color: #9876aa;
}
.bg-yellow {
background-color: #FFC66D;
background-color: #ffc66d;
}
.bg-grey {
@ -171,3 +171,21 @@
color: #ddd;
}
}
.scroll_bar_style::-webkit-scrollbar {
width: 9px;
height: 4px;
background-color: #eee;
}
.scroll_bar_style::-webkit-scrollbar-track {
background-color: #eee;
border-radius: 3px;
box-shadow: inset 0 0 6px #ccc;
}
.scroll_bar_style::-webkit-scrollbar-thumb {
border-radius: 3px;
box-shadow: inset 0 0 6px #ccc;
background-color: #aaa;
}

View File

@ -29,6 +29,7 @@ body {
html,
body {
height: 100%;
overflow-y: hidden;
}
div,
@ -153,3 +154,38 @@ pre {
.switch {
margin: 0 5px;
}
div.vis-tooltip {
max-width: 600px;
overflow: hidden;
background-color: #fff !important;
white-space: normal !important;
font-size: 12px !important;
}
.vis-item {
cursor: pointer;
height: 17px;
}
.vis-item.Error {
background-color: #e66;
opacity: 0.8;
border-color: #e66;
color: #fff !important;
}
.vis-item.Normal {
background-color: #fac858;
border-color: #fac858;
color: #666 !important;
}
.vis-item .vis-item-content {
padding: 0 5px !important;
}
.vis-item.vis-selected.Error,
.vis-item.vis-selected.Normal {
color: #1a1a1a !important;
}

4
src/types/app.d.ts vendored
View File

@ -28,3 +28,7 @@ export interface DurationTime {
end: string;
step: string;
}
export type Paging = {
pageNum: number;
pageSize: number;
};

View File

@ -39,9 +39,9 @@ export interface LayoutConfig {
}
export type MetricConfigOpt = {
unit: string;
label: string;
calculation: string;
unit?: string;
label?: string;
calculation?: string;
labelsIndex: string;
sortOrder: string;
topN?: number;

31
src/types/demand-log.ts Normal file
View File

@ -0,0 +1,31 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { DurationTime } from "./app";
export interface Conditions {
container: string;
serviceInstanceId: string;
duration: DurationTime;
keywordsOfContent?: string[];
excludingKeywordsOfContent?: string;
}
export interface Log {
content: string;
timestamp: number;
contentType: string;
}

5
src/types/ebpf.d.ts vendored
View File

@ -50,10 +50,9 @@ export type Process = {
serviceName: string;
instanceId: string;
instanceName: string;
layer: string;
agentId: string;
detectType: string;
attributes: { name: string; value: string };
attributes: { name: string; value: string }[];
labels: string[];
};
export type StackElement = {
@ -66,6 +65,8 @@ export type StackElement = {
stackType: string;
value: number;
children?: StackElement[];
rateOfRoot?: string;
rateOfParent: string;
};
export type AnalyzationTrees = {
id: string;

View File

@ -0,0 +1,17 @@
/**
* 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.
*/
declare module "monaco-editor";

View File

@ -26,7 +26,6 @@ export type Service = {
export type Instance = {
value: string;
label: string;
layer?: string;
language?: string;
instanceUUID?: string;
attributes?: { name: string; value: string }[];

View File

@ -46,8 +46,15 @@ export interface Span {
children?: Span[];
tags?: Array<Map<string, string>>;
logs?: log[];
parentSegmentId?: string;
refs?: Ref[];
}
export type Ref = {
type: string;
parentSegmentId: string;
parentSpanId: number;
traceId: string;
};
export interface log {
time: number;
data: Map<string, string>;

View File

@ -24,7 +24,7 @@ import Header from "./alarm/Header.vue";
import Content from "./alarm/Content.vue";
const appStore = useAppStoreWithOut();
appStore.setPageTitle("Alarm");
appStore.setPageTitle("Alerting");
</script>
<style lang="scss" scoped>
.alarm {

View File

@ -69,7 +69,7 @@ limitations under the License. -->
</div>
</template>
<script lang="ts" setup>
import { ref, reactive } from "vue";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useAppStoreWithOut } from "@/store/modules/app";
import timeFormat from "@/utils/timeFormat";
@ -78,9 +78,6 @@ import Selector from "@/components/Selector.vue";
const { t, locale } = useI18n();
const appStore = useAppStoreWithOut();
const state = reactive<{ timer: ReturnType<typeof setInterval> | null }>({
timer: null,
});
const lang = ref<string>(locale.value || "en");
const autoTime = ref<number>(6);
const auto = ref<boolean>(appStore.autoRefresh || false);
@ -101,10 +98,10 @@ const handleAuto = () => {
appStore.setAutoRefresh(auto.value);
if (auto.value) {
handleReload();
state.timer = setInterval(handleReload, autoTime.value * 1000);
appStore.setReloadTimer(setInterval(handleReload, autoTime.value * 1000));
} else {
if (state.timer) {
clearInterval(state.timer);
if (appStore.reloadTimer) {
clearInterval(appStore.reloadTimer);
}
}
};
@ -112,12 +109,12 @@ const changeAutoTime = () => {
if (autoTime.value < 1) {
return;
}
if (state.timer) {
clearInterval(state.timer);
if (appStore.reloadTimer) {
clearInterval(appStore.reloadTimer);
}
if (auto.value) {
handleReload();
state.timer = setInterval(handleReload, autoTime.value * 1000);
appStore.setReloadTimer(setInterval(handleReload, autoTime.value * 1000));
}
};
const setLang = (): void => {
@ -194,10 +191,11 @@ const setUTCMin = () => {
}
.label {
width: 160px;
width: 180px;
display: inline-block;
font-weight: 500;
color: #000;
line-height: 25px;
}
}
</style>

View File

@ -39,8 +39,8 @@ limitations under the License. -->
<el-pagination
v-model:currentPage="pageNum"
v-model:page-size="pageSize"
layout="prev, jumper, total, next"
:total="alarmStore.total"
layout="prev, pager, next"
:total="total"
@current-change="changePage"
:pager-count="5"
small
@ -55,7 +55,7 @@ limitations under the License. -->
</nav>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import ConditionTags from "@/views/components/ConditionTags.vue";
import { AlarmOptions } from "./data";
@ -70,6 +70,11 @@ const pageSize = 20;
const entity = ref<string>("");
const keyword = ref<string>("");
const pageNum = ref<number>(1);
const total = computed(() =>
alarmStore.alarms.length === pageSize
? pageSize * pageNum.value + 1
: pageSize * pageNum.value
);
refreshAlarms({ pageNum: 1 });
@ -79,7 +84,6 @@ async function refreshAlarms(param: { pageNum: number; tagsMap?: any }) {
paging: {
pageNum: param.pageNum,
pageSize,
needTotal: true,
},
tags: param.tagsMap,
};

View File

@ -13,73 +13,107 @@ 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="flex-h" :class="{ light: theme === 'light' }">
<div class="flex-h items-center mr-5">
<span class="sm grey" v-show="theme === 'dark'">{{ t("tags") }}: </span>
<span v-if="tagsList.length" class="trace-tags">
<!-- :style="type === 'LOG' ? `min-width: 122px;` : ''" -->
<div>
<span class="grey">{{ t("tags") }}: </span>
<span
v-if="tagsList.length"
class="trace-tags"
:style="type === 'LOG' ? `min-width: 122px;` : ''"
>
<span class="selected" v-for="(item, index) in tagsList" :key="index">
<span>{{ item }}</span>
<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')"
/>
<span class="tags-tip">
<span v-else>
<el-input
size="small"
v-model="tags"
class="trace-new-tag"
@click="showClick"
/>
<el-dropdown
ref="dropdownTag"
trigger="contextmenu"
:hide-on-click="false"
style="margin: 20px 0 0 -130px"
v-if="tagArr.length"
>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="(item, index) in tagArr" :key="index">
<span @click="selectTag(item)" class="tag-item">
{{ item }}
</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</span>
<span
class="tags-tip"
:class="type !== 'ALARM' && tagArr.length ? 'link-tips' : ''"
>
<a
v-if="false"
target="blank"
href="https://github.com/apache/skywalking/blob/master/docs/en/setup/backend/configuration-vocabulary.md"
>
{{ t("tagsLink") }}
</a>
<el-tooltip
:content="
t(
type === 'LOG'
? 'logTagsTip'
: type === 'TRACE'
? 'traceTagsTip'
: 'alarmTagsTip'
)
"
>
<el-tooltip :content="t(tipsMap[type])">
<span>
<Icon class="icon-help mr-5" iconName="help" size="middle" />
</span>
</el-tooltip>
<!-- <b v-if="type !== 'LOG'">{{ t("noticeTag") }}</b> -->
<b v-if="type === 'AL'">{{ t("noticeTag") }}</b>
</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, defineExpose } from "vue";
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useTraceStore } from "@/store/modules/trace";
import { useLogStore } from "@/store/modules/log";
import { ElMessage } from "element-plus";
import { useAppStoreWithOut } from "@/store/modules/app";
/*global defineEmits, defineProps */
/*global Nullable, defineEmits, defineProps */
const emit = defineEmits(["update"]);
defineProps({
const props = defineProps({
type: { type: String, default: "TRACE" },
});
const traceStore = useTraceStore();
const logStore = useLogStore();
const appStore = useAppStoreWithOut();
const { t } = useI18n();
const theme = ref<string>("dark");
const tags = ref<string>("");
const tagsList = ref<string[]>([]);
const tagArr = ref<string[]>([]);
const tagKeys = ref<string[]>([]);
const tipsMap = {
LOG: "logTagsTip",
TRACE: "traceTagsTip",
ALARM: "alarmTagsTip",
};
const dropdownTag = ref<Nullable<any>>(null);
defineExpose({
tagsList,
emptyTags
})
function emptyTags (){
tagsList.value = []
}
fetchTagKeys();
// defineExpose({
// tagsList,
// emptyTags
// })
// function emptyTags (){
// tagsList.value = []
// }
function removeTags(index: number) {
tagsList.value.splice(index, 1);
updateTags();
@ -102,11 +136,67 @@ function updateTags() {
});
emit("update", { tagsMap, tagsList: tagsList.value });
}
async function fetchTagKeys() {
let resp: any = {};
if (props.type === "TRACE") {
resp = await traceStore.getTagKeys();
} else {
resp = await logStore.getLogTagKeys();
}
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
tagArr.value = resp.data.tagKeys;
tagKeys.value = resp.data.tagKeys;
}
async function fetchTagValues() {
const param = tags.value.split("=")[0];
let resp: any = {};
if (props.type === "TRACE") {
resp = await traceStore.getTagValues(param);
} else {
resp = await logStore.getLogTagValues(param);
}
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
tagArr.value = resp.data.tagValues;
}
function selectTag(item: string) {
if (tags.value.includes("=")) {
tags.value += item;
addLabels();
tagArr.value = tagKeys.value;
dropdownTag.value.handleClose();
return;
}
tags.value = item + "=";
fetchTagValues();
}
function showClick() {
if (dropdownTag.value) {
dropdownTag.value.handleOpen();
}
}
watch(
() => appStore.durationTime,
() => {
fetchTagKeys();
}
);
</script>
<style lang="scss" scoped>
.items-center {
align-items: center;
}
.trace-tags {
padding: 1px 5px 0 0;
border-radius: 3px;
@ -131,7 +221,6 @@ function updateTags() {
padding: 2px 5px;
border-radius: 3px;
width: 250px;
margin-right: 3px;
}
.remove-icon {
@ -140,10 +229,20 @@ function updateTags() {
cursor: pointer;
}
.tag-item {
display: inline-block;
min-width: 210px;
}
.tags-tip {
color: #a7aebb;
}
.link-tips {
display: inline-block;
margin-left: 130px;
}
.light {
color: #3d444f;

View File

@ -146,9 +146,10 @@ import type { ElTable } from "element-plus";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useDashboardStore } from "@/store/modules/dashboard";
import router from "@/router";
import { DashboardItem } from "@/types/dashboard";
import { DashboardItem, LayoutConfig } from "@/types/dashboard";
import { saveFile, readFile } from "@/utils/file";
import { EntityType } from "./data";
import { isEmptyObject } from "@/utils/is";
/*global Nullable*/
const { t } = useI18n();
@ -221,12 +222,77 @@ function exportTemplates() {
const layout = JSON.parse(sessionStorage.getItem(key) || "{}");
return layout;
});
for (const item of templates) {
optimizeTemplate(item.configuration.children);
}
const name = `dashboards.json`;
saveFile(templates, name);
setTimeout(() => {
multipleTableRef.value!.clearSelection();
}, 2000);
}
function optimizeTemplate(
children: (LayoutConfig & { moved?: boolean; standard?: unknown })[]
) {
for (const child of children || []) {
delete child.moved;
delete child.activedTabIndex;
delete child.standard;
if (isEmptyObject(child.graph)) {
delete child.graph;
}
if (child.widget) {
if (child.widget.title === "") {
delete child.widget.title;
}
if (child.widget.tips === "") {
delete child.widget.tips;
}
}
if (isEmptyObject(child.widget)) {
delete child.widget;
}
if (!(child.metrics && child.metrics.length && child.metrics[0])) {
delete child.metrics;
}
if (
!(child.metricTypes && child.metricTypes.length && child.metricTypes[0])
) {
delete child.metricTypes;
}
if (child.metricConfig && child.metricConfig.length) {
child.metricConfig.forEach((c, index) => {
if (!c.calculation) {
delete c.calculation;
}
if (!c.unit) {
delete c.unit;
}
if (!c.label) {
delete c.label;
}
if (isEmptyObject(c)) {
(child.metricConfig || []).splice(index, 1);
}
});
}
if (!(child.metricConfig && child.metricConfig.length)) {
delete child.metricConfig;
}
if (child.type === "Tab") {
for (const item of child.children || []) {
optimizeTemplate(item.children);
}
}
if (
["Trace", "Topology", "Tab", "Profile", "Ebpf", "Log"].includes(
child.type
)
) {
delete child.widget;
}
}
}
function handleEdit(row: DashboardItem) {
dashboardStore.setMode(true);
dashboardStore.setEntity(row.entity);

View File

@ -41,7 +41,6 @@ import { useDashboardStore } from "@/store/modules/dashboard";
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const { selectedGrid } = dashboardStore;
const widget = dashboardStore.selectedGrid.widget || {};
const title = ref<string>(widget.title || "");
const tips = ref<string>(widget.tips || "");
@ -51,6 +50,7 @@ function updateWidgetConfig(param: { [key: string]: string }) {
if (!key) {
return;
}
const { selectedGrid } = dashboardStore;
const widget = {
...dashboardStore.selectedGrid.widget,
[key]: decodeURIComponent(param[key]),

View File

@ -0,0 +1,93 @@
<!-- 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="log-wrapper flex-v">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="dashboardStore.editMode"
>
<template #reference>
<span class="delete cp">
<Icon iconName="ellipsis_v" size="middle" class="operation" />
</span>
</template>
<div class="tools" @click="removeWidget">
<span>{{ t("delete") }}</span>
</div>
</el-popover>
<div class="header">
<Header />
</div>
<Content />
</div>
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/demand-log/Header.vue";
import Content from "../related/demand-log/Content.vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
activeIndex: { type: String, default: "" },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);
}
</script>
<style lang="scss" scoped>
.log-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
overflow: auto;
}
.delete {
position: absolute;
top: 5px;
right: 3px;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
min-width: 1024px;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
</style>

View File

@ -29,7 +29,7 @@ limitations under the License. -->
<span>{{ t("delete") }}</span>
</div>
</el-popover>
<Header />
<Header :needQuery="needQuery" />
<Content />
</div>
</template>
@ -47,6 +47,7 @@ const props = defineProps({
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();

View File

@ -0,0 +1,102 @@
<!-- 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="event-wrapper flex-v">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="dashboardStore.editMode"
>
<template #reference>
<span class="delete cp">
<Icon iconName="ellipsis_v" size="middle" class="operation" />
</span>
</template>
<div class="tools" @click="removeWidget">
<span>{{ t("delete") }}</span>
</div>
</el-popover>
<div class="header">
<Header :needQuery="needQuery" />
</div>
<div class="event">
<Content />
</div>
</div>
</template>
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/event/Header.vue";
import Content from "../related/event/Content.vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);
}
</script>
<style lang="scss" scoped>
.event-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
overflow: auto;
}
.delete {
position: absolute;
top: 5px;
right: 3px;
z-index: 9999;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
min-width: 1024px;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
.event {
width: 100%;
height: calc(100% - 80px);
}
</style>

View File

@ -13,10 +13,88 @@ 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>
<JbLog />
<div class="log-wrapper flex-v">
<el-popover
placement="bottom"
trigger="click"
:width="100"
v-if="dashboardStore.editMode"
>
<template #reference>
<span class="delete cp">
<Icon iconName="ellipsis_v" size="middle" class="operation" />
</span>
</template>
<div class="tools" @click="removeWidget">
<span>{{ t("delete") }}</span>
</div>
</el-popover>
<div class="header">
<Header :needQuery="needQuery" />
</div>
<div class="log">
<List />
</div>
</div>
</template>
<script lang="ts" setup>
import JbLog from "./JbLog.vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/log/Header.vue";
import List from "../related/log/List.vue";
/*global defineProps */
const props = defineProps({
data: {
type: Object,
default: () => ({}),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
function removeWidget() {
dashboardStore.removeControls(props.data);
}
</script>
<style lang="scss" scoped>
.log-wrapper {
width: 100%;
height: 100%;
font-size: 12px;
position: relative;
overflow: auto;
}
.delete {
position: absolute;
top: 5px;
right: 3px;
}
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
min-width: 1024px;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
.log {
width: 100%;
}
</style>

View File

@ -29,7 +29,7 @@ limitations under the License. -->
<span>{{ t("delete") }}</span>
</div>
</el-popover>
<Header />
<Header :needQuery="needQuery" />
<Content />
</div>
</template>
@ -47,6 +47,7 @@ const props = defineProps({
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();

View File

@ -13,8 +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 ref="tabRef" class="flex-h tab-header">
<div class="tabs">
<div class="flex-h tab-header">
<div class="tabs scroll_bar_style" @click="handleClick">
<span
v-for="(child, idx) in data.children || []"
:key="idx"
@ -26,55 +26,48 @@ limitations under the License. -->
v-model="child.name"
placeholder="Please input"
class="tab-name"
:readonly="isNaN(editTabIndex)"
:class="{ view: isNaN(editTabIndex) }"
:readonly="isNaN(editTabIndex) && !canEditTabName"
:class="{ view: !canEditTabName }"
/>
<Icon
v-show="activeTabIndex === idx"
size="sm"
iconName="cancel"
@click="deleteTabItem($event, idx)"
v-if="dashboardStore.editMode"
v-if="dashboardStore.editMode && canEditTabName"
/>
</span>
<span class="tab-icons">
<el-tooltip content="Copy Link" placement="bottom">
<i @click="copyLink">
<Icon size="middle" iconName="review-list" class="tab-icon" />
</i>
</el-tooltip>
</span>
<span class="tab-icons" v-if="dashboardStore.editMode">
<el-tooltip content="Add tab items" placement="bottom">
<i @click="addTabItem">
<Icon size="middle" iconName="add" />
<Icon size="middle" iconName="add_fill" class="tab-icon" />
</i>
</el-tooltip>
</span>
</div>
<div class="operations" v-if="dashboardStore.editMode">
<el-popover
placement="bottom"
trigger="click"
:width="200"
v-model:visible="showTools"
>
<template #reference>
<span>
<Icon
iconName="ellipsis_v"
size="middle"
class="operation"
@click="showTools = true"
/>
<el-dropdown placement="bottom" trigger="click" :width="200">
<span class="icon-operation">
<Icon iconName="ellipsis_v" size="middle" />
</span>
</template>
<div
class="tools"
@click="
canEditTabName = true;
showTools = false;
"
>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="canEditTabName = true">
<span class="edit-tab">{{ t("editTab") }}</span>
</div>
<div class="tools" @click="removeTab">
</el-dropdown-item>
<el-dropdown-item @click="removeTab">
<span>{{ t("delete") }}</span>
</div>
</el-popover>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<div class="tab-layout" @click="handleClick">
@ -87,41 +80,6 @@ limitations under the License. -->
:is-resizable="dashboardStore.editMode"
@layout-updated="layoutUpdatedEvent"
>
<div
ref="tabObserveContainer"
class="scroll-tab-container"
v-if="dashboardStore.fullView"
>
<div
v-if="dashboardStore.currentTabItems.length > 1"
class="scroll-handler__wrapper"
>
<div
@click="scrollToGraph(item.i, index)"
v-for="(item, index) in dashboardStore.currentTabItems"
:key="item.i"
:class="[currentItem === index ? 'active' : '']"
class="scroll-to"
></div>
</div>
<div
class="tabitem"
:id="`tabitem${item.i}`"
v-for="item in dashboardStore.currentTabItems"
:key="item.i"
>
<component
:is="item.type"
:data="item"
:activeIndex="`${data.i}-${activeTabIndex}-${item.i}`"
:needQuery="needQuery"
@click="clickTabGrid($event, item)"
:class="{ active: activeTabWidget === item.i }"
/>
</div>
</div>
<template v-else>
<grid-item
v-for="item in dashboardStore.currentTabItems"
:x="item.x"
@ -141,14 +99,14 @@ limitations under the License. -->
:needQuery="needQuery"
/>
</grid-item>
</template>
</grid-layout>
<div class="no-data-tips" v-else>{{ t("noWidget") }}</div>
</div>
</template>
<script lang="ts">
import { ref, watch, onMounted, onBeforeUnmount, defineComponent, toRefs } from "vue";
import { ref, watch, defineComponent, toRefs } from "vue";
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
import type { PropType } from "vue";
import { LayoutConfig } from "@/types/dashboard";
import { useDashboardStore } from "@/store/modules/dashboard";
@ -159,7 +117,10 @@ import Profile from "./Profile.vue";
import Log from "./Log.vue";
import Text from "./Text.vue";
import Ebpf from "./Ebpf.vue";
import Event from "./Event.vue";
import { dragIgnoreFrom } from "../data";
import DemandLog from "./DemandLog.vue";
import copy from "@/utils/copy";
const props = {
data: {
@ -170,92 +131,40 @@ const props = {
};
export default defineComponent({
name: "Tab",
components: { Topology, Widget, Trace, Profile, Log, Text, Ebpf },
components: {
Topology,
Widget,
Trace,
Profile,
Log,
Text,
Ebpf,
DemandLog,
Event,
},
props,
setup(props) {
const { t } = useI18n();
const dashboardStore = useDashboardStore();
const activeTabIndex = ref<number>(0);
const route = useRoute();
const activeTabIndex = ref<number>(
Number(route.params.activeTabIndex) || 0
);
const activeTabWidget = ref<string>("");
const editTabIndex = ref<number>(NaN); // edit tab item name
const canEditTabName = ref<boolean>(false);
const needQuery = ref<boolean>(false);
const showTools = ref<boolean>(false);
const tabRef = ref<any>("");
const tabObserveContainer = ref<any>(null);
const arrayOfItems = ref<Element[]>([]);
const currentItem = ref<number>(0);
const isScrolling = ref(false);
const l = dashboardStore.layout.findIndex((d: LayoutConfig) => d.i === props.data.i);
dashboardStore.setActiveTabIndex(activeTabIndex);
const l = dashboardStore.layout.findIndex(
(d: LayoutConfig) => d.i === props.data.i
);
if (dashboardStore.layout[l].children.length) {
dashboardStore.setCurrentTabItems(
dashboardStore.layout[l].children[activeTabIndex.value].children
);
dashboardStore.setActiveTabIndex(activeTabIndex.value, props.data.i);
setTimeout(() => {
observeItems();
}, 1500);
}
function scrollToGraph(e: any, index: number) {
document?.getElementById(`tabitem${e}`)?.scrollIntoView();
currentItem.value = index;
}
function observeItems(kill = false) {
const observer = new IntersectionObserver((entries) => {
entries.forEach((element) => {
if (element.isIntersecting && element.intersectionRatio > 0) {
setTimeout(() => {
// currentItem.value = element.target.id;
}, 200);
}
});
});
document.querySelectorAll(".tabitem").forEach((element) => {
arrayOfItems.value.push(element);
observer.observe(element);
});
if (kill) {
document.querySelectorAll(".tabitem").forEach((element) => {
observer.unobserve(element);
});
}
}
function scrollUp() {
if (currentItem.value > 0) {
currentItem.value--;
scrollTo(currentItem.value);
} else if (currentItem.value === 0) {
isScrolling.value = false;
}
}
function scrollDown() {
if (currentItem.value < arrayOfItems?.value?.length - 1) {
currentItem.value++;
scrollTo(currentItem.value);
} else if (currentItem.value === arrayOfItems?.value?.length - 1) {
isScrolling.value = true;
currentItem.value = 0;
scrollTo(currentItem.value);
}
}
function scrollTo(index: number) {
arrayOfItems.value[index]?.scrollIntoView();
if (isScrolling.value) {
setTimeout(() => {
isScrolling.value = false;
}, 800);
}
}
watch(
() => dashboardStore.currentTabItems,
() => {
setTimeout(() => {
observeItems();
}, 500);
}
);
function clickTabs(e: Event, idx: number) {
e.stopPropagation();
@ -270,6 +179,11 @@ export default defineComponent({
dashboardStore.layout[l].children[activeTabIndex.value].children
);
needQuery.value = true;
if (route.params.activeTabIndex) {
let p = location.href.split("/tab/")[0];
p = p + "/tab/" + activeTabIndex.value;
history.replaceState({}, "", p);
}
}
function removeTab(e: Event) {
e.stopPropagation();
@ -296,6 +210,7 @@ export default defineComponent({
editTabIndex.value = index;
}
function handleClick(el: any) {
needQuery.value = true;
if (["tab-name", "edit-tab"].includes(el.target.className)) {
return;
}
@ -305,7 +220,9 @@ export default defineComponent({
function clickTabGrid(e: Event, item: LayoutConfig) {
e.stopPropagation();
activeTabWidget.value = item.i;
dashboardStore.activeGridItem(`${props.data.i}-${activeTabIndex.value}-${item.i}`);
dashboardStore.activeGridItem(
`${props.data.i}-${activeTabIndex.value}-${item.i}`
);
handleClick(e);
}
function layoutUpdatedEvent() {
@ -316,18 +233,15 @@ export default defineComponent({
dashboardStore.layout[l].children[activeTabIndex.value].children
);
}
function initScrollWatcher() {
tabObserveContainer?.value?.addEventListener("wheel", (e: WheelEvent) => {
if (isScrolling.value === false) {
isScrolling.value = true;
if (e.deltaY < 0) {
scrollUp();
function copyLink() {
let path = "";
if (route.params.activeTabIndex === undefined) {
path = location.href + "/tab/" + activeTabIndex.value;
} else {
scrollDown();
const p = location.href.split("/tab/")[0];
path = p + "/tab/" + activeTabIndex.value;
}
}
});
copy(path);
}
document.body.addEventListener("click", handleClick, false);
watch(
@ -345,17 +259,7 @@ export default defineComponent({
}
}
);
onMounted(() => {
initScrollWatcher();
tabRef?.value["parentElement"]?.classList?.toggle("item");
});
onBeforeUnmount(() => {
observeItems(true);
});
return {
currentItem,
tabObserveContainer,
scrollToGraph,
handleClick,
layoutUpdatedEvent,
clickTabGrid,
@ -364,15 +268,14 @@ export default defineComponent({
deleteTabItem,
removeTab,
clickTabs,
copyLink,
...toRefs(props),
tabRef,
activeTabWidget,
dashboardStore,
activeTabIndex,
editTabIndex,
needQuery,
canEditTabName,
showTools,
t,
dragIgnoreFrom,
};
@ -380,76 +283,33 @@ export default defineComponent({
});
</script>
<style lang="scss" scoped>
.tab-layout::-webkit-scrollbar {
display: none !important;
}
.scroll-tab-container {
position: relative;
height: 80vh;
display: block;
scroll-behavior: smooth;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
perspective: 1000;
overflow: hidden;
}
.scroll-tab-container::-webkit-scrollbar {
display: none;
}
.scroll-tab-container {
-ms-overflow-style: none;
scrollbar-width: none;
}
.tabitem {
scroll-snap-align: start;
height: 100%;
margin: 0 0;
}
.scroll-handler__wrapper {
z-index: 20;
position: fixed;
display: flex;
flex-direction: column;
right: 0;
top: 40vh;
height: auto;
width: 17px;
.scroll-to {
opacity: 0.5;
width: 10px;
height: 10px;
margin: 5px 0;
border-radius: 50%;
cursor: pointer;
background: #4f4f4f;
}
.scroll-to.active {
opacity: 1;
padding: 6px;
background: #252a2f;
}
}
.tabs {
height: 40px;
color: #ccc;
width: 100%;
overflow-x: auto;
white-space: nowrap;
overflow-y: hidden;
span {
display: inline-block;
padding: 0 10px;
margin: 0 10px;
height: 40px;
line-height: 40px;
cursor: pointer;
text-align: center;
}
.tab-name {
max-width: 130px;
max-width: 110px;
height: 20px;
line-height: 20px;
outline: none;
color: #333;
font-style: normal;
margin-right: 5px;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
}
.tab-icons {
@ -470,12 +330,13 @@ export default defineComponent({
span.active {
border-bottom: 1px solid #409eff;
.tab-name {
color: #409eff;
}
}
}
.tab-header .tabs .span.active {
color: red !important;
}
.operations {
color: #aaa;
cursor: pointer;
@ -484,6 +345,11 @@ export default defineComponent({
padding-right: 10px;
}
.icon-operation {
display: inline-block;
margin-top: 8px;
}
.tab-header {
justify-content: space-between;
width: 100%;
@ -506,6 +372,10 @@ export default defineComponent({
overflow: auto;
}
.tab-icon {
color: #666;
}
.vue-grid-item.active {
border: 1px solid #409eff;
}
@ -517,17 +387,4 @@ export default defineComponent({
padding-top: 30px;
color: #888;
}
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
</style>

View File

@ -29,6 +29,9 @@ limitations under the License. -->
<span>{{ t("delete") }}</span>
</div>
</el-popover>
<div class="header">
<Filter :needQuery="needQuery" />
</div>
<div class="trace flex-h">
<TraceList @show:trace="showTraceDetails" v-if="traceListActive" />
<TraceDetail @show:list="showTraceList" v-if="!traceListActive" />
@ -51,6 +54,7 @@ const props = defineProps({
default: () => ({ graph: {} }),
},
activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const dashboardStore = useDashboardStore();
@ -81,6 +85,7 @@ onBeforeUnmount(() => {
height: 100%;
font-size: 12px;
position: relative;
overflow: auto;
}
.delete {
@ -93,6 +98,7 @@ onBeforeUnmount(() => {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
min-width: 1200px;
}
.tools {
@ -112,5 +118,6 @@ onBeforeUnmount(() => {
width: 100%;
height: 100%;
overflow: auto;
min-width: 1200px;
}
</style>

View File

@ -22,5 +22,18 @@ import Profile from "./Profile.vue";
import Log from "./Log.vue";
import Text from "./Text.vue";
import Ebpf from "./Ebpf.vue";
import DemandLog from "./DemandLog.vue";
import Event from "./Event.vue";
export default { Tab, Widget, Trace, Topology, Profile, Log, Text, Ebpf };
export default {
Tab,
Widget,
Trace,
Topology,
Profile,
Log,
Text,
Ebpf,
DemandLog,
Event,
};

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
export const dragIgnoreFrom =
"svg.d3-trace-tree, .dragger, .micro-topo-chart, .schedules";
"svg.d3-trace-tree, .dragger, .micro-topo-chart, .schedules, .vis-item, .vis-timeline";
export const PodsChartTypes = ["EndpointList", "InstanceList"];
@ -178,14 +178,16 @@ export const AllTools = [
{ name: "assignment", content: "Add Log", id: "addLog" },
];
export const ServiceTools = [
{ name: "playlist_add", content: "Widget", id: "addWidget" },
{ name: "all_inbox", content: "Tab", id: "addTab" },
{ name: "library_books", content: "Text", id: "addText" },
{ name: "device_hub", content: "Topology", id: "addTopology" },
{ name: "merge", content: "Trace", id: "addTrace" },
{ name: "timeline", content: "Trace Profiling", id: "addProfile" },
{ name: "insert_chart", content: "eBPF Profiling", id: "addEbpf" },
{ name: "assignment", content: "Log", id: "addLog" },
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "timeline", content: "Add Trace Profiling", id: "addProfile" },
{ name: "insert_chart", content: "Add eBPF Profiling", id: "addEbpf" },
{ name: "assignment", content: "Add Log", id: "addLog" },
{ name: "demand", content: "Add On Demand Log", id: "addDemandLog" },
{ name: "event", content: "Add Event", id: "addEvent" },
];
export const InstanceTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
@ -193,6 +195,8 @@ export const InstanceTools = [
{ name: "library_books", content: "Add Text", id: "addText" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "assignment", content: "Add Log", id: "addLog" },
{ name: "demand", content: "Add On Demand Log", id: "addDemandLog" },
{ name: "event", content: "Add Event", id: "addEvent" },
];
export const EndpointTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
@ -201,6 +205,7 @@ export const EndpointTools = [
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "assignment", content: "Add Log", id: "addLog" },
{ name: "event", content: "Add Event", id: "addEvent" },
];
export const ServiceRelationTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },

View File

@ -46,6 +46,7 @@ limitations under the License. -->
:intervalTime="intervalTime"
:colMetrics="colMetrics"
:config="config"
v-if="colMetrics.length"
/>
</el-table>
</div>
@ -96,7 +97,9 @@ const dashboardStore = useDashboardStore();
const chartLoading = ref<boolean>(false);
const endpoints = ref<Endpoint[]>([]);
const searchText = ref<string>("");
const colMetrics = computed(() => props.config.metrics.map((d: string) => d));
const colMetrics = computed(() =>
(props.config.metrics || []).filter((d: string) => d)
);
if (props.needQuery) {
queryEndpoints();
@ -119,7 +122,7 @@ async function queryEndpointMetrics(currentPods: Endpoint[]) {
if (!currentPods.length) {
return;
}
const metrics = (props.config.metrics || []).filter((d: string) => d);
const metrics = props.config.metrics || [];
const metricTypes = props.config.metricTypes || [];
if (metrics.length && metrics[0] && metricTypes.length && metricTypes[0]) {
const params = await useQueryPodsMetrics(

View File

@ -43,6 +43,7 @@ limitations under the License. -->
</template>
</el-table-column>
<ColumnGraph
v-if="colMetrics.length"
:intervalTime="intervalTime"
:colMetrics="colMetrics"
:config="config"
@ -126,7 +127,9 @@ const chartLoading = ref<boolean>(false);
const instances = ref<Instance[]>([]); // current instances
const pageSize = 10;
const searchText = ref<string>("");
const colMetrics = computed(() => props.config.metrics.map((d: string) => d));
const colMetrics = computed(() =>
(props.config.metrics || []).filter((d: string) => d)
);
if (props.needQuery) {
queryInstance();
}
@ -151,7 +154,8 @@ async function queryInstanceMetrics(currentInstances: Instance[]) {
if (!currentInstances.length) {
return;
}
const { metrics, metricTypes } = props.config;
const metrics = props.config.metrics || [];
const metricTypes = props.config.metricTypes || [];
if (metrics.length && metrics[0] && metricTypes.length && metricTypes[0]) {
const params = await useQueryPodsMetrics(

View File

@ -58,6 +58,7 @@ limitations under the License. -->
:intervalTime="intervalTime"
:colMetrics="colMetrics"
:config="config"
v-if="colMetrics.length"
/>
</el-table>
</div>
@ -117,7 +118,7 @@ const searchText = ref<string>("");
const groups = ref<any>({});
const sortServices = ref<(Service & { merge: boolean })[]>([]);
const colMetrics = computed(() =>
props.config.metrics.filter((d: string) => d)
(props.config.metrics || []).filter((d: string) => d)
);
queryServices();
@ -195,7 +196,8 @@ async function queryServiceMetrics(currentServices: Service[]) {
if (!currentServices.length) {
return;
}
const { metrics, metricTypes } = props.config;
const metrics = props.config.metrics || [];
const metricTypes = props.config.metricTypes || [];
if (metrics.length && metrics[0] && metricTypes.length && metricTypes[0]) {
const params = await useQueryPodsMetrics(

View File

@ -17,13 +17,13 @@ limitations under the License. -->
<div class="top-list" v-if="available">
<div class="chart-slow-i" v-for="(i, index) in data[key]" :key="index">
<div class="ell tools flex-h">
<div>
<div class="desc">
<span class="calls mr-10">{{ i.value }}</span>
<span class="cp mr-20">
{{ i.name }}
</span>
</div>
<div>
<div class="copy">
<Icon
iconName="review-list"
size="middle"
@ -103,6 +103,16 @@ function handleClick(i: string) {
height: 100%;
}
.desc {
flex-grow: 2;
overflow: hidden;
text-overflow: ellipsis;
}
.copy {
width: 30px;
}
.calls {
font-size: 12px;
padding: 0 5px;

View File

@ -155,7 +155,7 @@ function getLabel(metric: string, index: string) {
.value {
display: inline-block;
width: calc(100% - 30px);
flex-grow: 2;
height: 100%;
}
</style>

View File

@ -31,7 +31,7 @@ limitations under the License. -->
:h="item.h"
:i="item.i"
:key="item.i"
@click="clickGrid(item)"
@click="clickGrid(item, $event)"
:class="{ active: dashboardStore.activedGridItem === item.i }"
:drag-ignore-from="dragIgnoreFrom"
>
@ -63,10 +63,13 @@ export default defineComponent({
const dashboardStore = useDashboardStore();
const selectorStore = useSelectorStore();
function clickGrid(item: LayoutConfig) {
function clickGrid(item: LayoutConfig, event: Event) {
dashboardStore.activeGridItem(item.i);
dashboardStore.selectWidget(item);
if (item.type === "Tab") {
if (
item.type === "Tab" &&
(event.target as HTMLDivElement)?.className !== "tab-layout"
) {
dashboardStore.setActiveTabIndex(0);
}
}

View File

@ -131,13 +131,61 @@ limitations under the License. -->
/>
</div>
</div>
<div
class="flex-h tools"
v-loading="loading"
v-if="$route.query['portal'] !== 'true'"
>
<div class="selectors-item" v-if="key === 3 || key === 4">
<span class="label">
{{
["EndpointRelation", "Endpoint"].includes(dashboardStore.entity)
? "$Endpoint"
: "$ServiceInstance"
}}
</span>
<Selector
v-model="states.currentPod"
:options="selectorStore.pods"
size="small"
placeholder="Select a data"
@change="changePods"
@query="searchPods"
class="selectorPod"
:isRemote="
['EndpointRelation', 'Endpoint'].includes(dashboardStore.entity)
"
/>
</div>
<div class="selectors-item" v-if="key === 2 || key === 4">
<span class="label">$DestinationService</span>
<Selector
v-model="states.currentDestService"
:options="selectorStore.destServices"
size="small"
placeholder="Select a service"
@change="changeDestService"
class="selectors"
/>
</div>
<div class="selectors-item" v-if="key === 4">
<span class="label">
{{
dashboardStore.entity === "EndpointRelation"
? "$DestinationEndpoint"
: "$DestinationServiceInstance"
}}
</span>
<Selector
v-model="states.currentDestPod"
:options="selectorStore.destPods"
size="small"
placeholder="Select a data"
@change="changeDestPods"
class="selectorPod"
@query="searchDestPods"
:isRemote="dashboardStore.entity === 'EndpointRelation'"
/>
</div>
</div>
<div class="flex-h tools" v-loading="loading" v-if="!appStore.isMobile">
<div class="tool-icons flex-h" v-if="dashboardStore.editMode">
<el-dropdown content="Controls" placement="bottom">
<el-dropdown content="Controls" placement="bottom" :persistent="false">
<i>
<Icon class="icon-btn" size="sm" iconName="control" />
</i>
@ -174,7 +222,6 @@ limitations under the License. -->
/>
</div>
</div>
</div>
<Header v-if="showLogHeader" />
<TraceDetailsTools
v-if="showTraceHeader && currentTraceView === 'traceDetails'"
@ -423,7 +470,12 @@ async function getServices() {
states.currentService = selectorStore.currentService.value;
const e = dashboardStore.entity.split("Relation")[0];
if (
[EntityType[2].value, EntityType[3].value].includes(dashboardStore.entity)
[
EntityType[2].value,
EntityType[3].value,
EntityType[5].value,
EntityType[6].value,
].includes(dashboardStore.entity)
) {
fetchPods(e, selectorStore.currentService.id, true);
}
@ -443,7 +495,10 @@ async function changeService(service: any) {
if (service[0]) {
states.currentService = service[0].value;
selectorStore.setCurrentService(service[0]);
fetchPods(dashboardStore.entity, selectorStore.currentService.id, true);
const e = dashboardStore.entity.split("Relation")[0];
selectorStore.setCurrentPod(null);
states.currentPod = "";
fetchPods(e, selectorStore.currentService.id, true);
} else {
selectorStore.setCurrentService(null);
}
@ -453,6 +508,9 @@ function changeDestService(service: any) {
if (service[0]) {
states.currentDestService = service[0].value;
selectorStore.setCurrentDestService(service[0]);
selectorStore.setCurrentDestPod(null);
states.currentDestPod = "";
fetchPods(dashboardStore.entity, selectorStore.currentDestService.id, true);
} else {
selectorStore.setCurrentDestService(null);
}
@ -527,6 +585,12 @@ function setTabControls(id: string) {
case "addText":
dashboardStore.addTabControls("Text");
break;
case "addDemandLog":
dashboardStore.addTabControls("DemandLog");
break;
case "addEvent":
dashboardStore.addTabControls("Event");
break;
default:
ElMessage.info("Don't support this control");
break;
@ -559,6 +623,12 @@ function setControls(id: string) {
case "addText":
dashboardStore.addControl("Text");
break;
case "addDemandLog":
dashboardStore.addControl("DemandLog");
break;
case "addEvent":
dashboardStore.addControl("Event");
break;
default:
dashboardStore.addControl("Widget");
}
@ -615,11 +685,11 @@ async function fetchPods(
if (setPod) {
let p;
if (states.currentDestPod) {
p = selectorStore.pods.find(
p = selectorStore.destPods.find(
(d: { label: string }) => d.label === states.currentDestPod
);
} else {
p = selectorStore.pods.find(
p = selectorStore.destPods.find(
(d: { label: string }, index: number) => index === 0
);
}
@ -635,11 +705,11 @@ async function fetchPods(
if (setPod) {
let p;
if (states.currentDestPod) {
p = selectorStore.pods.find(
p = selectorStore.destPods.find(
(d: { label: string }) => d.label === states.currentDestPod
);
} else {
p = selectorStore.pods.find(
p = selectorStore.destPods.find(
(d: { label: string }, index: number) => index === 0
);
}

View File

@ -15,24 +15,21 @@ limitations under the License. -->
<template>
<div class="log">
<div :class="{ 'd-flex': visibleColumns.length < 6 }" class="log-header">
<template v-for="(item, index) in columns">
<template v-if="item.isVisible">
<div
class="method"
:style="`width: ${item.method}px`"
v-if="item.drag"
:key="index"
class="log-header"
:class="
type === 'browser' ? ['browser-header', 'flex-h'] : 'service-header'
"
>
<template v-for="(item, index) in columns" :key="`col${index}`">
<div
:class="[
item.label,
['message', 'stack'].includes(item.label) ? 'max-item' : '',
]"
>
<span class="r cp" ref="dragger" :data-index="index">
<Icon iconName="settings_ethernet" size="sm" />
</span>
{{ t(item.value) }}
</div>
<div v-else :class="item.label" :key="`col${index}`">
{{ t(item.value) }}
</div>
</template>
</template>
</div>
<div v-if="type === 'browser'">
@ -60,40 +57,41 @@ limitations under the License. -->
@closed="showDetail = false"
:title="t('logDetail')"
>
<LogDetail :currentLog="currentLog" />
<LogDetail :currentLog="currentLog" :columns="columns" />
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from "vue";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import LogBrowser from "./LogBrowser.vue";
import LogService from "./LogService.vue";
import LogDetail from "./LogDetail.vue";
import { logStore } from "@/store/modules/log";
// import { logStore } from "@/store/modules/log";
import { BrowserLogConstants, ServiceLogConstants } from "./data";
/*global defineProps, Nullable */
/*global defineProps */
const props = defineProps({
type: { type: String, default: "service" },
tableData: { type: Array, default: () => [] },
noLink: { type: Boolean, default: true },
});
const useLogStore = logStore();
// const useLogStore = logStore();
const { t } = useI18n();
const currentLog = ref<any>({});
const showDetail = ref<boolean>(false);
const dragger = ref<Nullable<HTMLSpanElement>>(null);
// const method = ref<number>(380);
const columns: any[] =
props.type === "browser" ? BrowserLogConstants : ServiceLogConstants;
const columns = ref<any[]>(
props.type === "browser"
? useLogStore.browserLogColumn
: useLogStore.serviceLogColumn
);
// const columns = ref<any[]>(
// props.type === "browser"
// ? useLogStore.browserLogColumn
// : useLogStore.serviceLogColumn
// );
const visibleColumns = computed(() =>
columns.value.filter((column) => column.isVisible)
);
// const visibleColumns = computed(() =>
// columns.value.filter((column) => column.isVisible)
// );
function setCurrentLog(log: any) {
showDetail.value = true;
currentLog.value = log;
@ -103,17 +101,18 @@ function setCurrentLog(log: any) {
.log {
font-size: 12px;
height: 100%;
border-bottom: 1px solid #eee;
width: 100%;
overflow: auto;
}
.log-header {
/*display: flex;*/
white-space: nowrap;
user-select: none;
border-left: 0;
border-right: 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
/*background-color: #f3f4f9;*/
.traceId {
width: 390px;
}
@ -131,11 +130,8 @@ function setCurrentLog(log: any) {
}
.log-header div {
/*min-width: 140px;*/
width: 140px;
/*flex-grow: 1;*/
display: inline-block;
padding: 0 4px;
padding: 0 5px;
border: 1px solid transparent;
border-right: 1px dotted silver;
line-height: 30px;
@ -145,10 +141,18 @@ function setCurrentLog(log: any) {
white-space: nowrap;
}
.d-flex{
display: flex;
div{
flex-grow: 1;
.browser-header {
div {
min-width: 140px;
width: 10%;
}
.max-item {
width: 20%;
}
}
.service-header div {
width: 140px;
}
</style>

View File

@ -14,18 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div @click="showSelectSpan" :class="['log-item', 'clearfix']" ref="logItem">
<div
@click="showSelectSpan"
:class="['log-item', 'clearfix', 'flex-h']"
ref="logItem"
>
<div
v-for="(item, index) in columns"
:key="index"
:class="[
'method',
['message', 'stack'].includes(item.label) ? 'autoHeight' : '',
'log',
['message', 'stack'].includes(item.label) ? 'max-item' : '',
]"
:style="{
lineHeight: 1.3,
width: `${item.drag ? item.method : ''}px`,
}"
>
<span v-if="item.label === 'time'">{{ dateFormat(data.time) }}</span>
<span v-else-if="item.label === 'errorUrl'">{{ data.pagePath }}</span>
@ -44,7 +44,7 @@ import { BrowserLogConstants } from "./data";
/*global defineProps, defineEmits, NodeListOf */
const props = defineProps({
data: { type: Array as any, default: () => [] },
data: { type: Object as any, default: () => ({}) },
});
const columns = BrowserLogConstants;
const emit = defineEmits(["select"]);
@ -84,7 +84,8 @@ function showSelectSpan() {
}
.log-item > div {
width: 140px;
width: 10%;
min-width: 140px;
padding: 0 5px;
display: inline-block;
border: 1px solid transparent;
@ -95,6 +96,10 @@ function showSelectSpan() {
white-space: nowrap;
}
.max-item.log {
width: 20%;
}
.log-item .text {
width: 100% !important;
display: inline-block;
@ -103,8 +108,7 @@ function showSelectSpan() {
white-space: nowrap;
}
.log-item > div.method {
padding: 7px 5px;
.log-item > div.log {
line-height: 30px;
}
</style>

View File

@ -20,7 +20,10 @@ limitations under the License. -->
:key="index"
>
<span class="g-sm-4 grey">{{ t(item.value) }}:</span>
<span v-if="item.label === 'timestamp'" class="g-sm-8 mb-10">
<span
v-if="['timestamp', 'time'].includes(item.label)"
class="g-sm-8 mb-10"
>
{{ dateFormat(currentLog[item.label]) }}
</span>
<textarea
@ -41,14 +44,14 @@ import { computed } from "vue";
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import dayjs from "dayjs";
import { ServiceLogDetail } from "./data";
import { Option } from "@/types/app";
/*global defineProps */
const props = defineProps({
currentLog: { type: Object as PropType<any>, default: () => ({}) },
columns: { type: Array as PropType<Option[]>, default: () => [] },
});
const { t } = useI18n();
const columns = ServiceLogDetail;
const logTags = computed(() => {
if (!props.currentLog.tags) {
return [];

View File

@ -30,12 +30,12 @@ limitations under the License. -->
<span v-else-if="item.label === 'tags'">
{{ tags }}
</span>
<router-link
<!-- <router-link
v-else-if="item.label === 'traceId' && !noLink"
:to="{ name: 'trace', query: { traceid: data[item.label] } }"
>
<span :class="noLink ? '' : 'blue'">{{ data[item.label] }}</span>
</router-link>
</router-link> -->
<span v-else>{{ data[item.label] }}</span>
</div>
</div>
@ -131,9 +131,10 @@ function showSelectSpan() {
padding: 3px 8px;
}
.d-flex{
.d-flex {
display: flex;
div{
div {
flex-grow: 1;
}
}

View File

@ -0,0 +1,99 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<span v-if="demandLogStore.message">{{ demandLogStore.message }}</span>
<div
v-else
v-loading="demandLogStore.loadLogs"
class="log-content"
ref="logContent"
style="width: calc(100% - 10px); height: calc(100% - 140px)"
></div>
</template>
<script lang="ts" setup>
import { onMounted, ref, onUnmounted, watch, toRaw } from "vue";
import { useDemandLogStore } from "@/store/modules/demand-log";
/*global Nullable */
const demandLogStore = useDemandLogStore();
const monacoInstance = ref();
const logContent = ref<Nullable<HTMLDivElement>>(null);
onMounted(() => {
init();
});
async function init() {
const monaco = await import("monaco-editor");
setTimeout(() => {
monacoInstanceGen(monaco);
}, 500);
window.addEventListener("resize", () => {
editorLayout();
});
}
function monacoInstanceGen(monaco: any) {
monacoInstance.value = monaco.editor.create(logContent.value, {
value: "",
language: "text",
wordWrap: true,
minimap: { enabled: false },
readonly: true,
});
toRaw(monacoInstance.value).updateOptions({ readOnly: true });
editorLayout();
}
function editorLayout() {
if (!logContent.value) {
return;
}
const { width, height } = logContent.value.getBoundingClientRect();
toRaw(monacoInstance.value).layout({
height: height,
width: width,
});
}
onUnmounted(() => {
if (!toRaw(monacoInstance.value)) {
return;
}
toRaw(monacoInstance.value).dispose();
monacoInstance.value = null;
demandLogStore.setLogs("");
});
watch(
() => demandLogStore.logs,
() => {
if (!toRaw(monacoInstance.value)) {
return;
}
toRaw(monacoInstance.value).setValue(demandLogStore.logs);
if (!demandLogStore.logs) {
return;
}
setTimeout(() => {
toRaw(monacoInstance.value).revealPosition({
column: 1,
lineNumber: demandLogStore.total,
});
}, 1000);
}
);
</script>
<style lang="scss" scoped>
.log-content {
min-width: 600px;
min-height: 400px;
}
</style>

View File

@ -0,0 +1,433 @@
<!-- 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="flex-h row">
<div class="mr-5 mb-5" v-if="dashboardStore.entity !== EntityType[3].value">
<span class="grey mr-5"> {{ t("instance") }}: </span>
<Selector
size="small"
:value="state.instance.value"
:options="demandLogStore.instances"
placeholder="Select a instance"
@change="changeField('instance', $event)"
class="selectors"
/>
</div>
<div class="mr-5 mb-5" v-if="state.container">
<span class="grey mr-5">{{ t("container") }}:</span>
<Selector
size="small"
:value="state.container.value"
:options="demandLogStore.containers"
placeholder="Select a container"
@change="changeField('container', $event)"
class="selectors"
/>
</div>
<!-- <div class="mr-5">
<span class="grey mr-5">{{ t("limit") }}:</span>
<el-input-number
v-model="limit"
:min="1"
:max="1000"
size="small"
controls-position="right"
@change="changeField('limit', $event)"
/>
</div> -->
<div class="mr-5">
<span class="grey mr-5">{{ t("duration") }}:</span>
<Selector
size="small"
:value="state.duration.value"
:options="TimeRanges"
placeholder="Select a time range"
@change="changeField('duration', $event)"
class="duration-range"
/>
</div>
<div class="mr-5">
<span class="grey mr-5">{{ t("interval") }}:</span>
<Selector
size="small"
:value="state.interval.value"
:options="IntervalOpts"
@change="changeField('interval', $event)"
/>
</div>
</div>
<div class="flex-h row">
<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}`"
>
<span>{{ item }}</span>
<span class="remove-icon" @click="removeContent(index)">×</span>
</span>
</span>
<el-input
size="small"
class="inputs-max"
:placeholder="t('addKeywordsOfContent')"
v-model="contentStr"
@change="addLabels('keywordsOfContent')"
/>
</div>
<div class="mr-5">
<span class="grey mr-5"> {{ t("excludingKeywordsOfContent") }}: </span>
<span class="log-tags">
<span
class="selected"
v-for="(item, index) in excludingKeywordsOfContent"
:key="`excludingKeywordsOfContent${index}`"
>
<span>{{ item }}</span>
<span class="remove-icon" @click="removeExcludeContent(index)">
×
</span>
</span>
</span>
<el-input
class="inputs-max"
size="small"
:placeholder="t('addExcludingKeywordsOfContent')"
v-model="excludingContentStr"
@change="addLabels('excludingKeywordsOfContent')"
/>
</div>
</div>
<div class="flex-h row btn-row">
<el-button
class="search-btn mt-10"
size="small"
type="primary"
@click="runInterval"
:disabled="disabled"
>
<Icon
size="middle"
iconName="retry"
:loading="!!intervalFn"
class="mr-5"
/>
{{ intervalFn ? t("pause") : t("start") }}
</el-button>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, watch, onMounted, onUnmounted } from "vue";
import { useI18n } from "vue-i18n";
import { useDemandLogStore } from "@/store/modules/demand-log";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
import { EntityType } from "../../data";
import { TimeRanges, IntervalOpts } from "./data";
import getLocalTime from "@/utils/localtime";
import dateFormatStep from "@/utils/dateFormat";
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const demandLogStore = useDemandLogStore();
const keywordsOfContent = ref<string[]>([]);
const excludingKeywordsOfContent = ref<string[]>([]);
const contentStr = ref<string>("");
const excludingContentStr = ref<string>("");
// const limit = ref<number>(20);
const state = reactive<any>({
instance: { value: "", label: "" },
container: { value: "", label: "" },
duration: { label: "From 30 minutes ago", value: 1800 },
interval: { label: "30 seconds", value: 30 },
});
const disabled = ref<boolean>(true);
/*global Nullable */
const intervalFn = ref<Nullable<any>>(null);
onMounted(() => {
fetchSelectors();
});
async function fetchSelectors() {
if (dashboardStore.entity !== EntityType[3].value) {
await getInstances();
}
getContainers();
if (intervalFn.value) {
clearTimer();
}
}
async function getContainers() {
if (
!(
state.instance.id ||
(selectorStore.currentPod && selectorStore.currentPod.id)
)
) {
return;
}
const resp = await demandLogStore.getContainers(
state.instance.id || selectorStore.currentPod.id
);
if (resp.errors) {
disabled.value = true;
ElMessage.error(resp.errors);
return;
}
if (resp.data.containers.errorReason) {
disabled.value = true;
ElMessage.warning(resp.data.containers.errorReason);
return;
}
if (demandLogStore.containers.length) {
state.container = demandLogStore.containers[0];
disabled.value = false;
}
}
async function getInstances() {
const resp = await demandLogStore.getInstances();
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.instance = demandLogStore.instances[0];
}
function runInterval() {
if (intervalFn.value) {
clearTimer();
return;
}
searchLogs();
if (state.interval.value === 0) {
return;
}
intervalFn.value = setInterval(searchLogs, state.interval.value * 1000);
setTimeout(() => {
clearTimer();
}, state.duration.value * 1000);
}
function searchLogs() {
let instance = "";
if (dashboardStore.entity === EntityType[3].value) {
instance = selectorStore.currentPod.id;
}
const serviceInstanceId =
instance || (state.instance && state.instance.id) || "";
demandLogStore.setLogCondition({
serviceInstanceId,
container: state.container.value,
duration: rangeTime(),
keywordsOfContent: keywordsOfContent.value.length
? keywordsOfContent.value
: undefined,
excludingKeywordsOfContent: excludingKeywordsOfContent.value.length
? excludingKeywordsOfContent.value
: undefined,
});
if (!serviceInstanceId) {
return;
}
queryLogs();
}
function rangeTime() {
{
const times = {
start: getLocalTime(
appStore.utc,
new Date(new Date().getTime() - state.duration.value * 1000)
),
end: getLocalTime(appStore.utc, new Date()),
step: "SECOND",
};
return {
start: dateFormatStep(times.start, times.step, false),
end: dateFormatStep(times.end, times.step, false),
step: times.step,
};
}
}
async function queryLogs() {
const res = await demandLogStore.getDemandLogs();
if (res && res.errors) {
ElMessage.error(res.errors);
}
}
function changeField(type: string, opt: any) {
clearTimer();
// if (["limit"].includes(type)) {
// state[type] = opt;
// return;
// }
state[type] = opt[0];
if (type === "instance") {
getContainers();
}
}
function removeContent(index: number) {
const keywordsOfContentList = keywordsOfContent.value || [];
keywordsOfContentList.splice(index, 1);
demandLogStore.setLogCondition({
keywordsOfContent: keywordsOfContentList,
});
contentStr.value = "";
clearTimer();
}
function addLabels(type: string) {
if (type === "keywordsOfContent" && !contentStr.value) {
return;
}
if (type === "excludingKeywordsOfContent" && !excludingContentStr.value) {
return;
}
if (type === "keywordsOfContent") {
keywordsOfContent.value.push(contentStr.value);
demandLogStore.setLogCondition({
[type]: keywordsOfContent.value,
});
contentStr.value = "";
} else if (type === "excludingKeywordsOfContent") {
excludingKeywordsOfContent.value.push(excludingContentStr.value);
demandLogStore.setLogCondition({
[type]: excludingKeywordsOfContent.value,
});
excludingContentStr.value = "";
}
clearTimer();
}
function removeExcludeContent(index: number) {
excludingKeywordsOfContent.value.splice(index, 1);
demandLogStore.setLogCondition({
excludingKeywordsOfContent: excludingKeywordsOfContent.value,
});
excludingContentStr.value = "";
clearTimer();
}
function clearTimer() {
if (!intervalFn.value) {
return;
}
clearInterval(intervalFn.value);
intervalFn.value = null;
}
onUnmounted(() => {
clearTimer();
});
watch(
() => selectorStore.currentService,
() => {
if (dashboardStore.entity === EntityType[0].value) {
fetchSelectors();
demandLogStore.setLogs("");
}
}
);
watch(
() => [selectorStore.currentPod],
() => {
if (dashboardStore.entity === EntityType[3].value) {
fetchSelectors();
demandLogStore.setLogs("");
}
}
);
</script>
<style lang="scss" scoped>
.inputs {
width: 120px;
}
.row {
margin-bottom: 5px;
position: relative;
flex-wrap: wrap;
}
.inputs-max {
width: 270px;
}
.traceId {
margin-top: 2px;
}
.search-btn {
cursor: pointer;
width: 120px;
}
.tips {
color: #888;
}
.log-tag {
width: 30%;
border-style: unset;
outline: 0;
border: 1px solid #ccc;
height: 30px;
padding: 0 5px;
}
.log-tags {
padding: 1px 5px 0 0;
border-radius: 3px;
height: 24px;
display: inline-block;
vertical-align: top;
}
.selected {
display: inline-block;
padding: 0 3px;
border-radius: 3px;
overflow: hidden;
color: #3d444f;
border: 1px dashed #aaa;
font-size: 12px;
margin: 0 2px;
}
.remove-icon {
display: inline-block;
margin-left: 3px;
cursor: pointer;
}
.selectors {
width: 250px;
}
.duration-range {
width: 210px;
}
.btn-row {
justify-content: flex-end;
}
.help {
color: #999;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,37 @@
/**
* 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 TimeRanges = [
{ label: "From 5 seconds ago -- Now", value: 5 },
{ label: "From 10 seconds ago -- Now", value: 10 },
{ label: "From 15 seconds ago -- Now", value: 15 },
{ label: "From 30 seconds ago -- Now", value: 30 },
{ label: "From 45 seconds ago -- Now", value: 45 },
{ label: "From 1 minute ago -- Now", value: 60 },
{ label: "From 5 minutes ago -- Now", value: 300 },
{ label: "From 15 minutes ago -- Now", value: 900 },
{ label: "From 30 minutes ago -- Now", value: 1800 },
];
export const IntervalOpts = [
{ label: "None", value: 0 },
{ label: "5 seconds", value: 5 },
{ label: "10 seconds", value: 10 },
{ label: "15 seconds", value: 15 },
{ label: "30 seconds", value: 30 },
{ label: "45 seconds", value: 45 },
{ label: "1 minute", value: 60 },
];

View File

@ -16,7 +16,7 @@ limitations under the License. -->
<div class="flex-h content">
<TaskList />
<div class="vis-graph ml-5">
<div class="item">
<div class="schedules">
<EBPFSchedules />
</div>
<div class="item">
@ -38,13 +38,21 @@ import EBPFStack from "./components/EBPFStack.vue";
.vis-graph {
height: 100%;
width: calc(100% - 300px);
flex-grow: 2;
min-width: 700px;
overflow: auto;
}
.item {
width: 100%;
overflow: auto;
height: calc(50% - 10px);
height: calc(100% - 100px);
padding-bottom: 10px;
}
.schedules {
height: 90px;
border-bottom: 1px solid #ccc;
padding-right: 10px;
}
</style>

View File

@ -39,6 +39,10 @@ import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { EntityType } from "../../data";
/*global defineProps */
const props = defineProps({
needQuery: { type: Boolean, default: true },
});
const ebpfStore = useEbpfStore();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
@ -46,7 +50,9 @@ const dashboardStore = useDashboardStore();
const { t } = useI18n();
const newTask = ref<boolean>(false);
searchTasks();
if (props.needQuery) {
searchTasks();
}
async function searchTasks() {
const serviceId =

View File

@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="filters">
<div class="mb-10 flex-h">
<Selector
:value="selectedLabels"
:options="labels"
@ -23,7 +24,28 @@ limitations under the License. -->
class="inputs mr-10"
:multiple="true"
/>
<el-popover placement="bottom" :width="680" trigger="click">
<div class="mr-5 duration" v-if="duration.length">
<span>{{ duration[0] }}</span>
<span> ~ </span>
<span>{{ duration[1] }}</span>
</div>
</div>
<div class="flex-h">
<Selector
v-if="ebpfStore.selectedTask.targetType === 'OFF_CPU'"
:value="aggregateType"
:options="AggregateTypes"
size="small"
placeholder="Please select a type"
@change="changeAggregateType"
class="selector mr-10"
/>
<el-popover
placement="bottom"
:width="680"
trigger="click"
:persistent="false"
>
<template #reference>
<el-button type="primary" size="small">
{{ t("processSelect") }}
@ -34,7 +56,7 @@ limitations under the License. -->
placeholder="Please input name"
class="input-with-search"
size="small"
@change="searchProcesses"
@change="searchProcesses(0)"
>
<template #append>
<el-button size="small">
@ -77,41 +99,43 @@ limitations under the License. -->
{{ t("analyze") }}
</el-button>
</div>
<div ref="timeline" class="schedules"></div>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from "vue";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
import { Option } from "@/types/app";
import { TableHeader } from "./data";
import { TableHeader, AggregateTypes } from "./data";
import { useEbpfStore } from "@/store/modules/ebpf";
import { EBPFProfilingSchedule, Process } from "@/types/ebpf";
import { DataSet, Timeline } from "vis-timeline/standalone";
import "vis-timeline/styles/vis-timeline-graph2d.css";
import { ElMessage, ElTable } from "element-plus";
const { t } = useI18n();
const ebpfStore = useEbpfStore();
const pageSize = 5;
/*global Nullable */
const multipleTableRef = ref<InstanceType<typeof ElTable>>();
const selectedProcesses = ref<string[]>([]);
const timeline = ref<Nullable<HTMLDivElement>>(null);
const visGraph = ref<Nullable<any>>(null);
const labels = ref<Option[]>([{ label: "All", value: "0" }]);
const processes = ref<Process[]>([]);
const currentProcesses = ref<Process[]>([]);
const selectedLabels = ref<string[]>(["0"]);
const searchText = ref<string>("");
const aggregateType = ref<string>(AggregateTypes[0].value);
const duration = ref<string[]>([]);
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
new Date(dayjs(date).format(pattern));
dayjs(date).format(pattern);
function changeLabels(opt: any[]) {
const arr = opt.map((d) => d.value);
selectedLabels.value = arr;
}
function changeAggregateType(opt: any[]) {
aggregateType.value = opt[0].value;
ebpfStore.setAnalyzeTrees([]);
}
const handleSelectionChange = (arr: Process[]) => {
selectedProcesses.value = arr.map((d: Process) => d.id);
};
@ -152,85 +176,89 @@ async function analyzeEBPF() {
const res = await ebpfStore.getEBPFAnalyze({
scheduleIdList,
timeRanges,
aggregateType: aggregateType.value,
});
if (res.data.errors) {
if (res.data && res.data.errors) {
ElMessage.error(res.data.errors);
return;
}
}
function visTimeline() {
if (visGraph.value) {
visGraph.value.destroy();
}
function getSchedules() {
labels.value = [{ label: "All", value: "0" }];
selectedLabels.value = ["0"];
processes.value = [];
const schedules = ebpfStore.eBPFSchedules.map(
(d: EBPFProfilingSchedule, index: number) => {
const ranges = ebpfStore.eBPFSchedules.map((d: EBPFProfilingSchedule) => {
for (const l of d.process.labels) {
labels.value.push({ label: l, value: l });
}
processes.value.push(d.process);
return {
id: index + 1,
content: d.process.name,
start: dateFormat(d.startTime),
end: dateFormat(d.endTime),
};
return [d.startTime / 10000, d.endTime / 10000];
});
if (ranges.length) {
const arr = ranges.flat(1);
const min = Math.min(...arr);
const max = Math.max(...arr);
duration.value = [dateFormat(min * 10000), dateFormat(max * 10000)];
} else {
duration.value = [];
}
);
searchProcesses();
if (!timeline.value) {
return;
}
const h = timeline.value.getBoundingClientRect().height;
const items: any = new DataSet(schedules);
const options = {
height: h,
width: "100%",
locale: "en",
};
visGraph.value = new Timeline(timeline.value, items, options);
searchProcesses(0);
analyzeEBPF();
}
function changePage(pageIndex: number) {
searchProcesses(pageIndex);
}
function searchProcesses(pageIndex?: any) {
function searchProcesses(pageIndex: number) {
const arr = processes.value.filter(
(d: { name: string; instanceName: string }) =>
(d: {
name: string;
instanceName: string;
attributes: { name: string; value: string }[];
}) =>
d.name.includes(searchText.value) ||
d.instanceName.includes(searchText.value)
d.instanceName.includes(searchText.value) ||
searchAttribute(d.attributes, searchText.value)
);
currentProcesses.value = arr.splice(
(pageIndex - 1 || 0) * pageSize,
pageSize * (pageIndex || 1)
currentProcesses.value = arr.filter(
(d, index: number) =>
(pageIndex - 1 || 0) * pageSize <= index &&
pageSize * (pageIndex || 1) > index
);
}
function searchAttribute(
attributes: { name: string; value: string }[],
text: string
) {
const item = attributes.find(
(d: { name: string; value: string }) => d.name === "command_line"
);
if (!item) {
return false;
}
return item.value.includes(text);
}
watch(
() => ebpfStore.eBPFSchedules,
() => {
visTimeline();
getSchedules();
}
);
</script>
<style lang="scss" scoped>
.filters {
margin: 5px 0;
}
.schedules {
width: calc(100% - 5px);
margin: 0 5px 5px 0;
height: calc(100% - 60px);
min-height: 150px;
width: 100%;
min-width: 560px;
}
.inputs {
width: 300px;
width: 400px;
}
.input-with-search {
@ -241,4 +269,12 @@ watch(
.pagination {
margin-top: 10px;
}
.selector {
width: 120px;
}
.duration {
line-height: 30px;
}
</style>

View File

@ -24,13 +24,17 @@ import d3tip from "d3-tip";
import { flamegraph } from "d3-flame-graph";
import { useEbpfStore } from "@/store/modules/ebpf";
import { StackElement } from "@/types/ebpf";
import { AggregateTypes } from "./data";
import "d3-flame-graph/dist/d3-flamegraph.css";
/*global Nullable*/
const ebpfStore = useEbpfStore();
const stackTree = ref<Nullable<StackElement>>(null);
const selectStack = ref<Nullable<StackElement>>(null);
const graph = ref<Nullable<HTMLDivElement>>(null);
const flameChart = ref<any>(null);
const min = ref<number>(1);
const max = ref<number>(1);
function drawGraph() {
if (flameChart.value) {
@ -39,53 +43,109 @@ function drawGraph() {
if (!ebpfStore.analyzeTrees.length) {
return (stackTree.value = null);
}
stackTree.value = processTree(ebpfStore.analyzeTrees);
const w = (graph.value && graph.value.getBoundingClientRect().width) || 10;
const root: StackElement = {
parentId: "0",
originId: "1",
name: "Virtual Root",
children: [],
value: 0,
id: "1",
symbol: "Virtual Root",
dumpCount: 0,
stackType: "",
rateOfRoot: "",
rateOfParent: "",
};
countRange();
for (const tree of ebpfStore.analyzeTrees) {
const ele = processTree(tree.elements);
root.children && root.children.push(ele);
}
const param = (root.children || []).reduce(
(prev: number[], curr: StackElement) => {
prev[0] += curr.value;
prev[1] += curr.dumpCount;
return prev;
},
[0, 0]
);
root.value = param[0];
root.dumpCount = 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(5)
.minFrameSize(1)
.transitionEase(d3.easeCubic as any)
.sort(true)
.title("")
.selfValue(false)
.inverted(true)
.onClick((d: { data: StackElement }) => {
selectStack.value = d.data;
})
.setColorMapper((d, originalColor) =>
d.highlight ? "#6aff8f" : originalColor
);
const tip = (d3tip as any)()
.attr("class", "d3-tip")
.direction("w")
.html(
(d: { data: StackElement }) =>
`<div class="mb-5">Symbol: ${d.data.name}</div><div class="mb-5">Dump Count: ${d.data.dumpCount}</div>`
);
.html((d: { data: StackElement } & { parent: { data: StackElement } }) => {
const name = d.data.name.replace("<", "&lt;").replace(">", "&gt;");
const valStr =
ebpfStore.aggregateType === AggregateTypes[0].value
? `<div class="mb-5">Dump Count: ${d.data.dumpCount}</div>`
: `<div class="mb-5">Duration: ${d.data.dumpCount} ns</div>`;
const rateOfParent =
(d.parent &&
`<div class="mb-5">Percentage Of Selected: ${
(
(d.data.dumpCount /
((selectStack.value && selectStack.value.dumpCount) ||
root.dumpCount)) *
100
).toFixed(3) + "%"
}</div>`) ||
"";
const rateOfRoot = `<div class="mb-5">Percentage Of Root: ${
((d.data.dumpCount / root.dumpCount) * 100).toFixed(3) + "%"
}</div>`;
return `<div class="mb-5 name">Symbol: ${name}</div>${valStr}${rateOfParent}${rateOfRoot}`;
})
.style("max-width", "500px");
flameChart.value.tooltip(tip);
d3.select("#graph-stack").datum(stackTree.value).call(flameChart.value);
}
function countRange() {
const list = [];
for (const tree of ebpfStore.analyzeTrees) {
for (const ele of tree.elements) {
list.push(ele.dumpCount);
}
}
max.value = Math.max(...list);
min.value = Math.min(...list);
}
function processTree(arr: StackElement[]) {
const copyArr = JSON.parse(JSON.stringify(arr));
const copyArr = (window as any).structuredClone(arr);
const obj: any = {};
let res = null;
let min = 1;
let max = 1;
for (const item of copyArr) {
item.originId = item.id;
item.parentId = String(Number(item.parentId) + 1);
item.originId = String(Number(item.id) + 1);
item.name = item.symbol;
delete item.id;
obj[item.originId] = item;
if (item.dumpCount > max) {
max = item.dumpCount;
}
if (item.dumpCount < min) {
min = item.dumpCount;
}
}
const scale = d3.scaleLinear().domain([min, max]).range([1, 200]);
const scale = d3.scaleLinear().domain([min.value, max.value]).range([1, 200]);
for (const item of copyArr) {
if (item.parentId === "0") {
if (item.parentId === "1") {
const val = Number(scale(item.dumpCount).toFixed(4));
res = item;
res.value = val;
@ -145,4 +205,8 @@ watch(
color: red;
margin-top: 20px;
}
.name {
word-wrap: break-word;
}
</style>

View File

@ -86,6 +86,7 @@ import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app";
import { ElMessage } from "element-plus";
import { InitTaskField, TargetTypes } from "./data";
/* global defineEmits */
const emits = defineEmits(["close"]);
const eBPFStore = useEbpfStore();
@ -97,6 +98,7 @@ const type = ref<string>(TargetTypes[0].value);
const monitorTime = ref<string>(InitTaskField.monitorTimeEn[0].value);
const monitorDuration = ref<number>(10);
const time = ref<Date>(appStore.durationRow.start);
const disabled = ref<boolean>(false);
function changeMonitorTime(opt: string) {
monitorTime.value = opt;
@ -111,23 +113,24 @@ function changeType(opt: any[]) {
}
async function createTask() {
if (!labels.value.length) {
ElMessage.warning("no labels");
if (disabled.value) {
return;
}
disabled.value = true;
const date = monitorTime.value === "0" ? new Date() : time.value;
const params = {
serviceId: selectorStore.currentService.id,
processLabels: labels.value,
startTime: date.getTime(),
duration: monitorDuration.value * 60,
targetType: "ON_CPU",
targetType: type.value,
};
const res = await eBPFStore.createTask(params);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
disabled.value = false;
if (!res.data.createTaskData.status) {
ElMessage.error(res.data.createTaskData.errorReason);
return;

View File

@ -34,7 +34,15 @@ limitations under the License. -->
}"
>
<div class="ell">
<span>{{ i.processLabels.join(" ") }}</span>
<span>
{{
i.targetType +
": " +
(i.processLabels.length
? i.processLabels.join(" ")
: `All Processes`)
}}
</span>
<a class="profile-btn r" @click="viewDetail = true">
<Icon iconName="view" size="middle" />
</a>
@ -74,7 +82,9 @@ limitations under the License. -->
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("labels") }}:</span>
<span class="g-sm-8 wba">{{ selectedTask.processLabels }}</span>
<span class="g-sm-8 wba">
{{ selectedTask.processLabels.join(";") }}
</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("monitorTime") }}:</span>
@ -117,6 +127,7 @@ const viewDetail = ref<boolean>(false);
async function changeTask(item: EBPFTaskList) {
selectedTask.value = item;
ebpfStore.setSelectedTask(item);
const res = await ebpfStore.getEBPFSchedules({
taskId: item.taskId,
});
@ -128,6 +139,7 @@ watch(
() => ebpfStore.taskList,
() => {
selectedTask.value = ebpfStore.taskList[0] || {};
ebpfStore.setSelectedTask(selectedTask.value);
}
);
</script>

View File

@ -28,7 +28,15 @@ export const NewTaskField = {
maxSamplingCount: { key: 5, label: "5" },
};
export const TargetTypes = [{ label: "ON_CPU", value: "ON_CPU" }];
export const TargetTypes = [
{ label: "ON_CPU", value: "ON_CPU" },
{ label: "OFF_CPU", value: "OFF_CPU" },
];
export const AggregateTypes = [
{ label: "Count", value: "COUNT" },
{ label: "Duration", value: "DURATION" },
];
export const InitTaskField = {
monitorTimeEn: [

View File

@ -0,0 +1,129 @@
<!-- 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 ref="timeline" class="events"></div>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted } from "vue";
import dayjs from "dayjs";
import { useThrottleFn } from "@vueuse/core";
import { useEventStore } from "@/store/modules/event";
import { DataSet, Timeline } from "vis-timeline/standalone";
import "vis-timeline/styles/vis-timeline-graph2d.css";
const eventStore = useEventStore();
/*global Nullable */
const timeline = ref<Nullable<HTMLDivElement>>(null);
const visGraph = ref<Nullable<any>>(null);
const oldVal = ref<{ width: number; height: number }>({ width: 0, height: 0 });
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
new Date(dayjs(date).format(pattern));
const visDate = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern);
onMounted(() => {
oldVal.value = (timeline.value && timeline.value.getBoundingClientRect()) || {
width: 0,
height: 0,
};
useThrottleFn(resize, 500)();
});
function visTimeline() {
if (!timeline.value) {
return;
}
if (visGraph.value) {
visGraph.value.destroy();
}
const h = timeline.value.getBoundingClientRect().height;
const events = eventStore.events.map((d, index) => {
return {
id: index + 1,
content: d.name,
start: dateFormat(d.startTime),
end: dateFormat(d.endTime),
data: d,
className: d.type,
};
});
const items: any = new DataSet(events);
const options: any = {
height: h,
width: "100%",
locale: "en",
groupHeightMode: "fitItems",
autoResize: false,
tooltip: {
overflowMethod: "cap",
template(item) {
const data = item.data || {};
let tmp = `<div>ID: ${data.uuid || ""}</div>
<div>Name: ${data.name || ""}</div>
<div>Event Type: ${data.type || ""}</div>
<div>Start Time: ${data.startTime ? visDate(data.startTime) : ""}</div>
<div>End Time: ${data.endTime ? visDate(data.endTime) : ""}</div>
<div>Message: ${data.message || ""}</div>
<div>Service: ${data.source.service || ""}</div>`;
if (data.source.endpoint) {
tmp += `<div>Endpoint: ${data.source.endpoint}</div>`;
}
if (data.source.instance) {
tmp += `<div>Service Instance: ${data.source.instance}</div>`;
}
return tmp;
},
},
};
visGraph.value = new Timeline(timeline.value, items, options);
}
function resize() {
const observer = new ResizeObserver((entries) => {
const entry = entries[0];
const cr = entry.contentRect;
if (
Math.abs(cr.width - oldVal.value.width) < 3 &&
Math.abs(cr.height - oldVal.value.height) < 3
) {
return;
}
visTimeline();
oldVal.value = { width: cr.width, height: cr.height };
});
if (timeline.value) {
observer.observe(timeline.value);
}
}
watch(
() => eventStore.events,
() => {
visTimeline();
}
);
</script>
<style lang="scss" scoped>
.events {
width: calc(100% - 5px);
margin: 0 5px 5px 0;
height: 100%;
min-height: 150px;
}
.message {
max-width: 400px;
text-overflow: ellipsis;
overflow: hidden;
}
</style>

View File

@ -0,0 +1,235 @@
<!-- 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="flex-h row">
<div class="mr-5" v-if="dashboardStore.entity !== EntityType[3].value">
<span class="grey mr-5"> {{ t("instance") }}: </span>
<Selector
size="small"
:value="state.instance.value"
:options="eventStore.instances"
placeholder="Select a instance"
@change="changeField('instance', $event)"
/>
</div>
<div class="mr-5" v-if="dashboardStore.entity !== EntityType[2].value">
<span class="grey mr-5"> {{ t("endpoint") }}: </span>
<Selector
size="small"
:value="state.endpoint.value"
:options="eventStore.endpoints"
placeholder="Select a endpoint"
@change="changeField('endpoint', $event)"
:isRemote="true"
@query="searchEndpoints"
/>
</div>
<div class="mr-5">
<span class="grey">{{ t("eventsType") }}: </span>
<Selector
v-model="state.eventType"
:options="EventTypes"
placeholder="Select a type"
@change="changeField('eventType', $event)"
class="event-tool-input"
size="small"
/>
</div>
<el-pagination
v-model:currentPage="pageNum"
v-model:page-size="pageSize"
layout="prev, pager, next"
:total="total"
@current-change="updatePage"
:pager-count="5"
small
/>
<el-button
class="search-btn"
size="small"
type="primary"
@click="queryEvents"
>
{{ t("search") }}
</el-button>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, reactive, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useEventStore } from "@/store/modules/event";
import { useDashboardStore } from "@/store/modules/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors";
import { ElMessage } from "element-plus";
import { EntityType } from "../../data";
import { EventTypes } from "./data";
/*global defineProps */
const props = defineProps({
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const eventStore = useEventStore();
const pageSize = 20;
const pageNum = ref<number>(1);
const state = reactive<any>({
instance: { value: "", label: "All", id: "" },
endpoint: { value: "", label: "All", id: "" },
eventType: { value: "", label: "All" },
});
const total = computed(() =>
eventStore.events.length === pageSize
? pageSize * pageNum.value + 1
: pageSize * pageNum.value
);
if (props.needQuery) {
init();
}
async function init() {
fetchSelectors();
await queryEvents();
state.instance = { value: "", label: "All" };
state.endpoint = { value: "", label: "All" };
}
function fetchSelectors() {
if (dashboardStore.entity === EntityType[2].value) {
getInstances();
return;
}
if (dashboardStore.entity === EntityType[3].value) {
getEndpoints();
return;
}
if (dashboardStore.entity === EntityType[0].value) {
getInstances();
getEndpoints();
}
}
async function getEndpoints(id?: string) {
const resp = await eventStore.getEndpoints(id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.endpoint = eventStore.endpoints[0];
}
async function getInstances(id?: string) {
const resp = await eventStore.getInstances(id);
if (resp.errors) {
ElMessage.error(resp.errors);
return;
}
state.instance = eventStore.instances[0];
}
async function queryEvents() {
let endpoint = state.endpoint.value,
instance = state.instance.value;
if (dashboardStore.entity === EntityType[2].value) {
endpoint = selectorStore.currentPod.id;
}
if (dashboardStore.entity === EntityType[3].value) {
instance = selectorStore.currentPod.id;
}
eventStore.setEventCondition({
// layer: dashboardStore.layerId,
paging: {
pageNum: pageNum.value,
pageSize: pageSize,
},
source: {
service: selectorStore.currentService.value || "",
endpoint: endpoint || "",
serviceInstance: instance || "",
},
type: state.eventType.value || undefined,
});
const res = await eventStore.getEvents();
if (res && res.errors) {
ElMessage.error(res.errors);
}
}
function changeField(type: string, opt: any[]) {
state[type] = opt[0];
}
async function searchEndpoints(keyword: string) {
const resp = await eventStore.getEndpoints(keyword);
if (resp.errors) {
ElMessage.error(resp.errors);
}
}
function updatePage(p: number) {
pageNum.value = p;
queryEvents();
}
watch(
() => [selectorStore.currentService],
() => {
if (dashboardStore.entity === EntityType[0].value) {
init();
}
}
);
watch(
() => [selectorStore.currentPod],
() => {
if (dashboardStore.entity === EntityType[0].value) {
return;
}
init();
}
);
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
init();
}
}
);
</script>
<style lang="scss" scoped>
.inputs {
width: 120px;
}
.inputs-max {
width: 270px;
}
.search-btn {
cursor: pointer;
width: 120px;
}
.selected {
display: inline-block;
padding: 0 3px;
border-radius: 3px;
overflow: hidden;
color: #3d444f;
border: 1px dashed #aaa;
font-size: 12px;
margin: 0 2px;
}
</style>

View File

@ -13,95 +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="flex-h log-wrapper">
<div v-if="currentSearchTerm === 'column'" class="flex-h items-center mr-5">
<el-dropdown class="dark" :hide-on-click="false">
<span class="cursor-pointer">
Select visible columns<el-icon class="el-icon--right"
><arrow-down
/></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu id="toggleColumn" class="dropdownSelector">
<el-dropdown-item style="padding: 0">
<div
style="width: 100%; padding: 5px 16px"
class="flex-h items-center"
@click="logStore.toggleAllColumns(true)"
>
<el-icon><View /></el-icon>
<span style="margin-right: 10px">Show All</span>
</div>
</el-dropdown-item>
<el-dropdown-item style="padding: 0">
<div
style="width: 100%; padding: 5px 16px"
class="flex-h items-center"
@click="logStore.toggleAllColumns(false)"
>
<el-icon><Hide /></el-icon>
<span style="margin-right: 10px">Hide All</span>
</div>
</el-dropdown-item>
<el-divider />
<el-dropdown-item
style="padding: 0"
v-for="item in logStore.serviceLogColumn"
:key="item.value"
>
<el-checkbox class="custom-checkbox" v-model="item.isVisible">
<span>{{ item.value }}</span>
</el-checkbox>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button class="toggle-btn mx-3danger" @click="setSearchTerm('')">
<Icon iconSize="sm" iconName="cancel" />
</el-button>
</div>
<div v-if="!currentSearchTerm.length" class="flex-h items-center">
<div v-for="(item, index) in arrayOfFilters" :key="index">
<el-tooltip
class="box-item"
effect="dark"
:content="item.description"
placement="bottom-start"
>
<el-button
type="success"
:class="[activeTerms.includes(item.name) ? 'active-toggle' : '']"
class="toggle-btn mx-3"
v-show="item.isVisible"
@click="setSearchTerm(item.name)"
>
<Icon iconSize="sm" :iconName="item.iconName" />
</el-button>
</el-tooltip>
</div>
<el-tooltip
class="box-item"
effect="dark"
content="Toggle columns"
placement="bottom-start"
>
<el-button
type="success"
:class="[false ? 'active-toggle' : '']"
class="toggle-btn mx-3"
@click="toggleColumSelector"
>
<Icon iconSize="sm" iconName="epic" />
</el-button>
</el-tooltip>
</div>
<div class="flex-h items-center">
<div class="flex-h items-center" v-if="currentSearchTerm === 'service'">
<div
class="mr-5 flex-h items-center"
v-if="dashboardStore.entity === EntityType[1].value"
>
<div class="flex-h row">
<div class="mr-5" v-if="dashboardStore.entity === EntityType[1].value">
<span class="grey mr-5">{{ t("service") }}:</span>
<Selector
size="small"
@ -111,17 +24,7 @@ limitations under the License. -->
@change="changeField('service', $event)"
/>
</div>
<b v-else>{{ t("service") }} data not available</b>
</div>
<div class="flex-h items-center" v-if="currentSearchTerm === 'instance'">
<div
class="mr-5 items-center flex-h"
v-if="
dashboardStore.entity !== EntityType[3].value &&
currentSearchTerm === 'instance'
"
>
<div class="mr-5" v-if="dashboardStore.entity !== EntityType[3].value">
<span class="grey mr-5">
{{ isBrowser ? t("version") : t("instance") }}:
</span>
@ -133,20 +36,10 @@ limitations under the License. -->
@change="changeField('instance', $event)"
/>
</div>
<b v-else>{{ t("instance") }} data not available</b>
</div>
<div class="flex-h items-center" v-if="currentSearchTerm === 'endpoints'">
<div
class="mr-5 flex-h items-center"
v-if="
dashboardStore.entity !== EntityType[2].value &&
currentSearchTerm === 'endpoints'
"
>
<span class="grey mr-5"
>{{ isBrowser ? t("page") : t("endpoint") }}:</span
>
<div class="mr-5" v-if="dashboardStore.entity !== EntityType[2].value">
<span class="grey mr-5">
{{ isBrowser ? t("page") : t("endpoint") }}:
</span>
<Selector
size="small"
:value="state.endpoint.value"
@ -157,34 +50,37 @@ limitations under the License. -->
@query="searchEndpoints"
/>
</div>
<b v-else>{{ t("endpoint") }} data not available</b>
<div class="mr-5" v-if="isBrowser">
<span class="grey mr-5"> {{ t("category") }}: </span>
<Selector
size="small"
:value="state.category.value"
:options="ErrorCategory"
placeholder="Select a category"
@change="changeField('category', $event)"
/>
</div>
<el-button
class="search-btn"
size="small"
type="primary"
@click="searchLogs"
>
{{ t("search") }}
</el-button>
</div>
<!-- <div class="row tips">
<b>{{ t("conditionNotice") }}</b>
</div> -->
<div class="flex-h items-center">
<div class="mr-5 flex-h items-center traceId" v-show="!isBrowser">
<div class="flex-h items-center" v-if="currentSearchTerm === 'traceId'">
<div class="flex-h row" v-show="!isBrowser">
<div class="mr-5 traceId">
<span class="grey mr-5">{{ t("traceID") }}:</span>
<el-input v-model="traceId" class="inputs-max" size="small" />
</div>
<ConditionTags :type="'LOG'" @update="updateTags" />
</div>
<keep-alive>
<ConditionTags
ref="logTagsComponent"
v-if="currentSearchTerm === 'tags'"
:type="'LOG'"
@update="updateTags"
/>
</keep-alive>
<div class="row tips" v-show="!isBrowser">
<b>{{ t("conditionNotice") }}</b>
</div>
<div class="flex-h items-center" v-show="!isBrowser">
<div
class="mr-5 flex-h items-center"
v-show="supportQueryLogsByKeywords && currentSearchTerm === 'keywords'"
>
<div class="flex-h" v-show="!isBrowser">
<div class="mr-5" v-show="logStore.supportQueryLogsByKeywords">
<span class="mr-5 grey">{{ t("keywordsOfContent") }}:</span>
<span class="log-tags">
<span
@ -204,12 +100,7 @@ limitations under the License. -->
@change="addLabels('keywordsOfContent')"
/>
</div>
<div
class="mr-5 flex-h items-center"
v-show="
supportExcludeQueryLogsByKeywords && currentSearchTerm === 'exclude'
"
>
<div class="mr-5" v-show="logStore.supportQueryLogsByKeywords">
<span class="grey mr-5"> {{ t("excludingKeywordsOfContent") }}: </span>
<span class="log-tags">
<span
@ -236,36 +127,10 @@ limitations under the License. -->
</span>
</el-tooltip>
</div>
<!-- Search&cancel buttons -->
<div
v-if="currentSearchTerm.length && currentSearchTerm !== 'column'"
class="flex-h items-center"
>
<el-button
class="search-btn toggle-btn"
size="small"
type="primary"
@click="searchLogs"
>
<Icon iconSize="sm" iconName="search" />
</el-button>
<el-button
class="search-btn toggle-btn"
size="small"
type="primary"
@click="cancelSearchTerm"
>
<Icon iconSize="sm" iconName="cancel" />
</el-button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ArrowDown, View, Hide } from "@element-plus/icons-vue";
import { ref, reactive, watch, computed, onMounted } from "vue";
import { useRoute } from "vue-router";
import { ref, reactive, watch, onUnmounted } from "vue";
import { useI18n } from "vue-i18n";
import { Option } from "@/types/app";
import { useLogStore } from "@/store/modules/log";
@ -275,26 +140,20 @@ import { useSelectorStore } from "@/store/modules/selectors";
import ConditionTags from "@/views/components/ConditionTags.vue";
import { ElMessage } from "element-plus";
import { EntityType } from "../../data";
import { ErrorCategory } from "./data";
/*global defineProps */
const props = defineProps({
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n();
const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore();
const { portal } = useRoute().query;
const logStore = useLogStore();
const showColumList = ref<boolean>(false);
const traceId = ref<string>("");
const keywordsOfContent = ref<string[]>([]);
const excludingKeywordsOfContent = ref<string[]>([]);
const supportQueryLogsByKeywords = computed<boolean>(() => {
return logStore.supportQueryLogsByKeywords;
});
const supportExcludeQueryLogsByKeywords = computed<boolean>(() => {
return logStore.supportQueryLogsByKeywords;
});
const currentSearchTerm = ref<string>("");
const activeTerms = ref<string[]>([]);
const tagsList = ref<string[]>([]);
const tagsMap = ref<Option[]>([]);
const contentStr = ref<string>("");
@ -304,75 +163,10 @@ const state = reactive<any>({
instance: { value: "0", label: "All" },
endpoint: { value: "0", label: "All" },
service: { value: "", label: "" },
category: { value: "ALL", label: "All" },
});
const logTagsComponent = ref<InstanceType<typeof ConditionTags> | null>(null);
interface filtersObject {
name: string;
iconName: string;
description: string;
isVisible?: boolean | unknown; // one of the situations is dependent on an api call
}
const arrayOfFilters = ref<filtersObject[]>([
{
name: "traceId",
iconName: "timeline",
description: "Trace ID",
isVisible: true,
},
{
name: "tags",
iconName: "epic",
description: "Tags",
isVisible: true,
},
{
name: "keywords",
iconName: "library_books",
description: "Keywords",
isVisible: supportQueryLogsByKeywords,
},
{
name: "exclude",
iconName: "issue-child",
description: "Exclude keywords",
isVisible: supportExcludeQueryLogsByKeywords,
},
{
name: "instance",
iconName: "epic",
description: "Instance",
isVisible: dashboardStore.entity !== EntityType[3].value,
},
{
name: "service",
iconName: "settings",
description: "Service",
isVisible: dashboardStore.entity === EntityType[1].value,
},
{
name: "endpoints",
iconName: "timeline",
description: "Endpoints",
isVisible: dashboardStore.entity !== EntityType[2].value,
},
]);
onMounted(() => {
if (portal) {
["endpoint", "time", "contentType", "tags", "traceID"].forEach((col) =>
logStore.hideColumns(col)
);
}
});
init();
function toggleColumSelector() {
showColumList.value = !showColumList.value;
setSearchTerm("column");
}
function hideTags() {
let tagsWrap = document.querySelector(".el-select__tags");
if (!tagsWrap) return;
tagsWrap.style.display = "none";
if (props.needQuery) {
init();
}
async function init() {
const resp = await logStore.getLogsByKeywords();
@ -433,46 +227,7 @@ async function getInstances(id?: string) {
}
state.instance = logStore.instances[0];
}
function addToActiveTerms() {
activeTerms.value.push(currentSearchTerm.value);
}
function removeFromActiveTerms() {
activeTerms.value = activeTerms.value.filter(
(term) => term !== currentSearchTerm.value
);
}
function handleActiveSearchTerms() {
switch (currentSearchTerm.value) {
case "traceId":
if (!traceId.value.length) return;
addToActiveTerms();
break;
case "tags":
if (!tagsList.value.length) return;
addToActiveTerms();
break;
case "keywords":
if (!keywordsOfContent.value.length) return;
addToActiveTerms();
break;
case "exclude":
if (!excludingKeywordsOfContent.value.length) return;
addToActiveTerms();
break;
case "instance":
addToActiveTerms();
break;
case "service":
addToActiveTerms();
break;
case "endpoints":
addToActiveTerms();
break;
}
}
function searchLogs() {
handleActiveSearchTerms();
currentSearchTerm.value = "";
let endpoint = "",
instance = "";
if (dashboardStore.entity === EntityType[2].value) {
@ -481,6 +236,18 @@ function searchLogs() {
if (dashboardStore.entity === EntityType[3].value) {
instance = selectorStore.currentPod.id;
}
if (dashboardStore.layerId === "BROWSER") {
logStore.setLogCondition({
serviceId: selectorStore.currentService
? selectorStore.currentService.id
: state.service.id,
pagePathId: endpoint || state.endpoint.id || undefined,
serviceVersionId: instance || state.instance.id || undefined,
paging: { pageNum: 1, pageSize: 15 },
queryDuration: appStore.durationTime,
category: state.category.value,
});
} else {
logStore.setLogCondition({
serviceId: selectorStore.currentService
? selectorStore.currentService.id
@ -491,9 +258,10 @@ function searchLogs() {
keywordsOfContent: keywordsOfContent.value,
excludingKeywordsOfContent: excludingKeywordsOfContent.value,
tags: tagsMap.value.length ? tagsMap.value : undefined,
paging: { pageNum: 1, pageSize: 15, needTotal: true },
paging: { pageNum: 1, pageSize: 15 },
relatedTrace: traceId.value ? { traceId: traceId.value } : undefined,
});
}
queryLogs();
}
async function queryLogs() {
@ -555,45 +323,9 @@ function removeExcludeContent(index: number) {
});
excludingContentStr.value = "";
}
function setSearchTerm(term: string) {
currentSearchTerm.value = term;
if (term === "column") {
setTimeout(() => {
hideTags();
}, 200);
}
}
function cancelSearchTerm() {
switch (currentSearchTerm.value) {
case "traceId":
traceId.value = "";
break;
case "tags":
tagsList.value = [];
tagsMap.value = [];
logTagsComponent.value?.emptyTags();
break;
case "keywords":
keywordsOfContent.value = [];
break;
case "exclude":
excludingKeywordsOfContent.value = [];
break;
case "instance":
state.instance = { value: "0", label: "All" };
break;
case "endpoints":
state.endpoint = { value: "0", label: "All" };
getEndpoints();
break;
case "service":
state.service = { value: "", label: "" };
break;
}
removeFromActiveTerms();
currentSearchTerm.value = "";
searchLogs();
}
onUnmounted(() => {
logStore.resetCondition();
});
watch(
() => selectorStore.currentService,
() => {
@ -621,44 +353,13 @@ watch(
);
</script>
<style lang="scss" scoped>
#toggleColumn.el-dropdown-menu {
padding: 0 !important;
}
.el-checkbox.custom-checkbox {
width: 100%;
padding: 5px 16px;
}
.dropdownSelector {
background: var(--nice-black);
}
.el-dropdown-link {
cursor: pointer;
color: var(--el-color-primary);
display: flex;
align-items: center;
}
.el-divider--horizontal {
margin: 0 !important;
}
.cursor-pointer {
cursor: pointer;
}
.custom-checkbox .el-checkbox__input.is-checked + .el-checkbox__label,
.custom-checkbox .el-checkbox__label {
color: var(--spp-white) !important;
}
.inputs {
width: 120px;
}
.items-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.row {
margin-bottom: 5px;
position: relative;
}
.inputs-max {
@ -670,8 +371,11 @@ watch(
}
.search-btn {
margin-left: 20px;
position: absolute;
top: 0;
right: 10px;
cursor: pointer;
width: 120px;
}
.tips {
@ -711,40 +415,4 @@ watch(
margin-left: 3px;
cursor: pointer;
}
/* buttons*/
.el-button span {
font-size: 10px !important;
}
.toggle-btn {
height: 18px;
margin: 0 5px;
}
.active-toggle.toggle-btn {
background: rgba(4, 147, 114, 1) !important;
span {
color: #275410 !important;
}
}
.items-center {
align-items: center;
}
.space-between {
justify-content: space-between !important;
}
.el-select-dropdown.is-multiple .el-select-dropdown__item.selected {
background: transparent;
}
.el-select-dropdown.is-multiple .el-select-dropdown__item.selected {
width: 100%;
padding: 0 32px 0 20px;
}
.el-select-dropdown__item.selected {
display: block;
width: 100%;
padding: 0 32px 0 20px;
}
.el-select-dropdown.is-multiple .el-select-dropdown__item.selected::after {
display: none;
}
</style>

Some files were not shown because too many files have changed in this diff Show More