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", "element-plus": "^2.0.2",
"jquery": "^3.6.0", "jquery": "^3.6.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"monaco-editor": "^0.27.0",
"pinia": "^2.0.5", "pinia": "^2.0.5",
"sockjs": "^0.3.24", "sockjs": "^0.3.24",
"sockjs-client": "^1.6.0", "sockjs-client": "^1.6.0",
@ -60,6 +61,7 @@
"eslint-plugin-vue": "^7.0.0", "eslint-plugin-vue": "^7.0.0",
"husky": "^7.0.4", "husky": "^7.0.4",
"lint-staged": "^12.1.3", "lint-staged": "^12.1.3",
"monaco-editor-webpack-plugin": "^4.1.2",
"node-sass": "^6.0.1", "node-sass": "^6.0.1",
"postcss-html": "^1.3.0", "postcss-html": "^1.3.0",
"postcss-scss": "^4.0.2", "postcss-scss": "^4.0.2",
@ -11233,9 +11235,10 @@
} }
}, },
"node_modules/eventsource": { "node_modules/eventsource": {
"version": "1.1.0", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.1.tgz",
"integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==", "integrity": "sha512-qV5ZC0h7jYIAOhArFJgSfdyz6rALJyb270714o7ZtNnw2WSJ+eexhKtE0O8LYPRsHZHf2osHKZBxGPvm3kPkCA==",
"dev": true,
"dependencies": { "dependencies": {
"original": "^1.0.0" "original": "^1.0.0"
}, },
@ -18371,6 +18374,38 @@
"node": "*" "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": { "node_modules/move-concurrently": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@ -37989,9 +38024,10 @@
"dev": true "dev": true
}, },
"eventsource": { "eventsource": {
"version": "1.1.0", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.1.tgz",
"integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==", "integrity": "sha512-qV5ZC0h7jYIAOhArFJgSfdyz6rALJyb270714o7ZtNnw2WSJ+eexhKtE0O8LYPRsHZHf2osHKZBxGPvm3kPkCA==",
"dev": true,
"requires": { "requires": {
"original": "^1.0.0" "original": "^1.0.0"
} }
@ -43595,6 +43631,33 @@
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" "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": { "move-concurrently": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",

View File

@ -23,6 +23,7 @@
"element-plus": "^2.0.2", "element-plus": "^2.0.2",
"jquery": "^3.6.0", "jquery": "^3.6.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"monaco-editor": "^0.27.0",
"pinia": "^2.0.5", "pinia": "^2.0.5",
"sockjs": "^0.3.24", "sockjs": "^0.3.24",
"sockjs-client": "^1.6.0", "sockjs-client": "^1.6.0",
@ -62,6 +63,7 @@
"eslint-plugin-vue": "^7.0.0", "eslint-plugin-vue": "^7.0.0",
"husky": "^7.0.4", "husky": "^7.0.4",
"lint-staged": "^12.1.3", "lint-staged": "^12.1.3",
"monaco-editor-webpack-plugin": "^4.1.2",
"node-sass": "^6.0.1", "node-sass": "^6.0.1",
"postcss-html": "^1.3.0", "postcss-html": "^1.3.0",
"postcss-scss": "^4.0.2", "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 See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<router-view :key="$route.fullPath" /> <router-view />
</template> </template>
<style> <style>
#app { #app {
color: var(--spp-white); color: var(--spp-white);
height: 100%; height: 100%;
overflow: auto;
min-width: 1024px;
} }
</style> </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) (state.showMinutes = state.showSeconds = false)
" "
:class="{ on: state.showHours }" :class="{ on: state.showHours }"
>{{ state.hour || dd }}</a >{{ dd(state.hour) }}</a
> >
<span>:</span> <span>:</span>
<a <a
@ -257,7 +257,7 @@ limitations under the License. -->
(state.showHours = state.showSeconds = false) (state.showHours = state.showSeconds = false)
" "
:class="{ on: state.showMinutes }" :class="{ on: state.showMinutes }"
>{{ state.minute || dd }}</a >{{ dd(state.minute) }}</a
> >
<span v-show="state.m !== 'D'"> <span v-show="state.m !== 'D'">
<span>:</span> <span>:</span>
@ -268,7 +268,7 @@ limitations under the License. -->
(state.showHours = state.showMinutes = false) (state.showHours = state.showMinutes = false)
" "
:class="{ on: state.showSeconds }" :class="{ on: state.showSeconds }"
>{{ state.second || dd }}</a >{{ dd(state.second) }}</a
> >
</span> </span>
</div> </div>
@ -464,7 +464,6 @@ const status = (
const minutes = time.getMinutes(); const minutes = time.getMinutes();
const seconds = time.getSeconds(); const seconds = time.getSeconds();
const milliseconds = time.getMilliseconds(); const milliseconds = time.getMilliseconds();
const dd = (t: number) => `0${t}`.slice(-2);
const map: { [key: string]: string | number } = { const map: { [key: string]: string | number } = {
YYYY: year, YYYY: year,
MM: dd(month + 1), MM: dd(month + 1),

View File

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

View File

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

View File

@ -45,6 +45,5 @@ export const Alarm = {
endTime 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 = { export const queryEBPFSchedules = {
variable: "$taskId: ID!, $duration: Duration!", variable: "$taskId: ID!",
query: ` query: `
eBPFSchedules: queryEBPFProfilingSchedules(taskId: $taskId, duration: $duration) { eBPFSchedules: queryEBPFProfilingSchedules(taskId: $taskId) {
scheduleId scheduleId
taskId taskId
process { process {
@ -60,7 +60,6 @@ export const queryEBPFSchedules = {
serviceName serviceName
instanceId instanceId
instanceName instanceName
layer
agentId agentId
detectType detectType
attributes { attributes {
@ -76,9 +75,9 @@ export const queryEBPFSchedules = {
export const analysisEBPFResult = { export const analysisEBPFResult = {
variable: variable:
"$scheduleIdList: [ID!]!, $timeRanges: [EBPFProfilingAnalyzeTimeRange!]!", "$scheduleIdList: [ID!]!, $timeRanges: [EBPFProfilingAnalyzeTimeRange!]!, $aggregateType: EBPFProfilingAnalyzeAggregateType",
query: ` query: `
analysisEBPFResult: analysisEBPFProfilingResult(scheduleIdList: $scheduleIdList, timeRanges: $timeRanges) { analysisEBPFResult: analysisEBPFProfilingResult(scheduleIdList: $scheduleIdList, timeRanges: $timeRanges, aggregateType: $aggregateType) {
tip tip
trees { trees {
elements { elements {

View File

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

View File

@ -30,7 +30,6 @@ export const QueryBrowserErrorLogs = {
stack stack
grade grade
} }
total
}`, }`,
}; };
@ -54,7 +53,6 @@ export const QueryServiceLogs = {
value value
} }
} }
total
}`, }`,
}; };
@ -63,3 +61,15 @@ export const QueryLogsByKeywords = {
query: ` query: `
support: supportQueryLogsByKeywords`, 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

@ -20,10 +20,10 @@ export const Services = {
services: listServices(layer: $layer) { services: listServices(layer: $layer) {
id id
value: name value: name
label: name label: name
group group
layers layers
normal normal
} }
`, `,
}; };
@ -36,17 +36,16 @@ export const Instances = {
variable: "$serviceId: ID!, $duration: Duration!", variable: "$serviceId: ID!, $duration: Duration!",
query: ` query: `
pods: listInstances(duration: $duration, serviceId: $serviceId) { pods: listInstances(duration: $duration, serviceId: $serviceId) {
id id
value: name value: name
label: name label: name
language language
instanceUUID instanceUUID
layer attributes {
attributes { name
name value
value
}
} }
}
`, `,
}; };
export const Endpoints = { export const Endpoints = {
@ -66,10 +65,10 @@ export const getService = {
service: getService(serviceId: $serviceId) { service: getService(serviceId: $serviceId) {
id id
value: name value: name
label: name label: name
group group
layers layers
normal normal
} }
`, `,
}; };
@ -79,16 +78,15 @@ export const getInstance = {
query: ` query: `
instance: getInstance(instanceId: $instanceId) { instance: getInstance(instanceId: $instanceId) {
id id
value: name value: name
label: name label: name
language language
instanceUUID instanceUUID
layer attributes {
attributes { name
name value
value
}
} }
}
`, `,
}; };
@ -97,10 +95,10 @@ export const getEndpoint = {
query: ` query: `
endpoint: getEndpointInfo(endpointId: $endpointId) { endpoint: getEndpointInfo(endpointId: $endpointId) {
id id
value: name value: name
label: name label: name
serviceId serviceId
serviceName serviceName
} }
`, `,
}; };

View File

@ -27,7 +27,6 @@ export const Traces = {
isError isError
traceIds 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 alarm from "./query/alarm";
import * as event from "./query/event"; import * as event from "./query/event";
import * as ebpf from "./query/ebpf"; import * as ebpf from "./query/ebpf";
import * as demandLog from "./query/demand-log";
const query: { [key: string]: string } = { const query: { [key: string]: string } = {
...app, ...app,
@ -38,6 +39,7 @@ const query: { [key: string]: string } = {
...alarm, ...alarm,
...event, ...event,
...ebpf, ...ebpf,
...demandLog,
}; };
class Graphql { class Graphql {
private queryData = ""; 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, QueryBrowserErrorLogs,
QueryServiceLogs, QueryServiceLogs,
QueryLogsByKeywords, QueryLogsByKeywords,
LogTagValues,
LogTagKeys,
} from "../fragments/log"; } from "../fragments/log";
export const queryBrowserErrorLogs = `query queryBrowserErrorLogs(${QueryBrowserErrorLogs.variable}) { export const queryBrowserErrorLogs = `query queryBrowserErrorLogs(${QueryBrowserErrorLogs.variable}) {
${QueryBrowserErrorLogs.query}}`; ${QueryBrowserErrorLogs.query}}`;
export const queryServiceLogs = `query queryLogs(${QueryServiceLogs.variable}) {${QueryServiceLogs.query}}`; export const queryServiceLogs = `query queryLogs(${QueryServiceLogs.variable}) {${QueryServiceLogs.query}}`;
export const queryLogsByKeywords = `query queryLogsByKeywords {${QueryLogsByKeywords.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. * 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 queryTraces = `query queryTraces(${Traces.variable}) {${Traces.query}}`;
export const queryTrace = `query queryTrace(${TraceSpans.variable}) {${TraceSpans.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)) { if (!el || !unref(el)) {
return; return;
} }
const { width, height } = el.getBoundingClientRect();
if (!width || !height) {
return;
}
chartInstance = echarts.init(el, t); chartInstance = echarts.init(el, t);
const { removeEvent } = useEventListener({ const { removeEvent } = useEventListener({
el: window, el: window,

View File

@ -331,7 +331,7 @@ export function useQueryTopologyMetrics(metrics: string[], ids: string[]) {
} }
function calculateExp( function calculateExp(
arr: { value: number }[], arr: { value: number }[],
config: { calculation: string } config: { calculation?: string }
): (number | string)[] { ): (number | string)[] {
const sum = arr const sum = arr
.map((d: { value: number }) => d.value) .map((d: { value: number }) => d.value)
@ -356,7 +356,7 @@ function calculateExp(
export function aggregation( export function aggregation(
val: number, val: number,
config: { calculation: string } config: { calculation?: string }
): number | string { ): number | string {
let data: number | string = Number(val); 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 See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <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="title">{{ appStore.pageTitle || t(pageName) }}</div>
<div class="app-config"> <div class="app-config">
<span class="red" v-show="timeRange">{{ t("timeTips") }}</span> <span class="red" v-show="timeRange">{{ t("timeTips") }}</span>
@ -94,9 +98,6 @@ watch(
} }
); );
async function getVersion() { async function getVersion() {
if (appStore.version) {
return;
}
const res = await appStore.fetchVersion(); const res = await appStore.fetchVersion();
if (res.errors) { if (res.errors) {
ElMessage.error(res.errors); ElMessage.error(res.errors);

View File

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

View File

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

View File

@ -134,8 +134,15 @@ const msg = {
taskId: "Task ID", taskId: "Task ID",
triggerType: "Trigger Type", triggerType: "Trigger Type",
targetType: "Target Type", targetType: "Target Type",
ebpfTip: "Don't have process could profiling", ebpfTip: "Don't have a process for profiling",
processSelect: "Click to select processes", processSelect: "Click to select processes",
container: "Container",
limit: "Limit",
page: "Page",
interval: "Refresh Interval",
pause: "Pause",
begin: "Start",
seconds: "Seconds",
hourTip: "Select Hour", hourTip: "Select Hour",
minuteTip: "Select Minute", minuteTip: "Select Minute",
secondTip: "Select Second", secondTip: "Select Second",
@ -154,7 +161,7 @@ const msg = {
dashboard: "Dashboard", dashboard: "Dashboard",
topology: "Topology", topology: "Topology",
trace: "Trace", trace: "Trace",
alarm: "Alarms", alarm: "Alerting",
auto: "Auto", auto: "Auto",
reload: "Reload", reload: "Reload",
version: "Version", version: "Version",
@ -327,7 +334,7 @@ const msg = {
addExcludingKeywordsOfContent: "Please input a keyword of excluding content", addExcludingKeywordsOfContent: "Please input a keyword of excluding content",
noticeTag: "Please press Enter after inputting a tag(key=value).", noticeTag: "Please press Enter after inputting a tag(key=value).",
conditionNotice: 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", language: "Language",
}; };
export default msg; 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: "目标类型", targetType: "目标类型",
processSelect: "点击选择进程", processSelect: "点击选择进程",
ebpfTip: "没有进程可以分析", ebpfTip: "没有进程可以分析",
container: "容器",
limit: "范围",
page: "页面",
interval: "刷新间隔时间",
pause: "暂停",
begin: "开始",
seconds: "秒",
hourTip: "选择小时", hourTip: "选择小时",
minuteTip: "选择分钟", minuteTip: "选择分钟",
secondTip: "选择秒数", secondTip: "选择秒数",
@ -327,7 +334,7 @@ const msg = {
addExcludingKeywordsOfContent: "请输入一个内容不包含的关键词", addExcludingKeywordsOfContent: "请输入一个内容不包含的关键词",
noticeTag: "请输入一个标签(key=value)之后回车", noticeTag: "请输入一个标签(key=value)之后回车",
conditionNotice: conditionNotice:
"请输入一个标签、内容关键词或者内容不包含的关键词(key=value)之后回车", "请输入一个内容关键词或者内容不包含的关键词(key=value)之后回车",
language: "语言", language: "语言",
}; };
export default msg; export default msg;

View File

@ -20,11 +20,18 @@ import router from "./router";
import { store } from "./store"; import { store } from "./store";
import components from "@/components"; import components from "@/components";
import i18n from "./locales"; import i18n from "./locales";
import { useAppStoreWithOut } from "@/store/modules/app";
import "./styles/index.ts"; import "./styles/index.ts";
const app = createApp(App); const app = createApp(App);
const appStore = useAppStoreWithOut();
app.use(components); app.use(components);
app.use(i18n); app.use(i18n);
app.use(store); 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, component: Layout,
children: [ children: [
{ {
path: "/alarm", path: "/alerting",
name: "Alarm", name: "Alarm",
meta: { meta: {
exact: false, exact: false,
}, },
component: () => 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", name: "Browser",
meta: { meta: {
title: "browser", title: "browser",
headPath: "/browser",
exact: true, exact: true,
}, },
component: () => component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"), import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
}, },
{
path: "/browser/tab/:activeTabIndex",
name: "BrowserActiveTabIndex",
meta: {
notShow: true,
},
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
], ],
}, },
]; ];

View File

@ -54,69 +54,156 @@ export const routesDashboard: Array<RouteRecordRaw> = [
}, },
}, },
{ {
path: "/dashboard/:layerId/:entity/:name", path: "",
redirect: "/dashboard/:layerId/:entity/:name",
name: "Create",
component: () => component: () =>
import( import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue" /* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
), ),
name: "Create",
meta: { meta: {
title: "dashboardEdit",
exact: false,
notShow: true, notShow: true,
}, },
children: [
{
path: "/dashboard/:layerId/:entity/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "CreateChild",
},
{
path: "/dashboard/:layerId/:entity/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "CreateActiveTabIndex",
},
],
}, },
{ {
path: "/dashboard/:layerId/:entity/:serviceId/:name", path: "",
component: () => component: () =>
import( import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue" /* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
), ),
name: "View", name: "View",
redirect: "/dashboard/:layerId/:entity/:serviceId/:name",
meta: { meta: {
title: "dashboardEdit",
exact: false,
notShow: true, notShow: true,
}, },
children: [
{
path: "/dashboard/:layerId/:entity/:serviceId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewChild",
},
{
path: "/dashboard/:layerId/:entity/:serviceId/:name/tab/:activeTabIndex",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewActiveTabIndex",
},
],
}, },
{ {
path: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name", path: "",
redirect:
"/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name",
component: () => component: () =>
import( import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue" /* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
), ),
name: "ViewServiceRelation", name: "ViewServiceRelation",
meta: { meta: {
title: "dashboardEdit",
exact: false,
notShow: true, notShow: true,
}, },
children: [
{
path: "/dashboard/related/:layerId/:entity/:serviceId/:destServiceId/:name",
component: () =>
import(
/* 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: "/dashboard/:layerId/:entity/:serviceId/:podId/:name", path: "",
redirect: "/dashboard/:layerId/:entity/:serviceId/:podId/:name",
component: () => component: () =>
import( import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue" /* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
), ),
name: "ViewPod", name: "ViewPod",
meta: { meta: {
title: "dashboardEdit",
exact: false,
notShow: true, notShow: true,
}, },
children: [
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:name",
component: () =>
import(
/* 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: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name", path: "",
redirect:
"/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name",
component: () => component: () =>
import( import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue" /* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
), ),
name: "ViewPodRelation", name: "ViewPodRelation",
meta: { meta: {
title: "dashboardEdit",
exact: true,
notShow: true, notShow: true,
}, },
children: [
{
path: "/dashboard/:layerId/:entity/:serviceId/:podId/:destServiceId/:destPodId/:name",
component: () =>
import(
/* webpackChunkName: "dashboards" */ "@/views/dashboard/Edit.vue"
),
name: "ViewPodRelation",
},
{
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", name: "Database",
meta: { meta: {
title: "virtualDatabase", title: "virtualDatabase",
headPath: "/database",
exact: true, exact: true,
}, },
component: () => component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"), 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", path: "/functions",
name: "Functions", name: "Functions",
meta: { meta: {
title: "functions",
headPath: "/functions",
exact: true, exact: true,
}, },
component: () => component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"), 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", path: "/general",
name: "GeneralServices", name: "GeneralServices",
meta: { meta: {
title: "services", exact: true,
headPath: "/general/service", },
component: () =>
import(/* webpackChunkName: "layers" */ "@/views/Layer.vue"),
},
{
path: "/general/tab/:activeTabIndex",
name: "GeneralServicesActiveTabIndex",
meta: {
exact: true, exact: true,
}, },
component: () => component: () =>

View File

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

View File

@ -39,6 +39,16 @@ export const routesInfra: Array<RouteRecordRaw> = [
component: () => component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"), 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", // path: "/infrastructure/vm",
// name: "VirtualMachine", // name: "VirtualMachine",

View File

@ -33,20 +33,42 @@ export const routesK8s: Array<RouteRecordRaw> = [
path: "/kubernetes/cluster", path: "/kubernetes/cluster",
name: "KubernetesCluster", name: "KubernetesCluster",
meta: { meta: {
notShow: false,
title: "kubernetesCluster", title: "kubernetesCluster",
}, },
component: () => component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"), 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", path: "/kubernetes/service",
name: "KubernetesService", name: "KubernetesService",
meta: { meta: {
notShow: false,
title: "kubernetesService", title: "kubernetesService",
}, },
component: () => component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"), 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", name: "SkyWalkingServer",
meta: { meta: {
title: "skyWalkingServer", title: "skyWalkingServer",
headPath: "/mesh/services", },
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/self/skyWalkingServer/tab/:activeTabIndex",
name: "SkyWalkingServerActiveTabIndex",
meta: {
notShow: true,
}, },
component: () => component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"), import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
@ -44,7 +52,15 @@ export const routesSelf: Array<RouteRecordRaw> = [
name: "Satellite", name: "Satellite",
meta: { meta: {
title: "satellite", title: "satellite",
headPath: "/mesh/controlPanel", },
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/self/satellite/tab/:activeTabIndex",
name: "SatelliteActiveTabIndex",
meta: {
notShow: true,
}, },
component: () => component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"), import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),

View File

@ -33,8 +33,17 @@ export const routesMesh: Array<RouteRecordRaw> = [
path: "/mesh/services", path: "/mesh/services",
name: "MeshServices", name: "MeshServices",
meta: { meta: {
notShow: false,
title: "services", title: "services",
headPath: "/mesh/services", },
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mesh/services/tab/:activeTabIndex",
name: "MeshServicesActiveTabIndex",
meta: {
notShow: true,
}, },
component: () => component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"), import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
@ -43,8 +52,17 @@ export const routesMesh: Array<RouteRecordRaw> = [
path: "/mesh/controlPanel", path: "/mesh/controlPanel",
name: "ControlPanel", name: "ControlPanel",
meta: { meta: {
notShow: false,
title: "controlPanel", title: "controlPanel",
headPath: "/mesh/controlPanel", },
component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
},
{
path: "/mesh/controlPanel/tab/:activeTabIndex",
name: "ControlPanelActiveTabIndex",
meta: {
notShow: true,
}, },
component: () => component: () =>
import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"), import(/* webpackChunkName: "layer" */ "@/views/Layer.vue"),
@ -53,10 +71,21 @@ export const routesMesh: Array<RouteRecordRaw> = [
path: "/mesh/dataPanel", path: "/mesh/dataPanel",
name: "DataPanel", name: "DataPanel",
meta: { meta: {
notShow: false,
title: "dataPanel", 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; autoRefresh: boolean;
pageTitle: string; pageTitle: string;
version: string; version: string;
isMobile: boolean;
reloadTimer: Nullable<any>;
} }
export const appStore = defineStore({ export const appStore = defineStore({
@ -51,6 +53,8 @@ export const appStore = defineStore({
autoRefresh: false, autoRefresh: false,
pageTitle: "", pageTitle: "",
version: "", version: "",
isMobile: false,
reloadTimer: null,
}), }),
getters: { getters: {
duration(): Duration { duration(): Duration {
@ -122,6 +126,9 @@ export const appStore = defineStore({
this.utcHour = utcHour; this.utcHour = utcHour;
this.utc = `${utcHour}:${utcMin}`; this.utc = `${utcHour}:${utcMin}`;
}, },
setIsMobile(mode: boolean) {
this.isMobile = mode;
},
setEventStack(funcs: (() => void)[]): void { setEventStack(funcs: (() => void)[]): void {
this.eventStack = funcs; this.eventStack = funcs;
}, },
@ -169,6 +176,9 @@ export const appStore = defineStore({
this.version = res.data.data.version; this.version = res.data.data.version;
return res.data; return res.data;
}, },
setReloadTimer(timer: any): void {
this.reloadTimer = timer;
},
}, },
}); });
export function useAppStoreWithOut(): any { export function useAppStoreWithOut(): any {

View File

@ -20,10 +20,8 @@ import { LayoutConfig } from "@/types/dashboard";
import graphql from "@/graphql"; import graphql from "@/graphql";
import query from "@/graphql/fetch"; import query from "@/graphql/fetch";
import { DashboardItem } from "@/types/dashboard"; import { DashboardItem } from "@/types/dashboard";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useSelectorStore } from "@/store/modules/selectors"; import { useSelectorStore } from "@/store/modules/selectors";
import { NewControl, TextConfig } from "../data"; import { NewControl, TextConfig } from "../data";
import { Duration } from "@/types/app";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
@ -35,7 +33,6 @@ interface DashboardState {
entity: string; entity: string;
layerId: string; layerId: string;
activedGridItem: string; activedGridItem: string;
durationTime: Duration;
selectorStore: any; selectorStore: any;
showTopology: boolean; showTopology: boolean;
fullView: boolean; fullView: boolean;
@ -56,7 +53,6 @@ export const dashboardStore = defineStore({
entity: "", entity: "",
layerId: "", layerId: "",
activedGridItem: "", activedGridItem: "",
durationTime: useAppStoreWithOut().durationTime,
selectorStore: useSelectorStore(), selectorStore: useSelectorStore(),
showTopology: false, showTopology: false,
showLogTools: false, showLogTools: false,
@ -123,12 +119,7 @@ export const dashboardStore = defineStore({
: 3, : 3,
}; };
} }
if ( if (["Trace", "Profile", "Log", "DemandLog", "Ebpf"].includes(type)) {
type === "Trace" ||
type === "Profile" ||
type === "Log" ||
type === "Ebpf"
) {
newItem.h = 36; newItem.h = 36;
} }
if (type === "Text") { if (type === "Text") {
@ -183,7 +174,7 @@ export const dashboardStore = defineStore({
showDepth: true, showDepth: true,
}; };
} }
if (type === "Trace" || type === "Profile" || type === "Log") { if (["Trace", "Profile", "Log", "DemandLog", "Ebpf"].includes(type)) {
newItem.h = 32; newItem.h = 32;
} }
if (type === "Text") { 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. * limitations under the License.
*/ */
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { Duration, Option } from "@/types/app"; import { Option } from "@/types/app";
import { import {
EBPFTaskCreationRequest, EBPFTaskCreationRequest,
EBPFProfilingSchedule, EBPFProfilingSchedule,
EBPFTaskList, EBPFTaskList,
AnalyzationTrees, AnalyzationTrees,
} from "@/types/ebpf"; } from "@/types/ebpf";
import { Trace, Span } from "@/types/trace";
import { store } from "@/store"; import { store } from "@/store";
import graphql from "@/graphql"; import graphql from "@/graphql";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
interface EbpfStore { interface EbpfStore {
durationTime: Duration;
taskList: EBPFTaskList[]; taskList: EBPFTaskList[];
eBPFSchedules: EBPFProfilingSchedule[]; eBPFSchedules: EBPFProfilingSchedule[];
currentSchedule: EBPFProfilingSchedule | Record<string, never>; currentSchedule: EBPFProfilingSchedule | Record<string, never>;
@ -37,12 +34,13 @@ interface EbpfStore {
labels: Option[]; labels: Option[];
couldProfiling: boolean; couldProfiling: boolean;
tip: string; tip: string;
selectedTask: Recordable<EBPFTaskList>;
aggregateType: string;
} }
export const ebpfStore = defineStore({ export const ebpfStore = defineStore({
id: "eBPF", id: "eBPF",
state: (): EbpfStore => ({ state: (): EbpfStore => ({
durationTime: useAppStoreWithOut().durationTime,
taskList: [], taskList: [],
eBPFSchedules: [], eBPFSchedules: [],
currentSchedule: {}, currentSchedule: {},
@ -50,14 +48,19 @@ export const ebpfStore = defineStore({
labels: [{ value: "", label: "" }], labels: [{ value: "", label: "" }],
couldProfiling: false, couldProfiling: false,
tip: "", tip: "",
selectedTask: {},
aggregateType: "COUNT",
}), }),
actions: { actions: {
setCurrentSpan(span: Span) { setSelectedTask(task: EBPFTaskList) {
this.currentSpan = span; this.selectedTask = task;
}, },
setCurrentSchedule(s: Trace) { setCurrentSchedule(s: EBPFProfilingSchedule) {
this.currentSchedule = s; this.currentSchedule = s;
}, },
setAnalyzeTrees(tree: AnalyzationTrees[]) {
this.analyzeTrees = tree;
},
async getCreateTaskData(serviceId: string) { async getCreateTaskData(serviceId: string) {
const res: AxiosResponse = await graphql const res: AxiosResponse = await graphql
.query("getCreateTaskData") .query("getCreateTaskData")
@ -85,6 +88,9 @@ export const ebpfStore = defineStore({
return res.data; return res.data;
}, },
async getTaskList(serviceId: string) { async getTaskList(serviceId: string) {
if (!serviceId) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql const res: AxiosResponse = await graphql
.query("getEBPFTasks") .query("getEBPFTasks")
.params({ serviceId }); .params({ serviceId });
@ -93,18 +99,20 @@ export const ebpfStore = defineStore({
if (res.data.errors) { if (res.data.errors) {
return res.data; return res.data;
} }
this.taskList = res.data.data.queryEBPFTasks.reverse() || []; this.taskList = res.data.data.queryEBPFTasks || [];
if (!this.taskList.length) { if (!this.taskList.length) {
return res.data; return res.data;
} }
this.getEBPFSchedules({ taskId: this.taskList[0].taskId }); this.getEBPFSchedules({ taskId: this.taskList[0].taskId });
return res.data; return res.data;
}, },
async getEBPFSchedules(params: { taskId: string; duration?: Duration }) { async getEBPFSchedules(params: { taskId: string }) {
const duration = useAppStoreWithOut().durationTime; if (!params.taskId) {
return new Promise((resolve) => resolve({}));
}
const res: AxiosResponse = await graphql const res: AxiosResponse = await graphql
.query("getEBPFSchedules") .query("getEBPFSchedules")
.params({ ...params, duration }); .params({ ...params });
if (res.data.errors) { if (res.data.errors) {
this.eBPFSchedules = []; this.eBPFSchedules = [];
@ -116,14 +124,22 @@ export const ebpfStore = defineStore({
this.eBPFSchedules = eBPFSchedules; this.eBPFSchedules = eBPFSchedules;
if (!eBPFSchedules.length) { if (!eBPFSchedules.length) {
this.eBPFSchedules = []; this.eBPFSchedules = [];
this.analyzeTrees = [];
} }
this.analyzeTrees = [];
return res.data; return res.data;
}, },
async getEBPFAnalyze(params: { async getEBPFAnalyze(params: {
scheduleIdList: string[]; scheduleIdList: string[];
timeRanges: Array<{ start: number; end: number }>; 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 const res: AxiosResponse = await graphql
.query("getEBPFResult") .query("getEBPFResult")
.params(params); .params(params);
@ -142,7 +158,7 @@ export const ebpfStore = defineStore({
this.analyzeTrees = []; this.analyzeTrees = [];
return res.data; return res.data;
} }
this.analyzeTrees = analysisEBPFResult.trees[0].elements; this.analyzeTrees = analysisEBPFResult.trees;
return res.data; return res.data;
}, },
}, },

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,10 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
.show-xs, .show-xs {
.show-sm,
.show-md,
.show-lg {
display: none !important; display: none !important;
} }
@media (max-width: 767px) { @media (max-width: 767px) {
@ -30,145 +27,6 @@
display: none !important; 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) { @media (min-width: 768px) {
.g-sm-1, .g-sm-1,
.g-sm-2, .g-sm-2,
@ -233,278 +91,4 @@
.g-sm-1 { .g-sm-1 {
width: 8.333%; 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 { .blue {
color: #6897BB; color: #6897bb;
} }
.purple { .purple {
color: #9876AA; color: #9876aa;
} }
.yellow { .yellow {
color: #FFC66D; color: #ffc66d;
} }
.grey { .grey {
@ -84,15 +84,15 @@
} }
.bg-blue { .bg-blue {
background-color: #6897BB; background-color: #6897bb;
} }
.bg-purple { .bg-purple {
background-color: #9876AA; background-color: #9876aa;
} }
.bg-yellow { .bg-yellow {
background-color: #FFC66D; background-color: #ffc66d;
} }
.bg-grey { .bg-grey {
@ -171,3 +171,21 @@
color: #ddd; 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, html,
body { body {
height: 100%; height: 100%;
overflow-y: hidden;
} }
div, div,
@ -153,3 +154,38 @@ pre {
.switch { .switch {
margin: 0 5px; 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; end: string;
step: string; step: string;
} }
export type Paging = {
pageNum: number;
pageSize: number;
};

View File

@ -39,9 +39,9 @@ export interface LayoutConfig {
} }
export type MetricConfigOpt = { export type MetricConfigOpt = {
unit: string; unit?: string;
label: string; label?: string;
calculation: string; calculation?: string;
labelsIndex: string; labelsIndex: string;
sortOrder: string; sortOrder: string;
topN?: number; 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; serviceName: string;
instanceId: string; instanceId: string;
instanceName: string; instanceName: string;
layer: string;
agentId: string; agentId: string;
detectType: string; detectType: string;
attributes: { name: string; value: string }; attributes: { name: string; value: string }[];
labels: string[]; labels: string[];
}; };
export type StackElement = { export type StackElement = {
@ -66,6 +65,8 @@ export type StackElement = {
stackType: string; stackType: string;
value: number; value: number;
children?: StackElement[]; children?: StackElement[];
rateOfRoot?: string;
rateOfParent: string;
}; };
export type AnalyzationTrees = { export type AnalyzationTrees = {
id: string; 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 = { export type Instance = {
value: string; value: string;
label: string; label: string;
layer?: string;
language?: string; language?: string;
instanceUUID?: string; instanceUUID?: string;
attributes?: { name: string; value: string }[]; attributes?: { name: string; value: string }[];

View File

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

View File

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

View File

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

View File

@ -39,8 +39,8 @@ limitations under the License. -->
<el-pagination <el-pagination
v-model:currentPage="pageNum" v-model:currentPage="pageNum"
v-model:page-size="pageSize" v-model:page-size="pageSize"
layout="prev, jumper, total, next" layout="prev, pager, next"
:total="alarmStore.total" :total="total"
@current-change="changePage" @current-change="changePage"
:pager-count="5" :pager-count="5"
small small
@ -55,7 +55,7 @@ limitations under the License. -->
</nav> </nav>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue"; import { ref, computed } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import ConditionTags from "@/views/components/ConditionTags.vue"; import ConditionTags from "@/views/components/ConditionTags.vue";
import { AlarmOptions } from "./data"; import { AlarmOptions } from "./data";
@ -70,6 +70,11 @@ const pageSize = 20;
const entity = ref<string>(""); const entity = ref<string>("");
const keyword = ref<string>(""); const keyword = ref<string>("");
const pageNum = ref<number>(1); const pageNum = ref<number>(1);
const total = computed(() =>
alarmStore.alarms.length === pageSize
? pageSize * pageNum.value + 1
: pageSize * pageNum.value
);
refreshAlarms({ pageNum: 1 }); refreshAlarms({ pageNum: 1 });
@ -79,7 +84,6 @@ async function refreshAlarms(param: { pageNum: number; tagsMap?: any }) {
paging: { paging: {
pageNum: param.pageNum, pageNum: param.pageNum,
pageSize, pageSize,
needTotal: true,
}, },
tags: param.tagsMap, 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 See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div class="flex-h" :class="{ light: theme === 'light' }"> <div>
<div class="flex-h items-center mr-5"> <span class="grey">{{ t("tags") }}: </span>
<span class="sm grey" v-show="theme === 'dark'">{{ t("tags") }}: </span> <span
<span v-if="tagsList.length" class="trace-tags"> v-if="tagsList.length"
<!-- :style="type === 'LOG' ? `min-width: 122px;` : ''" --> class="trace-tags"
<span class="selected" v-for="(item, index) in tagsList" :key="index"> :style="type === 'LOG' ? `min-width: 122px;` : ''"
<span>{{ item }}</span> >
<span class="remove-icon" @click="removeTags(index)">×</span> <span class="selected" v-for="(item, index) in tagsList" :key="index">
</span> <span>{{ item }}</span>
<span class="remove-icon" @click="removeTags(index)">×</span>
</span> </span>
</span>
<el-input
v-if="type === 'ALARM'"
size="small"
v-model="tags"
class="trace-new-tag"
@change="addLabels"
:placeholder="t('addTags')"
/>
<span v-else>
<el-input <el-input
size="small" size="small"
v-model="tags" v-model="tags"
class="trace-new-tag" class="trace-new-tag"
@change="addLabels" @click="showClick"
:placeholder="t('addTags')"
/> />
<span class="tags-tip"> <el-dropdown
<a ref="dropdownTag"
v-if="false" trigger="contextmenu"
target="blank" :hide-on-click="false"
href="https://github.com/apache/skywalking/blob/master/docs/en/setup/backend/configuration-vocabulary.md" style="margin: 20px 0 0 -130px"
> v-if="tagArr.length"
{{ t("tagsLink") }} >
</a> <template #dropdown>
<el-tooltip <el-dropdown-menu>
:content=" <el-dropdown-item v-for="(item, index) in tagArr" :key="index">
t( <span @click="selectTag(item)" class="tag-item">
type === 'LOG' {{ item }}
? 'logTagsTip' </span>
: type === 'TRACE' </el-dropdown-item>
? 'traceTagsTip' </el-dropdown-menu>
: 'alarmTagsTip' </template>
) </el-dropdown>
" </span>
> <span
<span> class="tags-tip"
<Icon class="icon-help mr-5" iconName="help" size="middle" /> :class="type !== 'ALARM' && tagArr.length ? 'link-tips' : ''"
</span> >
</el-tooltip> <a
<!-- <b v-if="type !== 'LOG'">{{ t("noticeTag") }}</b> --> target="blank"
</span> href="https://github.com/apache/skywalking/blob/master/docs/en/setup/backend/configuration-vocabulary.md"
</div> >
{{ t("tagsLink") }}
</a>
<el-tooltip :content="t(tipsMap[type])">
<span>
<Icon class="icon-help mr-5" iconName="help" size="middle" />
</span>
</el-tooltip>
<b v-if="type === 'AL'">{{ t("noticeTag") }}</b>
</span>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, defineExpose } from "vue"; import { ref, watch } from "vue";
import { useI18n } from "vue-i18n"; 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"]); const emit = defineEmits(["update"]);
const props = defineProps({
defineProps({
type: { type: String, default: "TRACE" }, type: { type: String, default: "TRACE" },
}); });
const traceStore = useTraceStore();
const logStore = useLogStore();
const appStore = useAppStoreWithOut();
const { t } = useI18n(); const { t } = useI18n();
const theme = ref<string>("dark");
const tags = ref<string>(""); const tags = ref<string>("");
const tagsList = 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({ fetchTagKeys();
tagsList,
emptyTags // defineExpose({
}) // tagsList,
function emptyTags (){ // emptyTags
tagsList.value = [] // })
} // function emptyTags (){
// tagsList.value = []
// }
function removeTags(index: number) { function removeTags(index: number) {
tagsList.value.splice(index, 1); tagsList.value.splice(index, 1);
updateTags(); updateTags();
@ -102,11 +136,67 @@ function updateTags() {
}); });
emit("update", { tagsMap, tagsList: tagsList.value }); 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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.items-center { .items-center {
align-items: center; align-items: center;
} }
.trace-tags { .trace-tags {
padding: 1px 5px 0 0; padding: 1px 5px 0 0;
border-radius: 3px; border-radius: 3px;
@ -131,7 +221,6 @@ function updateTags() {
padding: 2px 5px; padding: 2px 5px;
border-radius: 3px; border-radius: 3px;
width: 250px; width: 250px;
margin-right: 3px;
} }
.remove-icon { .remove-icon {
@ -140,10 +229,20 @@ function updateTags() {
cursor: pointer; cursor: pointer;
} }
.tag-item {
display: inline-block;
min-width: 210px;
}
.tags-tip { .tags-tip {
color: #a7aebb; color: #a7aebb;
} }
.link-tips {
display: inline-block;
margin-left: 130px;
}
.light { .light {
color: #3d444f; color: #3d444f;

View File

@ -146,9 +146,10 @@ import type { ElTable } from "element-plus";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import router from "@/router"; import router from "@/router";
import { DashboardItem } from "@/types/dashboard"; import { DashboardItem, LayoutConfig } from "@/types/dashboard";
import { saveFile, readFile } from "@/utils/file"; import { saveFile, readFile } from "@/utils/file";
import { EntityType } from "./data"; import { EntityType } from "./data";
import { isEmptyObject } from "@/utils/is";
/*global Nullable*/ /*global Nullable*/
const { t } = useI18n(); const { t } = useI18n();
@ -221,12 +222,77 @@ function exportTemplates() {
const layout = JSON.parse(sessionStorage.getItem(key) || "{}"); const layout = JSON.parse(sessionStorage.getItem(key) || "{}");
return layout; return layout;
}); });
for (const item of templates) {
optimizeTemplate(item.configuration.children);
}
const name = `dashboards.json`; const name = `dashboards.json`;
saveFile(templates, name); saveFile(templates, name);
setTimeout(() => { setTimeout(() => {
multipleTableRef.value!.clearSelection(); multipleTableRef.value!.clearSelection();
}, 2000); }, 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) { function handleEdit(row: DashboardItem) {
dashboardStore.setMode(true); dashboardStore.setMode(true);
dashboardStore.setEntity(row.entity); dashboardStore.setEntity(row.entity);

View File

@ -41,7 +41,6 @@ import { useDashboardStore } from "@/store/modules/dashboard";
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const { selectedGrid } = dashboardStore;
const widget = dashboardStore.selectedGrid.widget || {}; const widget = dashboardStore.selectedGrid.widget || {};
const title = ref<string>(widget.title || ""); const title = ref<string>(widget.title || "");
const tips = ref<string>(widget.tips || ""); const tips = ref<string>(widget.tips || "");
@ -51,6 +50,7 @@ function updateWidgetConfig(param: { [key: string]: string }) {
if (!key) { if (!key) {
return; return;
} }
const { selectedGrid } = dashboardStore;
const widget = { const widget = {
...dashboardStore.selectedGrid.widget, ...dashboardStore.selectedGrid.widget,
[key]: decodeURIComponent(param[key]), [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> <span>{{ t("delete") }}</span>
</div> </div>
</el-popover> </el-popover>
<Header /> <Header :needQuery="needQuery" />
<Content /> <Content />
</div> </div>
</template> </template>
@ -47,6 +47,7 @@ const props = defineProps({
default: () => ({ graph: {} }), default: () => ({ graph: {} }),
}, },
activeIndex: { type: String, default: "" }, activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
}); });
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); 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 See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div> <div class="log-wrapper flex-v">
<JbLog /> <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> </div>
</template> </template>
<script lang="ts" setup> <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> </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> <span>{{ t("delete") }}</span>
</div> </div>
</el-popover> </el-popover>
<Header /> <Header :needQuery="needQuery" />
<Content /> <Content />
</div> </div>
</template> </template>
@ -47,6 +47,7 @@ const props = defineProps({
default: () => ({ graph: {} }), default: () => ({ graph: {} }),
}, },
activeIndex: { type: String, default: "" }, activeIndex: { type: String, default: "" },
needQuery: { type: Boolean, default: true },
}); });
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); 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 See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div ref="tabRef" class="flex-h tab-header"> <div class="flex-h tab-header">
<div class="tabs"> <div class="tabs scroll_bar_style" @click="handleClick">
<span <span
v-for="(child, idx) in data.children || []" v-for="(child, idx) in data.children || []"
:key="idx" :key="idx"
@ -26,55 +26,48 @@ limitations under the License. -->
v-model="child.name" v-model="child.name"
placeholder="Please input" placeholder="Please input"
class="tab-name" class="tab-name"
:readonly="isNaN(editTabIndex)" :readonly="isNaN(editTabIndex) && !canEditTabName"
:class="{ view: isNaN(editTabIndex) }" :class="{ view: !canEditTabName }"
/> />
<Icon <Icon
v-show="activeTabIndex === idx" v-show="activeTabIndex === idx"
size="sm" size="sm"
iconName="cancel" iconName="cancel"
@click="deleteTabItem($event, idx)" @click="deleteTabItem($event, idx)"
v-if="dashboardStore.editMode" v-if="dashboardStore.editMode && canEditTabName"
/> />
</span> </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"> <span class="tab-icons" v-if="dashboardStore.editMode">
<el-tooltip content="Add tab items" placement="bottom"> <el-tooltip content="Add tab items" placement="bottom">
<i @click="addTabItem"> <i @click="addTabItem">
<Icon size="middle" iconName="add" /> <Icon size="middle" iconName="add_fill" class="tab-icon" />
</i> </i>
</el-tooltip> </el-tooltip>
</span> </span>
</div> </div>
<div class="operations" v-if="dashboardStore.editMode"> <div class="operations" v-if="dashboardStore.editMode">
<el-popover <el-dropdown placement="bottom" trigger="click" :width="200">
placement="bottom" <span class="icon-operation">
trigger="click" <Icon iconName="ellipsis_v" size="middle" />
:width="200" </span>
v-model:visible="showTools" <template #dropdown>
> <el-dropdown-menu>
<template #reference> <el-dropdown-item @click="canEditTabName = true">
<span> <span class="edit-tab">{{ t("editTab") }}</span>
<Icon </el-dropdown-item>
iconName="ellipsis_v" <el-dropdown-item @click="removeTab">
size="middle" <span>{{ t("delete") }}</span>
class="operation" </el-dropdown-item>
@click="showTools = true" </el-dropdown-menu>
/>
</span>
</template> </template>
<div </el-dropdown>
class="tools"
@click="
canEditTabName = true;
showTools = false;
"
>
<span class="edit-tab">{{ t("editTab") }}</span>
</div>
<div class="tools" @click="removeTab">
<span>{{ t("delete") }}</span>
</div>
</el-popover>
</div> </div>
</div> </div>
<div class="tab-layout" @click="handleClick"> <div class="tab-layout" @click="handleClick">
@ -87,68 +80,33 @@ limitations under the License. -->
:is-resizable="dashboardStore.editMode" :is-resizable="dashboardStore.editMode"
@layout-updated="layoutUpdatedEvent" @layout-updated="layoutUpdatedEvent"
> >
<div <grid-item
ref="tabObserveContainer" v-for="item in dashboardStore.currentTabItems"
class="scroll-tab-container" :x="item.x"
v-if="dashboardStore.fullView" :y="item.y"
:w="item.w"
:h="item.h"
:i="item.i"
:key="item.i"
@click="clickTabGrid($event, item)"
:class="{ active: activeTabWidget === item.i }"
:drag-ignore-from="dragIgnoreFrom"
> >
<div <component
v-if="dashboardStore.currentTabItems.length > 1" :is="item.type"
class="scroll-handler__wrapper" :data="item"
> :activeIndex="`${data.i}-${activeTabIndex}-${item.i}`"
<div :needQuery="needQuery"
@click="scrollToGraph(item.i, index)" />
v-for="(item, index) in dashboardStore.currentTabItems" </grid-item>
: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"
:y="item.y"
:w="item.w"
:h="item.h"
:i="item.i"
:key="item.i"
@click="clickTabGrid($event, item)"
:class="{ active: activeTabWidget === item.i }"
:drag-ignore-from="dragIgnoreFrom"
>
<component
:is="item.type"
:data="item"
:activeIndex="`${data.i}-${activeTabIndex}-${item.i}`"
:needQuery="needQuery"
/>
</grid-item>
</template>
</grid-layout> </grid-layout>
<div class="no-data-tips" v-else>{{ t("noWidget") }}</div> <div class="no-data-tips" v-else>{{ t("noWidget") }}</div>
</div> </div>
</template> </template>
<script lang="ts"> <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 { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
import type { PropType } from "vue"; import type { PropType } from "vue";
import { LayoutConfig } from "@/types/dashboard"; import { LayoutConfig } from "@/types/dashboard";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
@ -159,7 +117,10 @@ import Profile from "./Profile.vue";
import Log from "./Log.vue"; import Log from "./Log.vue";
import Text from "./Text.vue"; import Text from "./Text.vue";
import Ebpf from "./Ebpf.vue"; import Ebpf from "./Ebpf.vue";
import Event from "./Event.vue";
import { dragIgnoreFrom } from "../data"; import { dragIgnoreFrom } from "../data";
import DemandLog from "./DemandLog.vue";
import copy from "@/utils/copy";
const props = { const props = {
data: { data: {
@ -170,92 +131,40 @@ const props = {
}; };
export default defineComponent({ export default defineComponent({
name: "Tab", name: "Tab",
components: { Topology, Widget, Trace, Profile, Log, Text, Ebpf }, components: {
Topology,
Widget,
Trace,
Profile,
Log,
Text,
Ebpf,
DemandLog,
Event,
},
props, props,
setup(props) { setup(props) {
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); 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 activeTabWidget = ref<string>("");
const editTabIndex = ref<number>(NaN); // edit tab item name const editTabIndex = ref<number>(NaN); // edit tab item name
const canEditTabName = ref<boolean>(false); const canEditTabName = ref<boolean>(false);
const needQuery = 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) { if (dashboardStore.layout[l].children.length) {
dashboardStore.setCurrentTabItems( dashboardStore.setCurrentTabItems(
dashboardStore.layout[l].children[activeTabIndex.value].children dashboardStore.layout[l].children[activeTabIndex.value].children
); );
dashboardStore.setActiveTabIndex(activeTabIndex.value, props.data.i); 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) { function clickTabs(e: Event, idx: number) {
e.stopPropagation(); e.stopPropagation();
@ -270,6 +179,11 @@ export default defineComponent({
dashboardStore.layout[l].children[activeTabIndex.value].children dashboardStore.layout[l].children[activeTabIndex.value].children
); );
needQuery.value = true; 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) { function removeTab(e: Event) {
e.stopPropagation(); e.stopPropagation();
@ -296,6 +210,7 @@ export default defineComponent({
editTabIndex.value = index; editTabIndex.value = index;
} }
function handleClick(el: any) { function handleClick(el: any) {
needQuery.value = true;
if (["tab-name", "edit-tab"].includes(el.target.className)) { if (["tab-name", "edit-tab"].includes(el.target.className)) {
return; return;
} }
@ -305,7 +220,9 @@ export default defineComponent({
function clickTabGrid(e: Event, item: LayoutConfig) { function clickTabGrid(e: Event, item: LayoutConfig) {
e.stopPropagation(); e.stopPropagation();
activeTabWidget.value = item.i; activeTabWidget.value = item.i;
dashboardStore.activeGridItem(`${props.data.i}-${activeTabIndex.value}-${item.i}`); dashboardStore.activeGridItem(
`${props.data.i}-${activeTabIndex.value}-${item.i}`
);
handleClick(e); handleClick(e);
} }
function layoutUpdatedEvent() { function layoutUpdatedEvent() {
@ -316,18 +233,15 @@ export default defineComponent({
dashboardStore.layout[l].children[activeTabIndex.value].children dashboardStore.layout[l].children[activeTabIndex.value].children
); );
} }
function copyLink() {
function initScrollWatcher() { let path = "";
tabObserveContainer?.value?.addEventListener("wheel", (e: WheelEvent) => { if (route.params.activeTabIndex === undefined) {
if (isScrolling.value === false) { path = location.href + "/tab/" + activeTabIndex.value;
isScrolling.value = true; } else {
if (e.deltaY < 0) { const p = location.href.split("/tab/")[0];
scrollUp(); path = p + "/tab/" + activeTabIndex.value;
} else { }
scrollDown(); copy(path);
}
}
});
} }
document.body.addEventListener("click", handleClick, false); document.body.addEventListener("click", handleClick, false);
watch( watch(
@ -345,17 +259,7 @@ export default defineComponent({
} }
} }
); );
onMounted(() => {
initScrollWatcher();
tabRef?.value["parentElement"]?.classList?.toggle("item");
});
onBeforeUnmount(() => {
observeItems(true);
});
return { return {
currentItem,
tabObserveContainer,
scrollToGraph,
handleClick, handleClick,
layoutUpdatedEvent, layoutUpdatedEvent,
clickTabGrid, clickTabGrid,
@ -364,15 +268,14 @@ export default defineComponent({
deleteTabItem, deleteTabItem,
removeTab, removeTab,
clickTabs, clickTabs,
copyLink,
...toRefs(props), ...toRefs(props),
tabRef,
activeTabWidget, activeTabWidget,
dashboardStore, dashboardStore,
activeTabIndex, activeTabIndex,
editTabIndex, editTabIndex,
needQuery, needQuery,
canEditTabName, canEditTabName,
showTools,
t, t,
dragIgnoreFrom, dragIgnoreFrom,
}; };
@ -380,76 +283,33 @@ export default defineComponent({
}); });
</script> </script>
<style lang="scss" scoped> <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 { .tabs {
height: 40px; height: 40px;
color: #ccc; color: #ccc;
width: 100%;
overflow-x: auto;
white-space: nowrap;
overflow-y: hidden;
span { span {
display: inline-block; display: inline-block;
padding: 0 10px;
margin: 0 10px;
height: 40px; height: 40px;
line-height: 40px; line-height: 40px;
cursor: pointer; cursor: pointer;
text-align: center;
} }
.tab-name { .tab-name {
max-width: 130px; max-width: 110px;
height: 20px; height: 20px;
line-height: 20px; line-height: 20px;
outline: none; outline: none;
color: #333; color: #333;
font-style: normal; font-style: normal;
margin-right: 5px; margin-right: 5px;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
} }
.tab-icons { .tab-icons {
@ -470,12 +330,13 @@ export default defineComponent({
span.active { span.active {
border-bottom: 1px solid #409eff; border-bottom: 1px solid #409eff;
color: #409eff;
.tab-name {
color: #409eff;
}
} }
} }
.tab-header .tabs .span.active {
color: red !important;
}
.operations { .operations {
color: #aaa; color: #aaa;
cursor: pointer; cursor: pointer;
@ -484,6 +345,11 @@ export default defineComponent({
padding-right: 10px; padding-right: 10px;
} }
.icon-operation {
display: inline-block;
margin-top: 8px;
}
.tab-header { .tab-header {
justify-content: space-between; justify-content: space-between;
width: 100%; width: 100%;
@ -506,6 +372,10 @@ export default defineComponent({
overflow: auto; overflow: auto;
} }
.tab-icon {
color: #666;
}
.vue-grid-item.active { .vue-grid-item.active {
border: 1px solid #409eff; border: 1px solid #409eff;
} }
@ -517,17 +387,4 @@ export default defineComponent({
padding-top: 30px; padding-top: 30px;
color: #888; color: #888;
} }
.tools {
padding: 5px 0;
color: #999;
cursor: pointer;
position: relative;
text-align: center;
&:hover {
color: #409eff;
background-color: #eee;
}
}
</style> </style>

View File

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

View File

@ -22,5 +22,18 @@ import Profile from "./Profile.vue";
import Log from "./Log.vue"; import Log from "./Log.vue";
import Text from "./Text.vue"; import Text from "./Text.vue";
import Ebpf from "./Ebpf.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. * limitations under the License.
*/ */
export const dragIgnoreFrom = 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"]; export const PodsChartTypes = ["EndpointList", "InstanceList"];
@ -178,14 +178,16 @@ export const AllTools = [
{ name: "assignment", content: "Add Log", id: "addLog" }, { name: "assignment", content: "Add Log", id: "addLog" },
]; ];
export const ServiceTools = [ export const ServiceTools = [
{ name: "playlist_add", content: "Widget", id: "addWidget" }, { name: "playlist_add", content: "Add Widget", id: "addWidget" },
{ name: "all_inbox", content: "Tab", id: "addTab" }, { name: "all_inbox", content: "Add Tab", id: "addTab" },
{ name: "library_books", content: "Text", id: "addText" }, { name: "library_books", content: "Add Text", id: "addText" },
{ name: "device_hub", content: "Topology", id: "addTopology" }, { name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Trace", id: "addTrace" }, { name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "timeline", content: "Trace Profiling", id: "addProfile" }, { name: "timeline", content: "Add Trace Profiling", id: "addProfile" },
{ name: "insert_chart", content: "eBPF Profiling", id: "addEbpf" }, { name: "insert_chart", content: "Add eBPF Profiling", id: "addEbpf" },
{ name: "assignment", content: "Log", id: "addLog" }, { 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 = [ export const InstanceTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" }, { name: "playlist_add", content: "Add Widget", id: "addWidget" },
@ -193,6 +195,8 @@ export const InstanceTools = [
{ name: "library_books", content: "Add Text", id: "addText" }, { name: "library_books", content: "Add Text", id: "addText" },
{ name: "merge", content: "Add Trace", id: "addTrace" }, { name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "assignment", content: "Add Log", id: "addLog" }, { 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 = [ export const EndpointTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" }, { name: "playlist_add", content: "Add Widget", id: "addWidget" },
@ -201,6 +205,7 @@ export const EndpointTools = [
{ name: "device_hub", content: "Add Topology", id: "addTopology" }, { name: "device_hub", content: "Add Topology", id: "addTopology" },
{ name: "merge", content: "Add Trace", id: "addTrace" }, { name: "merge", content: "Add Trace", id: "addTrace" },
{ name: "assignment", content: "Add Log", id: "addLog" }, { name: "assignment", content: "Add Log", id: "addLog" },
{ name: "event", content: "Add Event", id: "addEvent" },
]; ];
export const ServiceRelationTools = [ export const ServiceRelationTools = [
{ name: "playlist_add", content: "Add Widget", id: "addWidget" }, { name: "playlist_add", content: "Add Widget", id: "addWidget" },

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -131,48 +131,95 @@ limitations under the License. -->
/> />
</div> </div>
</div> </div>
<div <div class="selectors-item" v-if="key === 3 || key === 4">
class="flex-h tools" <span class="label">
v-loading="loading" {{
v-if="$route.query['portal'] !== 'true'" ["EndpointRelation", "Endpoint"].includes(dashboardStore.entity)
> ? "$Endpoint"
<div class="tool-icons flex-h" v-if="dashboardStore.editMode"> : "$ServiceInstance"
<el-dropdown content="Controls" placement="bottom"> }}
<i> </span>
<Icon class="icon-btn" size="sm" iconName="control" /> <Selector
</i> v-model="states.currentPod"
<template #dropdown> :options="selectorStore.pods"
<el-dropdown-menu> size="small"
<el-dropdown-item placeholder="Select a data"
@click="clickIcons(t)" @change="changePods"
v-for="(t, index) in toolIcons" @query="searchPods"
:key="index" class="selectorPod"
:title="t.content" :isRemote="
> ['EndpointRelation', 'Endpoint'].includes(dashboardStore.entity)
<Icon class="mr-5" size="middle" :iconName="t.name" /> "
<span>{{ t.content }}</span> />
</el-dropdown-item> </div>
</el-dropdown-menu> <div class="selectors-item" v-if="key === 2 || key === 4">
</template> <span class="label">$DestinationService</span>
</el-dropdown> <Selector
<el-tooltip content="Apply" placement="bottom" effect="light"> v-model="states.currentDestService"
<i @click="applyDashboard"> :options="selectorStore.destServices"
<Icon class="icon-btn" size="sm" iconName="save" /> size="small"
</i> placeholder="Select a service"
</el-tooltip> @change="changeDestService"
</div> class="selectors"
<div class="switch"> />
<el-switch </div>
v-model="dashboardStore.editMode" <div class="selectors-item" v-if="key === 4">
active-text="Edit" <span class="label">
inactive-text="View" {{
size="small" dashboardStore.entity === "EndpointRelation"
inline-prompt ? "$DestinationEndpoint"
active-color="#409eff" : "$DestinationServiceInstance"
inactive-color="#999" }}
@change="changeMode" </span>
/> <Selector
</div> 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" :persistent="false">
<i>
<Icon class="icon-btn" size="sm" iconName="control" />
</i>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
@click="clickIcons(t)"
v-for="(t, index) in toolIcons"
:key="index"
:title="t.content"
>
<Icon class="mr-5" size="middle" :iconName="t.name" />
<span>{{ t.content }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-tooltip content="Apply" placement="bottom" effect="light">
<i @click="applyDashboard">
<Icon class="icon-btn" size="sm" iconName="save" />
</i>
</el-tooltip>
</div>
<div class="switch">
<el-switch
v-model="dashboardStore.editMode"
active-text="Edit"
inactive-text="View"
size="small"
inline-prompt
active-color="#409eff"
inactive-color="#999"
@change="changeMode"
/>
</div> </div>
</div> </div>
<Header v-if="showLogHeader" /> <Header v-if="showLogHeader" />
@ -423,7 +470,12 @@ async function getServices() {
states.currentService = selectorStore.currentService.value; states.currentService = selectorStore.currentService.value;
const e = dashboardStore.entity.split("Relation")[0]; const e = dashboardStore.entity.split("Relation")[0];
if ( 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); fetchPods(e, selectorStore.currentService.id, true);
} }
@ -443,7 +495,10 @@ async function changeService(service: any) {
if (service[0]) { if (service[0]) {
states.currentService = service[0].value; states.currentService = service[0].value;
selectorStore.setCurrentService(service[0]); 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 { } else {
selectorStore.setCurrentService(null); selectorStore.setCurrentService(null);
} }
@ -453,6 +508,9 @@ function changeDestService(service: any) {
if (service[0]) { if (service[0]) {
states.currentDestService = service[0].value; states.currentDestService = service[0].value;
selectorStore.setCurrentDestService(service[0]); selectorStore.setCurrentDestService(service[0]);
selectorStore.setCurrentDestPod(null);
states.currentDestPod = "";
fetchPods(dashboardStore.entity, selectorStore.currentDestService.id, true);
} else { } else {
selectorStore.setCurrentDestService(null); selectorStore.setCurrentDestService(null);
} }
@ -527,6 +585,12 @@ function setTabControls(id: string) {
case "addText": case "addText":
dashboardStore.addTabControls("Text"); dashboardStore.addTabControls("Text");
break; break;
case "addDemandLog":
dashboardStore.addTabControls("DemandLog");
break;
case "addEvent":
dashboardStore.addTabControls("Event");
break;
default: default:
ElMessage.info("Don't support this control"); ElMessage.info("Don't support this control");
break; break;
@ -559,6 +623,12 @@ function setControls(id: string) {
case "addText": case "addText":
dashboardStore.addControl("Text"); dashboardStore.addControl("Text");
break; break;
case "addDemandLog":
dashboardStore.addControl("DemandLog");
break;
case "addEvent":
dashboardStore.addControl("Event");
break;
default: default:
dashboardStore.addControl("Widget"); dashboardStore.addControl("Widget");
} }
@ -615,11 +685,11 @@ async function fetchPods(
if (setPod) { if (setPod) {
let p; let p;
if (states.currentDestPod) { if (states.currentDestPod) {
p = selectorStore.pods.find( p = selectorStore.destPods.find(
(d: { label: string }) => d.label === states.currentDestPod (d: { label: string }) => d.label === states.currentDestPod
); );
} else { } else {
p = selectorStore.pods.find( p = selectorStore.destPods.find(
(d: { label: string }, index: number) => index === 0 (d: { label: string }, index: number) => index === 0
); );
} }
@ -635,11 +705,11 @@ async function fetchPods(
if (setPod) { if (setPod) {
let p; let p;
if (states.currentDestPod) { if (states.currentDestPod) {
p = selectorStore.pods.find( p = selectorStore.destPods.find(
(d: { label: string }) => d.label === states.currentDestPod (d: { label: string }) => d.label === states.currentDestPod
); );
} else { } else {
p = selectorStore.pods.find( p = selectorStore.destPods.find(
(d: { label: string }, index: number) => index === 0 (d: { label: string }, index: number) => index === 0
); );
} }

View File

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

View File

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

View File

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

View File

@ -30,12 +30,12 @@ limitations under the License. -->
<span v-else-if="item.label === 'tags'"> <span v-else-if="item.label === 'tags'">
{{ tags }} {{ tags }}
</span> </span>
<router-link <!-- <router-link
v-else-if="item.label === 'traceId' && !noLink" v-else-if="item.label === 'traceId' && !noLink"
:to="{ name: 'trace', query: { traceid: data[item.label] } }" :to="{ name: 'trace', query: { traceid: data[item.label] } }"
> >
<span :class="noLink ? '' : 'blue'">{{ data[item.label] }}</span> <span :class="noLink ? '' : 'blue'">{{ data[item.label] }}</span>
</router-link> </router-link> -->
<span v-else>{{ data[item.label] }}</span> <span v-else>{{ data[item.label] }}</span>
</div> </div>
</div> </div>
@ -131,9 +131,10 @@ function showSelectSpan() {
padding: 3px 8px; padding: 3px 8px;
} }
.d-flex{ .d-flex {
display: flex; display: flex;
div{
div {
flex-grow: 1; 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"> <div class="flex-h content">
<TaskList /> <TaskList />
<div class="vis-graph ml-5"> <div class="vis-graph ml-5">
<div class="item"> <div class="schedules">
<EBPFSchedules /> <EBPFSchedules />
</div> </div>
<div class="item"> <div class="item">
@ -38,13 +38,21 @@ import EBPFStack from "./components/EBPFStack.vue";
.vis-graph { .vis-graph {
height: 100%; height: 100%;
width: calc(100% - 300px); flex-grow: 2;
min-width: 700px;
overflow: auto;
} }
.item { .item {
width: 100%; width: 100%;
overflow: auto; overflow: auto;
height: calc(50% - 10px); height: calc(100% - 100px);
padding-bottom: 10px; padding-bottom: 10px;
} }
.schedules {
height: 90px;
border-bottom: 1px solid #ccc;
padding-right: 10px;
}
</style> </style>

View File

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

View File

@ -14,104 +14,128 @@ See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div class="filters"> <div class="filters">
<Selector <div class="mb-10 flex-h">
:value="selectedLabels" <Selector
:options="labels" :value="selectedLabels"
size="small" :options="labels"
placeholder="Please select labels"
@change="changeLabels"
class="inputs mr-10"
:multiple="true"
/>
<el-popover placement="bottom" :width="680" trigger="click">
<template #reference>
<el-button type="primary" size="small">
{{ t("processSelect") }}
</el-button>
</template>
<el-input
v-model="searchText"
placeholder="Please input name"
class="input-with-search"
size="small" size="small"
@change="searchProcesses" placeholder="Please select labels"
@change="changeLabels"
class="inputs mr-10"
:multiple="true"
/>
<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 #append> <template #reference>
<el-button size="small"> <el-button type="primary" size="small">
<Icon size="sm" iconName="search" /> {{ t("processSelect") }}
</el-button> </el-button>
</template> </template>
</el-input> <el-input
<el-table v-model="searchText"
:data="currentProcesses" placeholder="Please input name"
ref="multipleTableRef" class="input-with-search"
@selection-change="handleSelectionChange" size="small"
> @change="searchProcesses(0)"
<el-table-column type="selection" width="55" /> >
<el-table-column <template #append>
v-for="(h, index) of TableHeader" <el-button size="small">
:property="h.property" <Icon size="sm" iconName="search" />
:label="h.label" </el-button>
:key="index"
width="150"
/>
<el-table-column width="300" label="Attributes">
<template #default="scope">
{{ scope.row.attributes.map((d: {name: string, value: string}) => `${d.name}=${d.value}`).join("; ") }}
</template> </template>
</el-table-column> </el-input>
</el-table> <el-table
<el-pagination :data="currentProcesses"
class="pagination" ref="multipleTableRef"
background @selection-change="handleSelectionChange"
small >
layout="prev, pager, next" <el-table-column type="selection" width="55" />
:page-size="pageSize" <el-table-column
:total="processes.length" v-for="(h, index) of TableHeader"
@current-change="changePage" :property="h.property"
@prev-click="changePage" :label="h.label"
@next-click="changePage" :key="index"
/> width="150"
</el-popover> />
<el-button type="primary" size="small" @click="analyzeEBPF"> <el-table-column width="300" label="Attributes">
{{ t("analyze") }} <template #default="scope">
</el-button> {{ scope.row.attributes.map((d: {name: string, value: string}) => `${d.name}=${d.value}`).join("; ") }}
</template>
</el-table-column>
</el-table>
<el-pagination
class="pagination"
background
small
layout="prev, pager, next"
:page-size="pageSize"
:total="processes.length"
@current-change="changePage"
@prev-click="changePage"
@next-click="changePage"
/>
</el-popover>
<el-button type="primary" size="small" @click="analyzeEBPF">
{{ t("analyze") }}
</el-button>
</div>
</div> </div>
<div ref="timeline" class="schedules"></div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch } from "vue"; import { ref, watch } from "vue";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { Option } from "@/types/app"; import { Option } from "@/types/app";
import { TableHeader } from "./data"; import { TableHeader, AggregateTypes } from "./data";
import { useEbpfStore } from "@/store/modules/ebpf"; import { useEbpfStore } from "@/store/modules/ebpf";
import { EBPFProfilingSchedule, Process } from "@/types/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"; import { ElMessage, ElTable } from "element-plus";
const { t } = useI18n(); const { t } = useI18n();
const ebpfStore = useEbpfStore(); const ebpfStore = useEbpfStore();
const pageSize = 5; const pageSize = 5;
/*global Nullable */
const multipleTableRef = ref<InstanceType<typeof ElTable>>(); const multipleTableRef = ref<InstanceType<typeof ElTable>>();
const selectedProcesses = ref<string[]>([]); 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 labels = ref<Option[]>([{ label: "All", value: "0" }]);
const processes = ref<Process[]>([]); const processes = ref<Process[]>([]);
const currentProcesses = ref<Process[]>([]); const currentProcesses = ref<Process[]>([]);
const selectedLabels = ref<string[]>(["0"]); const selectedLabels = ref<string[]>(["0"]);
const searchText = ref<string>(""); 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") => 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[]) { function changeLabels(opt: any[]) {
const arr = opt.map((d) => d.value); const arr = opt.map((d) => d.value);
selectedLabels.value = arr; selectedLabels.value = arr;
} }
function changeAggregateType(opt: any[]) {
aggregateType.value = opt[0].value;
ebpfStore.setAnalyzeTrees([]);
}
const handleSelectionChange = (arr: Process[]) => { const handleSelectionChange = (arr: Process[]) => {
selectedProcesses.value = arr.map((d: Process) => d.id); selectedProcesses.value = arr.map((d: Process) => d.id);
}; };
@ -152,85 +176,89 @@ async function analyzeEBPF() {
const res = await ebpfStore.getEBPFAnalyze({ const res = await ebpfStore.getEBPFAnalyze({
scheduleIdList, scheduleIdList,
timeRanges, timeRanges,
aggregateType: aggregateType.value,
}); });
if (res.data.errors) { if (res.data && res.data.errors) {
ElMessage.error(res.data.errors); ElMessage.error(res.data.errors);
return; return;
} }
} }
function visTimeline() { function getSchedules() {
if (visGraph.value) {
visGraph.value.destroy();
}
labels.value = [{ label: "All", value: "0" }]; labels.value = [{ label: "All", value: "0" }];
selectedLabels.value = ["0"]; selectedLabels.value = ["0"];
processes.value = []; processes.value = [];
const schedules = ebpfStore.eBPFSchedules.map( const ranges = ebpfStore.eBPFSchedules.map((d: EBPFProfilingSchedule) => {
(d: EBPFProfilingSchedule, index: number) => { for (const l of d.process.labels) {
for (const l of d.process.labels) { labels.value.push({ label: l, value: l });
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),
};
} }
); processes.value.push(d.process);
searchProcesses(); return [d.startTime / 10000, d.endTime / 10000];
if (!timeline.value) { });
return; 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 = [];
} }
const h = timeline.value.getBoundingClientRect().height; searchProcesses(0);
const items: any = new DataSet(schedules); analyzeEBPF();
const options = {
height: h,
width: "100%",
locale: "en",
};
visGraph.value = new Timeline(timeline.value, items, options);
} }
function changePage(pageIndex: number) { function changePage(pageIndex: number) {
searchProcesses(pageIndex); searchProcesses(pageIndex);
} }
function searchProcesses(pageIndex?: any) { function searchProcesses(pageIndex: number) {
const arr = processes.value.filter( 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.name.includes(searchText.value) ||
d.instanceName.includes(searchText.value) d.instanceName.includes(searchText.value) ||
searchAttribute(d.attributes, searchText.value)
); );
currentProcesses.value = arr.splice( currentProcesses.value = arr.filter(
(pageIndex - 1 || 0) * pageSize, (d, index: number) =>
pageSize * (pageIndex || 1) (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( watch(
() => ebpfStore.eBPFSchedules, () => ebpfStore.eBPFSchedules,
() => { () => {
visTimeline(); getSchedules();
} }
); );
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.filters { .filters {
margin: 5px 0; margin: 5px 0;
} width: 100%;
min-width: 560px;
.schedules {
width: calc(100% - 5px);
margin: 0 5px 5px 0;
height: calc(100% - 60px);
min-height: 150px;
} }
.inputs { .inputs {
width: 300px; width: 400px;
} }
.input-with-search { .input-with-search {
@ -241,4 +269,12 @@ watch(
.pagination { .pagination {
margin-top: 10px; margin-top: 10px;
} }
.selector {
width: 120px;
}
.duration {
line-height: 30px;
}
</style> </style>

View File

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

View File

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

View File

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

View File

@ -28,7 +28,15 @@ export const NewTaskField = {
maxSamplingCount: { key: 5, label: "5" }, 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 = { export const InitTaskField = {
monitorTimeEn: [ 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,259 +13,124 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div class="flex-h log-wrapper"> <div class="flex-h row">
<div v-if="currentSearchTerm === 'column'" class="flex-h items-center mr-5"> <div class="mr-5" v-if="dashboardStore.entity === EntityType[1].value">
<el-dropdown class="dark" :hide-on-click="false"> <span class="grey mr-5">{{ t("service") }}:</span>
<span class="cursor-pointer"> <Selector
Select visible columns<el-icon class="el-icon--right" size="small"
><arrow-down :value="state.service.value"
/></el-icon> :options="logStore.services"
</span> placeholder="Select a service"
<template #dropdown> @change="changeField('service', $event)"
<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>
<div v-if="!currentSearchTerm.length" class="flex-h items-center"> <div class="mr-5" v-if="dashboardStore.entity !== EntityType[3].value">
<div v-for="(item, index) in arrayOfFilters" :key="index"> <span class="grey mr-5">
<el-tooltip {{ isBrowser ? t("version") : t("instance") }}:
class="box-item" </span>
effect="dark" <Selector
:content="item.description" size="small"
placement="bottom-start" :value="state.instance.value"
:options="logStore.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">
{{ isBrowser ? t("page") : t("endpoint") }}:
</span>
<Selector
size="small"
:value="state.endpoint.value"
:options="logStore.endpoints"
placeholder="Select a endpoint"
@change="changeField('endpoint', $event)"
:isRemote="true"
@query="searchEndpoints"
/>
</div>
<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="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>
<div class="row tips" v-show="!isBrowser">
<b>{{ t("conditionNotice") }}</b>
</div>
<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
class="selected"
v-for="(item, index) in keywordsOfContent"
:key="`keywordsOfContent${index}`"
> >
<el-button <span>{{ item }}</span>
type="success" <span class="remove-icon" @click="removeContent(index)">×</span>
:class="[activeTerms.includes(item.name) ? 'active-toggle' : '']" </span>
class="toggle-btn mx-3" </span>
v-show="item.isVisible" <el-input
@click="setSearchTerm(item.name)" size="small"
> class="inputs-max"
<Icon iconSize="sm" :iconName="item.iconName" /> :placeholder="t('addKeywordsOfContent')"
</el-button> v-model="contentStr"
</el-tooltip> @change="addLabels('keywordsOfContent')"
</div> />
<el-tooltip </div>
class="box-item" <div class="mr-5" v-show="logStore.supportQueryLogsByKeywords">
effect="dark" <span class="grey mr-5"> {{ t("excludingKeywordsOfContent") }}: </span>
content="Toggle columns" <span class="log-tags">
placement="bottom-start" <span
> class="selected"
<el-button v-for="(item, index) in excludingKeywordsOfContent"
type="success" :key="`excludingKeywordsOfContent${index}`"
:class="[false ? 'active-toggle' : '']"
class="toggle-btn mx-3"
@click="toggleColumSelector"
> >
<Icon iconSize="sm" iconName="epic" /> <span>{{ item }}</span>
</el-button> <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')"
/>
<el-tooltip :content="t('keywordsOfContentLogTips')">
<span class="log-tips" v-show="!logStore.supportQueryLogsByKeywords">
<Icon icon="help" class="mr-5" />
</span>
</el-tooltip> </el-tooltip>
</div> </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"
>
<span class="grey mr-5">{{ t("service") }}:</span>
<Selector
size="small"
:value="state.service.value"
:options="logStore.services"
placeholder="Select a service"
@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'
"
>
<span class="grey mr-5">
{{ isBrowser ? t("version") : t("instance") }}:
</span>
<Selector
size="small"
:value="state.instance.value"
:options="logStore.instances"
placeholder="Select a instance"
@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
>
<Selector
size="small"
:value="state.endpoint.value"
:options="logStore.endpoints"
placeholder="Select a endpoint"
@change="changeField('endpoint', $event)"
:isRemote="true"
@query="searchEndpoints"
/>
</div>
<b v-else>{{ t("endpoint") }} data not available</b>
</div>
</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'">
<span class="grey mr-5">{{ t("traceID") }}:</span>
<el-input v-model="traceId" class="inputs-max" size="small" />
</div>
</div>
<keep-alive>
<ConditionTags
ref="logTagsComponent"
v-if="currentSearchTerm === 'tags'"
:type="'LOG'"
@update="updateTags"
/>
</keep-alive>
</div>
<div class="flex-h items-center" v-show="!isBrowser">
<div
class="mr-5 flex-h items-center"
v-show="supportQueryLogsByKeywords && currentSearchTerm === 'keywords'"
>
<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 flex-h items-center"
v-show="
supportExcludeQueryLogsByKeywords && currentSearchTerm === 'exclude'
"
>
<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')"
/>
<el-tooltip :content="t('keywordsOfContentLogTips')">
<span class="log-tips" v-show="!logStore.supportQueryLogsByKeywords">
<Icon icon="help" class="mr-5" />
</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> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ArrowDown, View, Hide } from "@element-plus/icons-vue"; import { ref, reactive, watch, onUnmounted } from "vue";
import { ref, reactive, watch, computed, onMounted } from "vue";
import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { Option } from "@/types/app"; import { Option } from "@/types/app";
import { useLogStore } from "@/store/modules/log"; import { useLogStore } from "@/store/modules/log";
@ -275,26 +140,20 @@ import { useSelectorStore } from "@/store/modules/selectors";
import ConditionTags from "@/views/components/ConditionTags.vue"; import ConditionTags from "@/views/components/ConditionTags.vue";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { EntityType } from "../../data"; import { EntityType } from "../../data";
import { ErrorCategory } from "./data";
/*global defineProps */
const props = defineProps({
needQuery: { type: Boolean, default: true },
});
const { t } = useI18n(); const { t } = useI18n();
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const selectorStore = useSelectorStore(); const selectorStore = useSelectorStore();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const { portal } = useRoute().query;
const logStore = useLogStore(); const logStore = useLogStore();
const showColumList = ref<boolean>(false);
const traceId = ref<string>(""); const traceId = ref<string>("");
const keywordsOfContent = ref<string[]>([]); const keywordsOfContent = ref<string[]>([]);
const excludingKeywordsOfContent = 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 tagsList = ref<string[]>([]);
const tagsMap = ref<Option[]>([]); const tagsMap = ref<Option[]>([]);
const contentStr = ref<string>(""); const contentStr = ref<string>("");
@ -304,75 +163,10 @@ const state = reactive<any>({
instance: { value: "0", label: "All" }, instance: { value: "0", label: "All" },
endpoint: { value: "0", label: "All" }, endpoint: { value: "0", label: "All" },
service: { value: "", label: "" }, service: { value: "", label: "" },
category: { value: "ALL", label: "All" },
}); });
const logTagsComponent = ref<InstanceType<typeof ConditionTags> | null>(null); if (props.needQuery) {
interface filtersObject { init();
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";
} }
async function init() { async function init() {
const resp = await logStore.getLogsByKeywords(); const resp = await logStore.getLogsByKeywords();
@ -433,46 +227,7 @@ async function getInstances(id?: string) {
} }
state.instance = logStore.instances[0]; 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() { function searchLogs() {
handleActiveSearchTerms();
currentSearchTerm.value = "";
let endpoint = "", let endpoint = "",
instance = ""; instance = "";
if (dashboardStore.entity === EntityType[2].value) { if (dashboardStore.entity === EntityType[2].value) {
@ -481,19 +236,32 @@ function searchLogs() {
if (dashboardStore.entity === EntityType[3].value) { if (dashboardStore.entity === EntityType[3].value) {
instance = selectorStore.currentPod.id; instance = selectorStore.currentPod.id;
} }
logStore.setLogCondition({ if (dashboardStore.layerId === "BROWSER") {
serviceId: selectorStore.currentService logStore.setLogCondition({
? selectorStore.currentService.id serviceId: selectorStore.currentService
: state.service.id, ? selectorStore.currentService.id
endpointId: endpoint || state.endpoint.id || undefined, : state.service.id,
serviceInstanceId: instance || state.instance.id || undefined, pagePathId: endpoint || state.endpoint.id || undefined,
queryDuration: appStore.durationTime, serviceVersionId: instance || state.instance.id || undefined,
keywordsOfContent: keywordsOfContent.value, paging: { pageNum: 1, pageSize: 15 },
excludingKeywordsOfContent: excludingKeywordsOfContent.value, queryDuration: appStore.durationTime,
tags: tagsMap.value.length ? tagsMap.value : undefined, category: state.category.value,
paging: { pageNum: 1, pageSize: 15, needTotal: true }, });
relatedTrace: traceId.value ? { traceId: traceId.value } : undefined, } else {
}); logStore.setLogCondition({
serviceId: selectorStore.currentService
? selectorStore.currentService.id
: state.service.id,
endpointId: endpoint || state.endpoint.id || undefined,
serviceInstanceId: instance || state.instance.id || undefined,
queryDuration: appStore.durationTime,
keywordsOfContent: keywordsOfContent.value,
excludingKeywordsOfContent: excludingKeywordsOfContent.value,
tags: tagsMap.value.length ? tagsMap.value : undefined,
paging: { pageNum: 1, pageSize: 15 },
relatedTrace: traceId.value ? { traceId: traceId.value } : undefined,
});
}
queryLogs(); queryLogs();
} }
async function queryLogs() { async function queryLogs() {
@ -555,45 +323,9 @@ function removeExcludeContent(index: number) {
}); });
excludingContentStr.value = ""; excludingContentStr.value = "";
} }
function setSearchTerm(term: string) { onUnmounted(() => {
currentSearchTerm.value = term; logStore.resetCondition();
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();
}
watch( watch(
() => selectorStore.currentService, () => selectorStore.currentService,
() => { () => {
@ -621,44 +353,13 @@ watch(
); );
</script> </script>
<style lang="scss" scoped> <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 { .inputs {
width: 120px; width: 120px;
} }
.items-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.row { .row {
margin-bottom: 5px; margin-bottom: 5px;
position: relative;
} }
.inputs-max { .inputs-max {
@ -670,8 +371,11 @@ watch(
} }
.search-btn { .search-btn {
margin-left: 20px; position: absolute;
top: 0;
right: 10px;
cursor: pointer; cursor: pointer;
width: 120px;
} }
.tips { .tips {
@ -711,40 +415,4 @@ watch(
margin-left: 3px; margin-left: 3px;
cursor: pointer; 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> </style>

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