mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-05-01 07:42:52 +00:00
feat: associate metrics with trace widget on dashboards (#174)
This commit is contained in:
parent
78f0096c00
commit
eda44db0cd
16
src/assets/icons/conditions.svg
Normal file
16
src/assets/icons/conditions.svg
Normal file
@ -0,0 +1,16 @@
|
||||
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License. -->
|
||||
|
||||
<svg t="1666624449554" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2649" width="48" height="48"><path d="M381.482667 673.877333a90.389333 90.389333 0 0 1 85.226666 60.245334H853.333333v64H465.28a90.389333 90.389333 0 0 1-167.573333 0H170.666667v-64h125.610666a90.389333 90.389333 0 0 1 85.205334-60.245334z m0 64a26.346667 26.346667 0 1 0 0 52.693334 26.346667 26.346667 0 0 0 0-52.693334z m261.034666-304.938666a90.389333 90.389333 0 0 1 85.205334 60.245333H853.333333v64h-127.04a90.389333 90.389333 0 0 1-167.573333 0H170.666667v-64h386.624a90.389333 90.389333 0 0 1 85.226666-60.245333z m0 64a26.346667 26.346667 0 1 0 0 52.693333 26.346667 26.346667 0 0 0 0-52.693333zM381.482667 192a90.389333 90.389333 0 0 1 85.226666 60.224H853.333333v64H465.28a90.389333 90.389333 0 0 1-167.573333 0H170.666667v-64h125.610666A90.389333 90.389333 0 0 1 381.482667 192z m0 64a26.346667 26.346667 0 1 0 0 52.693333 26.346667 26.346667 0 0 0 0-52.693333z" p-id="2650"></path></svg>
|
After Width: | Height: | Size: 1.7 KiB |
15
src/assets/icons/copy.svg
Normal file
15
src/assets/icons/copy.svg
Normal 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="1664265269855" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4109" width="48" height="48"><path d="M866.461538 39.384615H354.461538c-43.323077 0-78.769231 35.446154-78.76923 78.769231v39.384616h472.615384c43.323077 0 78.769231 35.446154 78.769231 78.76923v551.384616h39.384615c43.323077 0 78.769231-35.446154 78.769231-78.769231V118.153846c0-43.323077-35.446154-78.769231-78.769231-78.769231z m-118.153846 275.692308c0-43.323077-35.446154-78.769231-78.76923-78.769231H157.538462c-43.323077 0-78.769231 35.446154-78.769231 78.769231v590.769231c0 43.323077 35.446154 78.769231 78.769231 78.769231h512c43.323077 0 78.769231-35.446154 78.76923-78.769231V315.076923z m-354.461538 137.846154c0 11.815385-7.876923 19.692308-19.692308 19.692308h-157.538461c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h157.538461c11.815385 0 19.692308 7.876923 19.692308 19.692308v39.384615z m157.538461 315.076923c0 11.815385-7.876923 19.692308-19.692307 19.692308H216.615385c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h315.076923c11.815385 0 19.692308 7.876923 19.692307 19.692308v39.384615z m78.769231-157.538462c0 11.815385-7.876923 19.692308-19.692308 19.692308H216.615385c-11.815385 0-19.692308-7.876923-19.692308-19.692308v-39.384615c0-11.815385 7.876923-19.692308 19.692308-19.692308h393.846153c11.815385 0 19.692308 7.876923 19.692308 19.692308v39.384615z" p-id="4110"></path></svg>
|
After Width: | Height: | Size: 2.3 KiB |
@ -13,6 +13,5 @@ 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 version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<title>info_outline</title>
|
||||
<path d="M11.016 9v-2.016h1.969v2.016h-1.969zM12 20.016q3.281 0 5.648-2.367t2.367-5.648-2.367-5.648-5.648-2.367-5.648 2.367-2.367 5.648 2.367 5.648 5.648 2.367zM12 2.016q4.125 0 7.055 2.93t2.93 7.055-2.93 7.055-7.055 2.93-7.055-2.93-2.93-7.055 2.93-7.055 7.055-2.93zM11.016 17.016v-6h1.969v6h-1.969z"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
15
src/assets/icons/operation.svg
Normal file
15
src/assets/icons/operation.svg
Normal 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="1664266918236" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5378" width="48" height="48"><path d="M571.178667 643.328a144 144 0 0 1-189.098667-193.450667l77.781333 77.866667a48 48 0 1 0 67.882667-67.84l-77.824-77.909333a144 144 0 0 1 193.450667 189.141333l226.517333 207.061333a64.896 64.896 0 1 1-91.690667 91.690667l-207.018666-226.56z m51.498666 134.656a288.298667 288.298667 0 0 1-38.656 12.928v95.488c0 5.290667-4.309333 9.6-9.642666 9.6h-124.757334a9.6 9.6 0 0 1-9.6-9.6v-95.488a286.293333 286.293333 0 0 1-74.325333-30.805333l-67.541333 67.541333a9.6 9.6 0 0 1-13.568 0L196.352 739.413333a9.6 9.6 0 0 1 0-13.568l67.541333-67.541333a286.293333 286.293333 0 0 1-30.805333-74.325333H137.6A9.6 9.6 0 0 1 128 574.378667v-124.757334c0-5.290667 4.309333-9.6 9.6-9.6h95.488c6.826667-26.453333 17.28-51.370667 30.805333-74.325333L196.352 298.154667a9.6 9.6 0 0 1 0-13.568L284.586667 196.352a9.6 9.6 0 0 1 13.568 0l67.541333 67.498667a287.146667 287.146667 0 0 1 74.325333-30.848V137.6c0-5.290667 4.266667-9.6 9.6-9.6h124.8c5.248 0 9.6 4.309333 9.6 9.6v95.488c26.368 6.826667 51.328 17.28 74.282667 30.805333l67.541333-67.541333a9.6 9.6 0 0 1 13.568 0l88.234667 88.234667a9.6 9.6 0 0 1 0 13.568l-67.498667 67.541333a287.146667 287.146667 0 0 1 30.848 74.282667h95.402667c5.290667 0 9.6 4.352 9.6 9.642666v124.757334c0 5.333333-4.266667 9.6-9.6 9.6h-95.488c-4.693333 18.133333-11.178667 35.754667-19.328 52.650666a9.6 9.6 0 0 1-15.018667 2.986667l-10.112-9.173333-38.314666-34.261334-12.16-10.88a9.6 9.6 0 0 1-2.688-10.24A192.298667 192.298667 0 0 0 512 320a192 192 0 1 0 63.018667 373.333333 9.6 9.6 0 0 1 10.24 2.645334l10.837333 12.074666 35.285333 39.338667 8.149334 9.130667a9.6 9.6 0 0 1-2.901334 15.061333 283.306667 283.306667 0 0 1-13.952 6.4z" p-id="5379"></path></svg>
|
After Width: | Height: | Size: 2.5 KiB |
@ -15,6 +15,27 @@ limitations under the License. -->
|
||||
<template>
|
||||
<div class="chart" ref="chartRef" :style="`height:${height};width:${width};`">
|
||||
<div v-if="!available" class="no-data">No Data</div>
|
||||
<div class="menus" v-show="visMenus" ref="menus">
|
||||
<div class="tools" @click="associateMetrics">
|
||||
{{ t("associateMetrics") }}
|
||||
</div>
|
||||
<div
|
||||
class="tools"
|
||||
@click="viewTrace"
|
||||
v-if="props.relatedTrace && props.relatedTrace.enableRelate"
|
||||
>
|
||||
{{ t("viewTrace") }}
|
||||
</div>
|
||||
</div>
|
||||
<el-drawer
|
||||
v-model="showTrace"
|
||||
size="100%"
|
||||
:destroy-on-close="true"
|
||||
:before-close="() => (showTrace = false)"
|
||||
:append-to-body="true"
|
||||
>
|
||||
<Trace :data="traceOptions" />
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
@ -28,15 +49,28 @@ import {
|
||||
computed,
|
||||
} from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { EventParams } from "@/types/app";
|
||||
import { Filters, RelatedTrace } from "@/types/dashboard";
|
||||
import { useECharts } from "@/hooks/useEcharts";
|
||||
import { addResizeListener, removeResizeListener } from "@/utils/event";
|
||||
import Trace from "@/views/dashboard/related/trace/Index.vue";
|
||||
import associateProcessor from "@/hooks/useAssociateProcessor";
|
||||
|
||||
/*global Nullable, defineProps, defineEmits*/
|
||||
const emits = defineEmits(["select"]);
|
||||
const { t } = useI18n();
|
||||
const chartRef = ref<Nullable<HTMLDivElement>>(null);
|
||||
const menus = ref<Nullable<HTMLDivElement>>(null);
|
||||
const visMenus = ref<boolean>(false);
|
||||
const { setOptions, resize, getInstance } = useECharts(
|
||||
chartRef as Ref<HTMLDivElement>
|
||||
);
|
||||
const currentParams = ref<Nullable<EventParams>>(null);
|
||||
const showTrace = ref<boolean>(false);
|
||||
const traceOptions = ref<{ type: string; filters?: unknown }>({
|
||||
type: "Trace",
|
||||
});
|
||||
const props = defineProps({
|
||||
height: { type: String, default: "100%" },
|
||||
width: { type: String, default: "100%" },
|
||||
@ -45,15 +79,10 @@ const props = defineProps({
|
||||
default: () => ({}),
|
||||
},
|
||||
filters: {
|
||||
type: Object as PropType<{
|
||||
duration: {
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
};
|
||||
isRange: boolean;
|
||||
dataIndex?: number;
|
||||
sourceId: string;
|
||||
}>,
|
||||
type: Object as PropType<Filters>,
|
||||
},
|
||||
relatedTrace: {
|
||||
type: Object as PropType<RelatedTrace>,
|
||||
},
|
||||
});
|
||||
const available = computed(
|
||||
@ -66,14 +95,29 @@ const available = computed(
|
||||
onMounted(async () => {
|
||||
await setOptions(props.option);
|
||||
chartRef.value && addResizeListener(unref(chartRef), resize);
|
||||
instanceEvent();
|
||||
});
|
||||
|
||||
function instanceEvent() {
|
||||
setTimeout(() => {
|
||||
const instance = getInstance();
|
||||
|
||||
if (!instance) {
|
||||
return;
|
||||
}
|
||||
instance.on("click", (params: unknown) => {
|
||||
emits("select", params);
|
||||
instance.on("click", (params: EventParams) => {
|
||||
currentParams.value = params;
|
||||
if (!menus.value || !chartRef.value) {
|
||||
return;
|
||||
}
|
||||
visMenus.value = true;
|
||||
const w = chartRef.value.getBoundingClientRect().width || 0;
|
||||
if (w - params.event.offsetX > 125) {
|
||||
menus.value.style.left = params.event.offsetX + "px";
|
||||
} else {
|
||||
menus.value.style.left = params.event.offsetX - 125 + "px";
|
||||
}
|
||||
menus.value.style.top = params.event.offsetY + 5 + "px";
|
||||
});
|
||||
document.addEventListener(
|
||||
"click",
|
||||
@ -81,9 +125,7 @@ onMounted(async () => {
|
||||
if (instance.isDisposed()) {
|
||||
return;
|
||||
}
|
||||
instance.dispatchAction({
|
||||
type: "hideTip",
|
||||
});
|
||||
visMenus.value = false;
|
||||
instance.dispatchAction({
|
||||
type: "updateAxisPointer",
|
||||
currTrigger: "leave",
|
||||
@ -92,7 +134,13 @@ onMounted(async () => {
|
||||
true
|
||||
);
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
function associateMetrics() {
|
||||
emits("select", currentParams.value);
|
||||
visMenus.value = true;
|
||||
updateOptions();
|
||||
}
|
||||
|
||||
function updateOptions() {
|
||||
const instance = getInstance();
|
||||
@ -103,60 +151,26 @@ function updateOptions() {
|
||||
return;
|
||||
}
|
||||
if (props.filters.isRange) {
|
||||
const { eventAssociate } = associateProcessor(props);
|
||||
const options = eventAssociate();
|
||||
setOptions(options || props.option);
|
||||
} else {
|
||||
instance.dispatchAction({
|
||||
type: "showTip",
|
||||
type: "updateAxisPointer",
|
||||
dataIndex: props.filters.dataIndex,
|
||||
seriesIndex: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function eventAssociate() {
|
||||
if (!props.filters) {
|
||||
return;
|
||||
}
|
||||
if (!props.filters.duration) {
|
||||
return props.option;
|
||||
}
|
||||
if (!props.option.series[0]) {
|
||||
return;
|
||||
}
|
||||
const list = props.option.series[0].data.map(
|
||||
(d: (number | string)[]) => d[0]
|
||||
);
|
||||
if (!list.includes(props.filters.duration.endTime)) {
|
||||
return;
|
||||
}
|
||||
const markArea = {
|
||||
silent: true,
|
||||
itemStyle: {
|
||||
opacity: 0.3,
|
||||
},
|
||||
data: [
|
||||
[
|
||||
{
|
||||
xAxis: props.filters.duration.startTime,
|
||||
},
|
||||
{
|
||||
xAxis: props.filters.duration.endTime,
|
||||
},
|
||||
],
|
||||
],
|
||||
function viewTrace() {
|
||||
const item = associateProcessor(props).traceFilters(currentParams.value);
|
||||
traceOptions.value = {
|
||||
...traceOptions.value,
|
||||
filters: item,
|
||||
};
|
||||
const series = (window as any).structuredClone(props.option.series);
|
||||
for (const [key, temp] of series.entries()) {
|
||||
if (key === 0) {
|
||||
temp.markArea = markArea;
|
||||
}
|
||||
}
|
||||
const options = {
|
||||
...props.option,
|
||||
series,
|
||||
};
|
||||
return options;
|
||||
showTrace.value = true;
|
||||
visMenus.value = true;
|
||||
}
|
||||
|
||||
watch(
|
||||
@ -170,6 +184,7 @@ watch(
|
||||
}
|
||||
let options;
|
||||
if (props.filters && props.filters.isRange) {
|
||||
const { eventAssociate } = associateProcessor(props);
|
||||
options = eventAssociate();
|
||||
}
|
||||
setOptions(options || props.option);
|
||||
@ -201,4 +216,28 @@ onBeforeUnmount(() => {
|
||||
.chart {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.menus {
|
||||
position: absolute;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
z-index: 9999999;
|
||||
box-shadow: #ddd 1px 2px 10px;
|
||||
transition: all cubic-bezier(0.075, 0.82, 0.165, 1) linear;
|
||||
background-color: rgb(255, 255, 255);
|
||||
border-radius: 4px;
|
||||
color: rgb(51, 51, 51);
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.tools {
|
||||
padding: 5px;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #409eff;
|
||||
background-color: #eee;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -32,6 +32,6 @@ export const queryInstances = `query queryInstances(${Instances.variable}) {${In
|
||||
export const queryLayers = `query listLayer {${Layers.query}}`;
|
||||
export const queryService = `query queryService(${getService.variable}) {${getService.query}}`;
|
||||
export const queryInstance = `query queryInstance(${getInstance.variable}) {${getInstance.query}}`;
|
||||
export const queryEndpoint = `query queryInstance(${getEndpoint.variable}) {${getEndpoint.query}}`;
|
||||
export const queryEndpoint = `query queryEndpoint(${getEndpoint.variable}) {${getEndpoint.query}}`;
|
||||
export const queryProcesses = `query queryProcesses(${Processes.variable}) {${Processes.query}}`;
|
||||
export const queryProcess = `query queryProcess(${getProcess.variable}) {${getProcess.query}}`;
|
||||
|
138
src/hooks/useAssociateProcessor.ts
Normal file
138
src/hooks/useAssociateProcessor.ts
Normal file
@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import dateFormatStep from "@/utils/dateFormat";
|
||||
import getLocalTime from "@/utils/localtime";
|
||||
import { EventParams } from "@/types/app";
|
||||
|
||||
export default function associateProcessor(props: any) {
|
||||
function eventAssociate() {
|
||||
if (!props.filters) {
|
||||
return;
|
||||
}
|
||||
if (!props.filters.duration) {
|
||||
return props.option;
|
||||
}
|
||||
if (!props.option.series[0]) {
|
||||
return;
|
||||
}
|
||||
const list = props.option.series[0].data.map(
|
||||
(d: (number | string)[]) => d[0]
|
||||
);
|
||||
if (!list.includes(props.filters.duration.endTime)) {
|
||||
return;
|
||||
}
|
||||
const markArea = {
|
||||
silent: true,
|
||||
itemStyle: {
|
||||
opacity: 0.3,
|
||||
},
|
||||
data: [
|
||||
[
|
||||
{
|
||||
xAxis: props.filters.duration.startTime,
|
||||
},
|
||||
{
|
||||
xAxis: props.filters.duration.endTime,
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
||||
const series = (window as any).structuredClone(props.option.series);
|
||||
for (const [key, temp] of series.entries()) {
|
||||
if (key === 0) {
|
||||
temp.markArea = markArea;
|
||||
}
|
||||
}
|
||||
const options = {
|
||||
...props.option,
|
||||
series,
|
||||
};
|
||||
return options;
|
||||
}
|
||||
function traceFilters(currentParams: Nullable<EventParams>) {
|
||||
const appStore = useAppStoreWithOut();
|
||||
|
||||
if (!currentParams) {
|
||||
return;
|
||||
}
|
||||
const start = appStore.intervalUnix[currentParams.dataIndex];
|
||||
const { step } = appStore.durationRow;
|
||||
let duration = undefined;
|
||||
|
||||
if (start) {
|
||||
const end = start;
|
||||
duration = {
|
||||
start: dateFormatStep(
|
||||
getLocalTime(appStore.utc, new Date(start)),
|
||||
step,
|
||||
true
|
||||
),
|
||||
end: dateFormatStep(
|
||||
getLocalTime(appStore.utc, new Date(end)),
|
||||
step,
|
||||
true
|
||||
),
|
||||
step,
|
||||
};
|
||||
}
|
||||
const relatedTrace = props.relatedTrace || {};
|
||||
const status = relatedTrace.status;
|
||||
const queryOrder = relatedTrace.queryOrder;
|
||||
const latency = relatedTrace.latency;
|
||||
const series = props.option.series || [];
|
||||
const item: any = {
|
||||
duration,
|
||||
queryOrder,
|
||||
status,
|
||||
};
|
||||
if (latency) {
|
||||
const latencyList = series.map(
|
||||
(d: { name: string; data: number[][] }, index: number) => {
|
||||
const data = [
|
||||
d.data[currentParams.dataIndex][1],
|
||||
series[index + 1]
|
||||
? series[index + 1].data[currentParams.dataIndex][1]
|
||||
: Infinity,
|
||||
];
|
||||
return {
|
||||
label:
|
||||
d.name +
|
||||
"--" +
|
||||
(series[index + 1] ? series[index + 1].name : "Infinity"),
|
||||
value: String(index),
|
||||
data,
|
||||
};
|
||||
}
|
||||
);
|
||||
item.latency = latencyList;
|
||||
}
|
||||
const value = series.map(
|
||||
(d: { name: string; data: number[][] }, index: number) => {
|
||||
return {
|
||||
label: d.name,
|
||||
value: String(index),
|
||||
data: d.data[currentParams.dataIndex][1],
|
||||
date: d.data[currentParams.dataIndex][0],
|
||||
};
|
||||
}
|
||||
);
|
||||
item.metricValue = value;
|
||||
return item;
|
||||
}
|
||||
return { eventAssociate, traceFilters };
|
||||
}
|
@ -146,6 +146,7 @@ const msg = {
|
||||
pause: "Pause",
|
||||
begin: "Start",
|
||||
associateOptions: "Association Options",
|
||||
associateMetrics: "Association Metrics",
|
||||
widget: "Widget",
|
||||
nameTip:
|
||||
"The name only supports Chinese and English, horizontal lines and underscores. The length of the name is limited to 300 characters",
|
||||
@ -156,6 +157,16 @@ const msg = {
|
||||
postgreSQL: "PostgreSQL",
|
||||
endpointTips: "The table shows up to 20 pieces of endpoints.",
|
||||
apisix: "APISIX",
|
||||
viewTrace: "View Related Traces",
|
||||
relatedTraceOptions: "Related Trace Options",
|
||||
setLatencyDuration: "Set Latency Range",
|
||||
queryOrder: "Query Order",
|
||||
latency: "Latency",
|
||||
metricValues: "Metric Values",
|
||||
queryConditions: "Query Conditions",
|
||||
enableRelatedTrace: "Enable Related Trace",
|
||||
maxTraceDuration: "Maximum Duration",
|
||||
minTraceDuration: "Minimum Duration",
|
||||
seconds: "Seconds",
|
||||
hourTip: "Select Hour",
|
||||
minuteTip: "Select Minute",
|
||||
@ -348,6 +359,6 @@ const msg = {
|
||||
"Notice: Please press Enter after inputting a key of content, exclude key of content(key=value).",
|
||||
language: "Language",
|
||||
gateway: "Gateway",
|
||||
virtualMQ: "Virtual MQ"
|
||||
virtualMQ: "Virtual MQ",
|
||||
};
|
||||
export default msg;
|
||||
|
@ -146,6 +146,7 @@ const msg = {
|
||||
pause: "Pausa",
|
||||
begin: "Inicio",
|
||||
associateOptions: "Opciones de asociación",
|
||||
associateMetrics: "Índice de correlación",
|
||||
widget: "Dispositivo pequeño",
|
||||
text: "Texto",
|
||||
duplicateName: "Nombre duplicado",
|
||||
@ -156,10 +157,20 @@ const msg = {
|
||||
postgreSQL: "PostgreSQL",
|
||||
endpointTips: "Aquí, la tabla muestra hasta 20 punto final.",
|
||||
apisix: "APISIX",
|
||||
queryOrder: "Orden de consulta",
|
||||
latency: "Retraso",
|
||||
metricValues: "Valor métrico",
|
||||
seconds: "Segundos",
|
||||
hourTip: "Seleccione Hora",
|
||||
minuteTip: "Seleccione Minuto",
|
||||
secondTip: "Seleccione Segundo",
|
||||
viewTrace: "Ver trazas relacionadas",
|
||||
relatedTraceOptions: "Opciones de seguimiento relacionadas",
|
||||
setLatencyDuration: "Establecer el rango de retardo",
|
||||
enableRelatedTrace: "Activar trazas relacionadas",
|
||||
queryConditions: "Condiciones de consulta",
|
||||
maxTraceDuration: "Duración máxima",
|
||||
minTraceDuration: "Duración mínima",
|
||||
second: "s",
|
||||
yearSuffix: "Año",
|
||||
monthsHead: "Ene_Feb_Mar_Abr_May_Jun_Jul_Ago_Set_Oct_Nov_Dic",
|
||||
|
@ -144,6 +144,7 @@ const msg = {
|
||||
pause: "暂停",
|
||||
begin: "开始",
|
||||
associateOptions: "关联选项",
|
||||
associateMetrics: "关联指标",
|
||||
widget: "部件",
|
||||
enableAssociate: "启用关联",
|
||||
nameTip: "该名称仅支持中文和英文、横线和下划线, 并且限制长度为300个字符",
|
||||
@ -153,6 +154,16 @@ const msg = {
|
||||
postgreSQL: "PostgreSQL",
|
||||
endpointTips: "这里最多展示20条endpoints。",
|
||||
apisix: "APISIX",
|
||||
viewTrace: "查看相关Trace",
|
||||
relatedTraceOptions: "相关的Trace选项",
|
||||
setLatencyDuration: "设置延时范围",
|
||||
queryOrder: "查询顺序",
|
||||
latency: "延迟",
|
||||
metricValues: "指标值",
|
||||
enableRelatedTrace: "启用相关Trace",
|
||||
queryConditions: "查询条件",
|
||||
maxTraceDuration: "最大持续时间",
|
||||
minTraceDuration: "最小持续时间",
|
||||
seconds: "秒",
|
||||
hourTip: "选择小时",
|
||||
minuteTip: "选择分钟",
|
||||
|
@ -88,7 +88,7 @@ export default [
|
||||
notShow: true,
|
||||
layer: "VIRTUAL_MQ",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
@ -71,7 +71,7 @@ export const appStore = defineStore({
|
||||
step: this.duration.step,
|
||||
};
|
||||
},
|
||||
intervalTime(): string[] {
|
||||
intervalUnix(): number[] {
|
||||
let interval = 946080000000;
|
||||
switch (this.duration.step) {
|
||||
case "MINUTE":
|
||||
@ -97,12 +97,17 @@ export const appStore = defineStore({
|
||||
this.utcMin * 60000;
|
||||
const startUnix: number = this.duration.start.getTime();
|
||||
const endUnix: number = this.duration.end.getTime();
|
||||
const timeIntervals: string[] = [];
|
||||
const timeIntervals: number[] = [];
|
||||
for (let i = 0; i <= endUnix - startUnix; i += interval) {
|
||||
const temp: string = dateFormatTime(
|
||||
new Date(startUnix + i - utcSpace),
|
||||
this.duration.step
|
||||
);
|
||||
timeIntervals.push(startUnix + i - utcSpace);
|
||||
}
|
||||
return timeIntervals;
|
||||
},
|
||||
intervalTime(): string[] {
|
||||
const arr = this.intervalUnix;
|
||||
const timeIntervals: string[] = [];
|
||||
for (const item of arr) {
|
||||
const temp: string = dateFormatTime(new Date(item), this.duration.step);
|
||||
timeIntervals.push(temp);
|
||||
}
|
||||
return timeIntervals;
|
||||
|
@ -22,6 +22,7 @@ import graphql from "@/graphql";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import { QueryOrders } from "@/views/dashboard/data";
|
||||
|
||||
interface TraceState {
|
||||
services: Service[];
|
||||
@ -47,7 +48,7 @@ export const traceStore = defineStore({
|
||||
conditions: {
|
||||
queryDuration: useAppStoreWithOut().durationTime,
|
||||
traceState: "ALL",
|
||||
queryOrder: "BY_START_TIME",
|
||||
queryOrder: QueryOrders[0].value,
|
||||
paging: { pageNum: 1, pageSize: 20 },
|
||||
},
|
||||
traceSpanLogs: [],
|
||||
@ -71,7 +72,7 @@ export const traceStore = defineStore({
|
||||
queryDuration: useAppStoreWithOut().durationTime,
|
||||
paging: { pageNum: 1, pageSize: 20 },
|
||||
traceState: "ALL",
|
||||
queryOrder: "BY_START_TIME",
|
||||
queryOrder: QueryOrders[0].value,
|
||||
};
|
||||
},
|
||||
async getServices(layer: string) {
|
||||
@ -84,6 +85,36 @@ export const traceStore = defineStore({
|
||||
this.services = res.data.data.services;
|
||||
return res.data;
|
||||
},
|
||||
async getService(serviceId: string) {
|
||||
if (!serviceId) {
|
||||
return;
|
||||
}
|
||||
const res: AxiosResponse = await graphql.query("queryService").params({
|
||||
serviceId,
|
||||
});
|
||||
|
||||
return res.data;
|
||||
},
|
||||
async getInstance(instanceId: string) {
|
||||
if (!instanceId) {
|
||||
return;
|
||||
}
|
||||
const res: AxiosResponse = await graphql.query("queryInstance").params({
|
||||
instanceId,
|
||||
});
|
||||
|
||||
return res.data;
|
||||
},
|
||||
async getEndpoint(endpointId: string) {
|
||||
if (!endpointId) {
|
||||
return;
|
||||
}
|
||||
const res: AxiosResponse = await graphql.query("queryEndpoint").params({
|
||||
endpointId,
|
||||
});
|
||||
|
||||
return res.data;
|
||||
},
|
||||
async getInstances(id: string) {
|
||||
const serviceId = this.selectorStore.currentService
|
||||
? this.selectorStore.currentService.id
|
||||
|
@ -17,6 +17,7 @@
|
||||
import "element-plus/es/components/message/style/css";
|
||||
import "element-plus/es/components/message-box/style/css";
|
||||
import "element-plus/es/components/notification/style/css";
|
||||
import "element-plus/es/components/drawer/style/css";
|
||||
import "./grid.scss";
|
||||
import "./lib.scss";
|
||||
import "./reset.scss";
|
||||
|
@ -153,6 +153,14 @@ pre {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.el-drawer__header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.el-drawer__body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.switch {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
15
src/types/app.d.ts
vendored
15
src/types/app.d.ts
vendored
@ -32,3 +32,18 @@ export type Paging = {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
};
|
||||
|
||||
export type EventParams = {
|
||||
componentType: string;
|
||||
seriesType: string;
|
||||
seriesIndex: number;
|
||||
seriesName: string;
|
||||
name: string;
|
||||
dataIndex: number;
|
||||
data: unknown;
|
||||
dataType: string;
|
||||
value: number | Array;
|
||||
color: string;
|
||||
event: any;
|
||||
dataIndex: number;
|
||||
};
|
||||
|
1
src/types/components.d.ts
vendored
1
src/types/components.d.ts
vendored
@ -25,6 +25,7 @@ declare module '@vue/runtime-core' {
|
||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSlider: typeof import('element-plus/es')['ElSlider']
|
||||
|
38
src/types/dashboard.d.ts
vendored
38
src/types/dashboard.d.ts
vendored
@ -1,3 +1,4 @@
|
||||
import { DurationTime } from "@/types/app";
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
@ -39,19 +40,32 @@ export interface LayoutConfig {
|
||||
id?: string;
|
||||
associate?: { widgetId: string }[];
|
||||
eventAssociate?: boolean;
|
||||
filters?: {
|
||||
dataIndex: number;
|
||||
sourceId: string;
|
||||
isRange?: boolean;
|
||||
duration?: {
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
};
|
||||
traceId?: string;
|
||||
spanId?: string;
|
||||
segmentId?: string;
|
||||
};
|
||||
filters?: Filters;
|
||||
relatedTrace?: RelatedTrace;
|
||||
}
|
||||
export type RelatedTrace = {
|
||||
duration: DurationTime;
|
||||
status: string;
|
||||
queryOrder: string;
|
||||
latency: boolean;
|
||||
enableRelate: boolean;
|
||||
};
|
||||
|
||||
export type Filters = {
|
||||
dataIndex: number;
|
||||
sourceId: string;
|
||||
isRange?: boolean;
|
||||
duration?: {
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
};
|
||||
traceId?: string;
|
||||
spanId?: string;
|
||||
segmentId?: string;
|
||||
id?: string;
|
||||
queryOrder?: string;
|
||||
status?: string;
|
||||
};
|
||||
|
||||
export type MetricConfigOpt = {
|
||||
unit?: string;
|
||||
|
@ -36,6 +36,7 @@ limitations under the License. -->
|
||||
metrics: dashboardStore.selectedGrid.metrics,
|
||||
metricTypes: dashboardStore.selectedGrid.metricTypes,
|
||||
metricConfig: dashboardStore.selectedGrid.metricConfig,
|
||||
relatedTrace: dashboardStore.selectedGrid.relatedTrace,
|
||||
}"
|
||||
:needQuery="true"
|
||||
/>
|
||||
@ -65,6 +66,13 @@ limitations under the License. -->
|
||||
>
|
||||
<AssociateOptions />
|
||||
</el-collapse-item>
|
||||
<el-collapse-item
|
||||
:title="t('relatedTraceOptions')"
|
||||
name="5"
|
||||
v-if="hasAssociate"
|
||||
>
|
||||
<RelatedTraceOptions />
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
<div class="footer">
|
||||
|
@ -0,0 +1,90 @@
|
||||
<!-- 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="item">
|
||||
<span class="label">{{ t("enableRelatedTrace") }}</span>
|
||||
<el-switch
|
||||
v-model="enableRelate"
|
||||
active-text="Yes"
|
||||
inactive-text="No"
|
||||
@change="updateConfig({ enableRelate })"
|
||||
/>
|
||||
</div>
|
||||
<div v-show="enableRelate">
|
||||
<div class="item">
|
||||
<span class="label">{{ t("status") }}</span>
|
||||
<Selector
|
||||
size="small"
|
||||
:value="status"
|
||||
:options="Status"
|
||||
placeholder="Select a status"
|
||||
@change="updateConfig({ status: $event[0].value })"
|
||||
/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="label">{{ t("queryOrder") }}</span>
|
||||
<Selector
|
||||
size="small"
|
||||
:value="queryOrder"
|
||||
:options="QueryOrders"
|
||||
placeholder="Select a option"
|
||||
@change="updateConfig({ queryOrder: $event[0].value })"
|
||||
/>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="label">{{ t("setLatencyDuration") }}</span>
|
||||
<el-switch
|
||||
v-model="latency"
|
||||
active-text="Yes"
|
||||
inactive-text="No"
|
||||
@change="updateConfig({ latency })"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { Status, QueryOrders } from "../../data";
|
||||
|
||||
const { t } = useI18n();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const traceOpt = dashboardStore.selectedGrid.relatedTrace || {};
|
||||
const status = ref<string>(traceOpt.status || Status[0].value);
|
||||
const queryOrder = ref<string>(traceOpt.queryOrder || QueryOrders[0].value);
|
||||
const latency = ref<boolean>(traceOpt.setLatencyDuration || false);
|
||||
const enableRelate = ref<boolean>(traceOpt.enableRelate || false);
|
||||
|
||||
function updateConfig(param: { [key: string]: unknown }) {
|
||||
const relatedTrace = {
|
||||
...dashboardStore.selectedGrid.relatedTrace,
|
||||
...param,
|
||||
};
|
||||
dashboardStore.selectWidget({ ...dashboardStore.selectedGrid, relatedTrace });
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.label {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
@ -19,10 +19,12 @@ import StyleOptions from "./graph-styles";
|
||||
import WidgetOptions from "./WidgetOptions.vue";
|
||||
import MetricOptions from "./metric/Index.vue";
|
||||
import AssociateOptions from "./AssociateOptions.vue";
|
||||
import RelatedTraceOptions from "./RelatedTraceOptions.vue";
|
||||
|
||||
export default {
|
||||
...StyleOptions,
|
||||
WidgetOptions,
|
||||
MetricOptions,
|
||||
AssociateOptions,
|
||||
RelatedTraceOptions,
|
||||
};
|
||||
|
@ -40,7 +40,7 @@ limitations under the License. -->
|
||||
<span class="tab-icons">
|
||||
<el-tooltip content="Copy Link" placement="bottom">
|
||||
<i @click="copyLink">
|
||||
<Icon size="middle" iconName="review-list" class="tab-icon" />
|
||||
<Icon size="middle" iconName="copy" class="tab-icon" />
|
||||
</i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
|
@ -64,6 +64,7 @@ limitations under the License. -->
|
||||
id: data.id,
|
||||
metricConfig: data.metricConfig,
|
||||
filters: data.filters || {},
|
||||
relatedTrace: data.relatedTrace || {},
|
||||
}"
|
||||
:needQuery="needQuery"
|
||||
@click="clickHandle"
|
||||
|
@ -174,7 +174,7 @@ export const SortOrder = [
|
||||
];
|
||||
export const AllTools = [
|
||||
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
|
||||
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
|
||||
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
|
||||
{ name: "library_books", content: "Add Text", id: "addText" },
|
||||
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
|
||||
{ name: "merge", content: "Add Trace", id: "addTrace" },
|
||||
@ -182,7 +182,7 @@ export const AllTools = [
|
||||
];
|
||||
export const ServiceTools = [
|
||||
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
|
||||
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
|
||||
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
|
||||
{ name: "library_books", content: "Add Text", id: "addText" },
|
||||
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
|
||||
{ name: "merge", content: "Add Trace", id: "addTrace" },
|
||||
@ -194,7 +194,7 @@ export const ServiceTools = [
|
||||
];
|
||||
export const InstanceTools = [
|
||||
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
|
||||
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
|
||||
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
|
||||
{ name: "library_books", content: "Add Text", id: "addText" },
|
||||
{ name: "merge", content: "Add Trace", id: "addTrace" },
|
||||
{ name: "assignment", content: "Add Log", id: "addLog" },
|
||||
@ -208,7 +208,7 @@ export const InstanceTools = [
|
||||
];
|
||||
export const EndpointTools = [
|
||||
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
|
||||
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
|
||||
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
|
||||
{ name: "library_books", content: "Add Text", id: "addText" },
|
||||
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
|
||||
{ name: "merge", content: "Add Trace", id: "addTrace" },
|
||||
@ -217,25 +217,25 @@ export const EndpointTools = [
|
||||
];
|
||||
export const ProcessTools = [
|
||||
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
|
||||
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
|
||||
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
|
||||
{ name: "library_books", content: "Add Text", id: "addText" },
|
||||
{ name: "time_range", content: "Add Time Range Text", id: "addTimeRange" },
|
||||
];
|
||||
export const ServiceRelationTools = [
|
||||
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
|
||||
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
|
||||
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
|
||||
{ name: "library_books", content: "Add Text", id: "addText" },
|
||||
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
|
||||
];
|
||||
|
||||
export const EndpointRelationTools = [
|
||||
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
|
||||
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
|
||||
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
|
||||
{ name: "library_books", content: "Add Text", id: "addText" },
|
||||
];
|
||||
export const InstanceRelationTools = [
|
||||
{ name: "playlist_add", content: "Add Widget", id: "addWidget" },
|
||||
{ name: "all_inbox", content: "Add Tab", id: "addTab" },
|
||||
{ name: "all_inbox", content: "Add Tabs", id: "addTab" },
|
||||
{ name: "library_books", content: "Add Text", id: "addText" },
|
||||
{ name: "device_hub", content: "Add Topology", id: "addTopology" },
|
||||
];
|
||||
|
@ -24,7 +24,12 @@ limitations under the License. -->
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from "vue";
|
||||
import Line from "./Line.vue";
|
||||
import { AreaConfig, EventParams } from "@/types/dashboard";
|
||||
import {
|
||||
AreaConfig,
|
||||
EventParams,
|
||||
RelatedTrace,
|
||||
Filters,
|
||||
} from "@/types/dashboard";
|
||||
|
||||
/*global defineProps, defineEmits */
|
||||
const emits = defineEmits(["click"]);
|
||||
@ -37,15 +42,8 @@ defineProps({
|
||||
config: {
|
||||
type: Object as PropType<
|
||||
AreaConfig & {
|
||||
filters: {
|
||||
sourceId: string;
|
||||
duration: {
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
};
|
||||
isRange: boolean;
|
||||
dataIndex?: number;
|
||||
};
|
||||
filters: Filters;
|
||||
relatedTrace: RelatedTrace;
|
||||
} & { id: string }
|
||||
>,
|
||||
default: () => ({}),
|
||||
|
@ -18,7 +18,12 @@ limitations under the License. -->
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import { BarConfig, EventParams } from "@/types/dashboard";
|
||||
import {
|
||||
BarConfig,
|
||||
EventParams,
|
||||
RelatedTrace,
|
||||
Filters,
|
||||
} from "@/types/dashboard";
|
||||
|
||||
/*global defineProps, defineEmits */
|
||||
const emits = defineEmits(["click"]);
|
||||
@ -32,15 +37,8 @@ const props = defineProps({
|
||||
config: {
|
||||
type: Object as PropType<
|
||||
BarConfig & {
|
||||
filters: {
|
||||
sourceId: string;
|
||||
duration: {
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
};
|
||||
isRange: boolean;
|
||||
dataIndex?: number;
|
||||
};
|
||||
filters: Filters;
|
||||
relatedTrace: RelatedTrace;
|
||||
} & { id: string }
|
||||
>,
|
||||
default: () => ({}),
|
||||
@ -107,16 +105,12 @@ function getOption() {
|
||||
return {
|
||||
color,
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
zlevel: 1000,
|
||||
z: 60,
|
||||
confine: true,
|
||||
textStyle: {
|
||||
fontSize: 13,
|
||||
trigger: "none",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
color: "#333",
|
||||
backgroundColor: "rgba(255, 255, 255, 0.8)",
|
||||
},
|
||||
enterable: true,
|
||||
extraCssText: "max-height: 300px; overflow: auto; border: none",
|
||||
},
|
||||
legend: {
|
||||
type: "scroll",
|
||||
@ -136,6 +130,12 @@ function getOption() {
|
||||
bottom: 5,
|
||||
containLabel: true,
|
||||
},
|
||||
axisPointer: {
|
||||
label: {
|
||||
color: "#333",
|
||||
backgroundColor: "rgba(255, 255, 255, 0.8)",
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
type: "category",
|
||||
axisTick: {
|
||||
|
@ -13,12 +13,22 @@ 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>
|
||||
<Graph :option="option" @select="clickEvent" :filters="config.filters" />
|
||||
<Graph
|
||||
:option="option"
|
||||
@select="clickEvent"
|
||||
:filters="config.filters"
|
||||
:relatedTrace="config.relatedTrace"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import { LineConfig, EventParams } from "@/types/dashboard";
|
||||
import {
|
||||
LineConfig,
|
||||
EventParams,
|
||||
RelatedTrace,
|
||||
Filters,
|
||||
} from "@/types/dashboard";
|
||||
|
||||
/*global defineProps, defineEmits */
|
||||
const emits = defineEmits(["click"]);
|
||||
@ -32,15 +42,8 @@ const props = defineProps({
|
||||
config: {
|
||||
type: Object as PropType<
|
||||
LineConfig & {
|
||||
filters: {
|
||||
sourceId: string;
|
||||
duration: {
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
};
|
||||
isRange: boolean;
|
||||
dataIndex?: number;
|
||||
};
|
||||
filters: Filters;
|
||||
relatedTrace: RelatedTrace;
|
||||
} & { id: string }
|
||||
>,
|
||||
default: () => ({
|
||||
@ -115,13 +118,19 @@ function getOption() {
|
||||
break;
|
||||
}
|
||||
const tooltip = {
|
||||
trigger: "axis",
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
trigger: "none",
|
||||
axisPointer: {
|
||||
type: "cross",
|
||||
color: "#333",
|
||||
backgroundColor: "rgba(255, 255, 255, 0.8)",
|
||||
},
|
||||
enterable: true,
|
||||
confine: true,
|
||||
// trigger: "axis",
|
||||
// textStyle: {
|
||||
// fontSize: 12,
|
||||
// color: "#333",
|
||||
// },
|
||||
// enterable: true,
|
||||
// confine: true,
|
||||
extraCssText: "max-height: 300px; overflow: auto; border: none;",
|
||||
};
|
||||
const tips = {
|
||||
@ -151,6 +160,12 @@ function getOption() {
|
||||
color: props.theme === "dark" ? "#fff" : "#333",
|
||||
},
|
||||
},
|
||||
axisPointer: {
|
||||
label: {
|
||||
color: "#333",
|
||||
backgroundColor: "rgba(255, 255, 255, 0.8)",
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
top: keys.length === 1 ? 15 : 55,
|
||||
left: 0,
|
||||
|
@ -23,14 +23,19 @@ limitations under the License. -->
|
||||
{{ i.name }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="copy">
|
||||
<Icon
|
||||
iconName="review-list"
|
||||
size="middle"
|
||||
class="cp"
|
||||
@click="handleClick(i.name)"
|
||||
/>
|
||||
</div>
|
||||
<el-popover placement="bottom" trigger="click">
|
||||
<template #reference>
|
||||
<div class="operation-icon cp ml-10">
|
||||
<Icon iconName="ellipsis_v" size="middle" />
|
||||
</div>
|
||||
</template>
|
||||
<div class="operation" @click="handleClick(i.name)">
|
||||
<span>{{ t("copy") }}</span>
|
||||
</div>
|
||||
<div class="operation" @click="viewTrace(i)">
|
||||
<span>{{ t("viewTrace") }}</span>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
<el-progress
|
||||
:stroke-width="6"
|
||||
@ -39,28 +44,45 @@ limitations under the License. -->
|
||||
:show-text="false"
|
||||
/>
|
||||
</div>
|
||||
<el-drawer
|
||||
v-model="showTrace"
|
||||
size="100%"
|
||||
:destroy-on-close="true"
|
||||
:before-close="() => (showTrace = false)"
|
||||
:append-to-body="true"
|
||||
>
|
||||
<Trace :data="traceOptions" />
|
||||
</el-drawer>
|
||||
</div>
|
||||
<div class="center no-data" v-else>No Data</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import type { PropType } from "vue";
|
||||
import { computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { computed, ref } from "vue";
|
||||
import copy from "@/utils/copy";
|
||||
import { TextColors } from "@/views/dashboard/data";
|
||||
import Trace from "@/views/dashboard/related/trace/Index.vue";
|
||||
import { QueryOrders, Status } from "../data";
|
||||
/*global defineProps */
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object as PropType<{
|
||||
[key: string]: { name: string; value: number; traceIds: string[] }[];
|
||||
[key: string]: { name: string; value: number; id: string }[];
|
||||
}>,
|
||||
default: () => ({}),
|
||||
},
|
||||
config: {
|
||||
type: Object as PropType<{ color: string }>,
|
||||
type: Object as PropType<{ color: string; metrics: string[] }>,
|
||||
default: () => ({ color: "purple" }),
|
||||
},
|
||||
intervalTime: { type: Array as PropType<string[]>, default: () => [] },
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const showTrace = ref<boolean>(false);
|
||||
const traceOptions = ref<{ type: string; filters?: unknown }>({
|
||||
type: "Trace",
|
||||
});
|
||||
const key = computed(() => Object.keys(props.data)[0] || "");
|
||||
const available = computed(
|
||||
() =>
|
||||
@ -78,6 +100,21 @@ const maxValue = computed(() => {
|
||||
function handleClick(i: string) {
|
||||
copy(i);
|
||||
}
|
||||
function viewTrace(item: { name: string; id: string; value: unknown }) {
|
||||
const filters = {
|
||||
...item,
|
||||
queryOrder: QueryOrders[1].value,
|
||||
status: Status[2].value,
|
||||
metricValue: [
|
||||
{ label: props.config.metrics[0], data: item.value, value: item.name },
|
||||
],
|
||||
};
|
||||
traceOptions.value = {
|
||||
...traceOptions.value,
|
||||
filters,
|
||||
};
|
||||
showTrace.value = true;
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.top-list {
|
||||
@ -109,10 +146,6 @@ function handleClick(i: string) {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.copy {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.calls {
|
||||
font-size: 12px;
|
||||
padding: 0 5px;
|
||||
@ -141,4 +174,22 @@ function handleClick(i: string) {
|
||||
-webkit-box-pack: center;
|
||||
-webkit-box-align: center;
|
||||
}
|
||||
|
||||
.operation-icon {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.operation {
|
||||
padding: 5px 0;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
|
||||
&:hover {
|
||||
color: #409eff;
|
||||
background-color: #eee;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -34,7 +34,7 @@ limitations under the License. -->
|
||||
</el-button>
|
||||
</div>
|
||||
</h5>
|
||||
<div class="mb-5 blue sm">
|
||||
<div class="mb-5 blue">
|
||||
<Selector
|
||||
size="small"
|
||||
:value="
|
||||
@ -46,12 +46,7 @@ limitations under the License. -->
|
||||
@change="changeTraceId"
|
||||
class="trace-detail-ids"
|
||||
/>
|
||||
<Icon
|
||||
size="sm"
|
||||
class="icon grey link-hover cp ml-5"
|
||||
iconName="review-list"
|
||||
@click="handleClick"
|
||||
/>
|
||||
<Icon class="cp ml-5" iconName="copy" @click="handleClick" />
|
||||
</div>
|
||||
<div class="flex-h item">
|
||||
<div>
|
||||
|
@ -95,17 +95,15 @@ limitations under the License. -->
|
||||
import { ref, reactive, watch, onUnmounted } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Option } from "@/types/app";
|
||||
import { Status } from "../../data";
|
||||
import { Option, DurationTime } from "@/types/app";
|
||||
import { useTraceStore } from "@/store/modules/trace";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import ConditionTags from "@/views/components/ConditionTags.vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { EntityType } from "../../data";
|
||||
import { EntityType, QueryOrders, Status } from "../../data";
|
||||
import { LayoutConfig } from "@/types/dashboard";
|
||||
import { DurationTime } from "@/types/app";
|
||||
|
||||
/*global defineProps, Recordable */
|
||||
const props = defineProps({
|
||||
@ -115,28 +113,29 @@ const props = defineProps({
|
||||
default: () => ({ graph: {} }),
|
||||
},
|
||||
});
|
||||
const traceId = ref<string>(
|
||||
(props.data.filters && props.data.filters.traceId) || ""
|
||||
);
|
||||
const filters = reactive<Recordable>(props.data.filters || {});
|
||||
const traceId = ref<string>(filters.traceId || "");
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStoreWithOut();
|
||||
const selectorStore = useSelectorStore();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const traceStore = useTraceStore();
|
||||
const duration = ref<DurationTime>(
|
||||
(props.data.filters && props.data.filters.duration) || appStore.durationTime
|
||||
);
|
||||
const duration = ref<DurationTime>(filters.duration || appStore.durationTime);
|
||||
const minTraceDuration = ref<number>();
|
||||
const maxTraceDuration = ref<number>();
|
||||
const tagsList = ref<string[]>([]);
|
||||
const tagsMap = ref<Option[]>([]);
|
||||
const state = reactive<Recordable>({
|
||||
status: { label: "All", value: "ALL" },
|
||||
status: filters.status === "ERROR" ? Status[2] : Status[0],
|
||||
instance: { value: "0", label: "All" },
|
||||
endpoint: { value: "0", label: "All" },
|
||||
service: { value: "", label: "" },
|
||||
});
|
||||
|
||||
if (filters.queryOrder) {
|
||||
traceStore.setTraceCondition({
|
||||
queryOrder: filters.queryOrder,
|
||||
});
|
||||
}
|
||||
if (props.needQuery) {
|
||||
init();
|
||||
}
|
||||
@ -164,7 +163,7 @@ async function getServices() {
|
||||
ElMessage.error(resp.errors);
|
||||
return;
|
||||
}
|
||||
state.service = traceStore.services[0];
|
||||
state.service = getCurrentNode(traceStore.services) || traceStore.services[0];
|
||||
getEndpoints(state.service.id);
|
||||
getInstances(state.service.id);
|
||||
}
|
||||
@ -175,7 +174,8 @@ async function getEndpoints(id?: string, keyword?: string) {
|
||||
ElMessage.error(resp.errors);
|
||||
return;
|
||||
}
|
||||
state.endpoint = traceStore.endpoints[0];
|
||||
state.endpoint =
|
||||
getCurrentNode(traceStore.endpoints) || traceStore.endpoints[0];
|
||||
}
|
||||
async function getInstances(id?: string) {
|
||||
const resp = await traceStore.getInstances(id);
|
||||
@ -183,9 +183,39 @@ async function getInstances(id?: string) {
|
||||
ElMessage.error(resp.errors);
|
||||
return;
|
||||
}
|
||||
state.instance = traceStore.instances[0];
|
||||
state.instance =
|
||||
getCurrentNode(traceStore.instances) || traceStore.instances[0];
|
||||
}
|
||||
function searchTraces() {
|
||||
function getCurrentNode(arr: { id: string }[]) {
|
||||
let item;
|
||||
if (!props.data.filters) {
|
||||
return item;
|
||||
}
|
||||
if (props.data.filters.id) {
|
||||
item = arr.find((d: { id: string }) => d.id === props.data.filters?.id);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
function setCondition() {
|
||||
let param: any = {
|
||||
traceState: state.status.value || "ALL",
|
||||
tags: tagsMap.value.length ? tagsMap.value : undefined,
|
||||
queryOrder: traceStore.conditions.queryOrder || QueryOrders[1].value,
|
||||
queryDuration: duration.value,
|
||||
minTraceDuration: Number(minTraceDuration.value),
|
||||
maxTraceDuration: Number(maxTraceDuration.value),
|
||||
traceId: traceId.value || undefined,
|
||||
paging: { pageNum: 1, pageSize: 20 },
|
||||
};
|
||||
if (props.data.filters && props.data.filters.id) {
|
||||
param = {
|
||||
...param,
|
||||
serviceId: selectorStore.currentService.id,
|
||||
endpointId: state.endpoint.id || undefined,
|
||||
serviceInstanceId: state.instance.id || undefined,
|
||||
};
|
||||
return param;
|
||||
}
|
||||
let endpoint = "",
|
||||
instance = "";
|
||||
if (dashboardStore.entity === EntityType[2].value) {
|
||||
@ -194,21 +224,18 @@ function searchTraces() {
|
||||
if (dashboardStore.entity === EntityType[3].value) {
|
||||
instance = selectorStore.currentPod.id;
|
||||
}
|
||||
traceStore.setTraceCondition({
|
||||
param = {
|
||||
...param,
|
||||
serviceId: selectorStore.currentService
|
||||
? selectorStore.currentService.id
|
||||
: state.service.id,
|
||||
traceId: traceId.value || undefined,
|
||||
endpointId: endpoint || state.endpoint.id || undefined,
|
||||
serviceInstanceId: instance || state.instance.id || undefined,
|
||||
traceState: state.status.value || "ALL",
|
||||
queryDuration: duration.value,
|
||||
minTraceDuration: Number(minTraceDuration.value),
|
||||
maxTraceDuration: Number(maxTraceDuration.value),
|
||||
queryOrder: traceStore.conditions.queryOrder || "BY_DURATION",
|
||||
tags: tagsMap.value.length ? tagsMap.value : undefined,
|
||||
paging: { pageNum: 1, pageSize: 20 },
|
||||
});
|
||||
};
|
||||
return param;
|
||||
}
|
||||
function searchTraces() {
|
||||
traceStore.setTraceCondition(setCondition());
|
||||
queryTraces();
|
||||
}
|
||||
async function queryTraces() {
|
||||
@ -263,17 +290,19 @@ watch(
|
||||
}
|
||||
}
|
||||
);
|
||||
// Event widget associate with trace widget
|
||||
watch(
|
||||
() => props.data.filters,
|
||||
(newJson, oldJson) => {
|
||||
if (props.data.filters) {
|
||||
if (JSON.stringify(newJson) === JSON.stringify(oldJson)) {
|
||||
return;
|
||||
}
|
||||
traceId.value = props.data.filters.traceId || "";
|
||||
duration.value = props.data.filters.duration || appStore.durationTime;
|
||||
init();
|
||||
if (!props.data.filters) {
|
||||
return;
|
||||
}
|
||||
if (JSON.stringify(newJson) === JSON.stringify(oldJson)) {
|
||||
return;
|
||||
}
|
||||
traceId.value = props.data.filters.traceId || "";
|
||||
duration.value = props.data.filters.duration || appStore.durationTime;
|
||||
init();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
267
src/views/dashboard/related/trace/Header.vue
Normal file
267
src/views/dashboard/related/trace/Header.vue
Normal file
@ -0,0 +1,267 @@
|
||||
<!-- 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="conditions flex-h">
|
||||
<el-radio-group v-model="conditions" @change="changeCondition">
|
||||
<el-radio-button
|
||||
v-for="(item, index) in items"
|
||||
:label="item.label"
|
||||
:key="item.label + index"
|
||||
border
|
||||
>
|
||||
{{ t(item.label) }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
<Selector
|
||||
v-if="conditions === 'latency' && filters.latency.length > 1"
|
||||
:value="filters.latency[0].value"
|
||||
:options="filters.latency"
|
||||
placeholder="Select a option"
|
||||
@change="changeLatency"
|
||||
class="ml-10"
|
||||
/>
|
||||
<el-popover trigger="hover" width="250" placement="bottom" effect="light">
|
||||
<template #reference>
|
||||
<div class="cp conditions-popup">
|
||||
<Icon iconName="conditions" size="middle" />
|
||||
</div>
|
||||
</template>
|
||||
<div>
|
||||
<div class="title">{{ t("queryConditions") }}</div>
|
||||
<div
|
||||
v-for="key in Object.keys(FiltersKeys)"
|
||||
:key="key"
|
||||
v-show="traceStore.conditions[FiltersKeys[key]]"
|
||||
>
|
||||
<span v-if="key !== 'duration'">
|
||||
{{ t(key) }}: {{ traceStore.conditions[FiltersKeys[key]] }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
<el-popover trigger="hover" width="250" placement="bottom" effect="light">
|
||||
<template #reference>
|
||||
<div class="cp metric-value">
|
||||
<Icon iconName="info_outline" size="middle" />
|
||||
</div>
|
||||
</template>
|
||||
<div>
|
||||
<div class="title">{{ t("metricValues") }}</div>
|
||||
<div v-for="metric in filters.metricValue" :key="metric.value">
|
||||
{{ metric.label }}: {{ metric.data }}
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div class="flex-h">
|
||||
<ConditionTags :type="'TRACE'" @update="updateTags" />
|
||||
<div class="search-btn">
|
||||
<el-button size="small" type="primary" @click="queryTraces">
|
||||
{{ t("search") }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, onUnmounted } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Option, DurationTime } from "@/types/app";
|
||||
import { useTraceStore } from "@/store/modules/trace";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import ConditionTags from "@/views/components/ConditionTags.vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { EntityType, QueryOrders, Status } from "../../data";
|
||||
import { LayoutConfig } from "@/types/dashboard";
|
||||
|
||||
const FiltersKeys: { [key: string]: string } = {
|
||||
status: "traceState",
|
||||
queryOrder: "queryOrder",
|
||||
duration: "queryDuration",
|
||||
minTraceDuration: "minTraceDuration",
|
||||
maxTraceDuration: "maxTraceDuration",
|
||||
};
|
||||
/*global defineProps, Recordable */
|
||||
const props = defineProps({
|
||||
needQuery: { type: Boolean, default: true },
|
||||
data: {
|
||||
type: Object as PropType<LayoutConfig>,
|
||||
default: () => ({ graph: {} }),
|
||||
},
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const filters = reactive<Recordable>(props.data.filters || {});
|
||||
const appStore = useAppStoreWithOut();
|
||||
const selectorStore = useSelectorStore();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const traceStore = useTraceStore();
|
||||
const tagsList = ref<string[]>([]);
|
||||
const tagsMap = ref<Option[]>([]);
|
||||
const duration = ref<DurationTime>(filters.duration || appStore.durationTime);
|
||||
const state = reactive<Recordable>({
|
||||
instance: "",
|
||||
endpoint: "",
|
||||
service: "",
|
||||
});
|
||||
const conditions = ref<string>("");
|
||||
const items = ref<{ label: string; value: string }[]>([]);
|
||||
const currentLatency = ref<number[]>(
|
||||
filters.latency ? filters.latency[0].data : []
|
||||
);
|
||||
|
||||
init();
|
||||
|
||||
async function init() {
|
||||
for (const d of Object.keys(filters)) {
|
||||
if (filters[d] && ["status", "queryOrder", "latency"].includes(d)) {
|
||||
items.value.push({ label: d, value: FiltersKeys[d] });
|
||||
}
|
||||
}
|
||||
conditions.value = (items.value[0] && items.value[0].label) || "";
|
||||
if (!filters.id) {
|
||||
state.service = selectorStore.currentService.id;
|
||||
if (dashboardStore.entity === EntityType[2].value) {
|
||||
state.instance = selectorStore.currentPod.id;
|
||||
}
|
||||
if (dashboardStore.entity === EntityType[3].value) {
|
||||
state.endpoint = selectorStore.currentPod.id;
|
||||
}
|
||||
await queryTraces();
|
||||
return;
|
||||
}
|
||||
if (dashboardStore.entity === EntityType[1].value) {
|
||||
await getService();
|
||||
}
|
||||
if (dashboardStore.entity === EntityType[0].value) {
|
||||
state.service = selectorStore.currentService.id;
|
||||
await getInstance();
|
||||
if (!state.instance) {
|
||||
await getEndpoint();
|
||||
}
|
||||
}
|
||||
await queryTraces();
|
||||
}
|
||||
function changeCondition() {
|
||||
if (conditions.value === "latency") {
|
||||
currentLatency.value = filters.latency ? filters.latency[0].data : [];
|
||||
}
|
||||
queryTraces();
|
||||
}
|
||||
|
||||
function changeLatency(options: any[]) {
|
||||
currentLatency.value = options[0].data;
|
||||
queryTraces();
|
||||
}
|
||||
|
||||
async function getService() {
|
||||
const resp = await traceStore.getService(filters.id);
|
||||
if (resp.errors) {
|
||||
ElMessage.error(resp.errors);
|
||||
return;
|
||||
}
|
||||
state.service = (resp.data.service && resp.data.service) || "";
|
||||
}
|
||||
async function getEndpoint() {
|
||||
const resp = await traceStore.getEndpoint(filters.id);
|
||||
if (resp.errors) {
|
||||
ElMessage.error(resp.errors);
|
||||
return;
|
||||
}
|
||||
state.endpoint = (resp.data.endpoint && resp.data.endpoint.id) || "";
|
||||
}
|
||||
async function getInstance() {
|
||||
const resp = await traceStore.getInstance(filters.id);
|
||||
if (resp.errors) {
|
||||
ElMessage.error(resp.errors);
|
||||
return;
|
||||
}
|
||||
state.instance = (resp.data.instance && resp.data.instance.id) || "";
|
||||
}
|
||||
function setCondition() {
|
||||
let params: any = {
|
||||
traceState: Status[0].value,
|
||||
queryOrder: QueryOrders[0].value,
|
||||
queryDuration: duration.value,
|
||||
minTraceDuration: isNaN(currentLatency.value[0])
|
||||
? undefined
|
||||
: currentLatency.value[0] === currentLatency.value[1]
|
||||
? currentLatency.value[0] - 10
|
||||
: currentLatency.value[0],
|
||||
maxTraceDuration:
|
||||
isNaN(currentLatency.value[1]) || currentLatency.value[1] === Infinity
|
||||
? undefined
|
||||
: currentLatency.value[1],
|
||||
tags: tagsMap.value.length ? tagsMap.value : undefined,
|
||||
paging: { pageNum: 1, pageSize: 20 },
|
||||
serviceId: state.service || undefined,
|
||||
endpointId: state.endpoint || undefined,
|
||||
serviceInstanceId: state.instance || undefined,
|
||||
};
|
||||
for (const k of items.value) {
|
||||
if (k.label === conditions.value && FiltersKeys[k.label]) {
|
||||
params[k.value] = filters[k.label];
|
||||
}
|
||||
}
|
||||
if (!isNaN(params.minTraceDuration)) {
|
||||
params.queryOrder = QueryOrders[1].value;
|
||||
}
|
||||
return params;
|
||||
}
|
||||
async function queryTraces() {
|
||||
traceStore.setTraceCondition(setCondition());
|
||||
const res = await traceStore.getTraces();
|
||||
if (res && res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
}
|
||||
}
|
||||
function updateTags(data: { tagsMap: Array<Option>; tagsList: string[] }) {
|
||||
tagsList.value = data.tagsList;
|
||||
tagsMap.value = data.tagsMap;
|
||||
}
|
||||
onUnmounted(() => {
|
||||
traceStore.resetState();
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.row {
|
||||
margin-bottom: 5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.conditions {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
padding: 0 5px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.conditions-popup {
|
||||
padding-left: 10px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
63
src/views/dashboard/related/trace/Index.vue
Normal file
63
src/views/dashboard/related/trace/Index.vue
Normal file
@ -0,0 +1,63 @@
|
||||
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License. -->
|
||||
<template>
|
||||
<div class="trace-wrapper flex-v">
|
||||
<div class="header">
|
||||
<Header :data="data" />
|
||||
</div>
|
||||
<div class="trace flex-h">
|
||||
<TraceList />
|
||||
<TraceDetail />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { provide } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import Header from "./Header.vue";
|
||||
import TraceList from "./TraceList.vue";
|
||||
import TraceDetail from "./Detail.vue";
|
||||
|
||||
/*global defineProps */
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({ graph: {} }),
|
||||
},
|
||||
});
|
||||
provide("options", props.data);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.trace-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
min-width: 1200px;
|
||||
}
|
||||
|
||||
.trace {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
min-width: 1200px;
|
||||
}
|
||||
</style>
|
@ -58,8 +58,8 @@ limitations under the License. -->
|
||||
<span class="b">{{ i.endpointNames[0] }}</span>
|
||||
</div>
|
||||
<div class="grey ell sm">
|
||||
<span class="tag mr-10 sm">{{ i.duration }} ms</span
|
||||
>{{ dateFormat(parseInt(i.start, 10)) }}
|
||||
<span class="tag mr-10 sm"> {{ i.duration }} ms </span>
|
||||
{{ dateFormat(parseInt(i.start, 10)) }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
Loading…
Reference in New Issue
Block a user