feat: support collapsing and expanding for the event widget (#503)

This commit is contained in:
Fine0830
2025-10-10 15:35:48 +08:00
committed by GitHub
parent 51817f32de
commit 4bf57ec7c5
13 changed files with 133 additions and 40 deletions

View File

@@ -413,5 +413,6 @@ const msg = {
spanName: "Span name", spanName: "Span name",
parentId: "Parent ID", parentId: "Parent ID",
shareTrace: "Share This Trace", shareTrace: "Share This Trace",
eventDefaultCollapse: "Default Collapse",
}; };
export default msg; export default msg;

View File

@@ -413,5 +413,6 @@ const msg = {
spanName: "Nombre de Lapso", spanName: "Nombre de Lapso",
parentId: "ID Padre", parentId: "ID Padre",
shareTrace: "Compartir Traza", shareTrace: "Compartir Traza",
eventDefaultCollapse: "Default Collapse",
}; };
export default msg; export default msg;

View File

@@ -411,5 +411,6 @@ const msg = {
spanName: "跨度名称", spanName: "跨度名称",
parentId: "父ID", parentId: "父ID",
shareTrace: "分享Trace", shareTrace: "分享Trace",
eventDefaultCollapse: "默认折叠",
}; };
export default msg; export default msg;

View File

@@ -253,9 +253,13 @@ export const dashboardStore = defineStore({
setTopology(show: boolean) { setTopology(show: boolean) {
this.showTopology = show; this.showTopology = show;
}, },
setConfigs(param: LayoutConfig) { setLayouts(param: LayoutConfig[]) {
const actived = this.activedGridItem.split("-"); this.layout = param;
},
setConfigs(param: LayoutConfig, gridIndex?: string) {
const actived = gridIndex || this.activedGridItem.split("-");
const index = this.layout.findIndex((d: LayoutConfig) => actived[0] === d.i); const index = this.layout.findIndex((d: LayoutConfig) => actived[0] === d.i);
if (actived.length === 3) { if (actived.length === 3) {
const tabIndex = Number(actived[1]); const tabIndex = Number(actived[1]);
const itemIndex = (this.layout[index].children || [])[tabIndex].children.findIndex( const itemIndex = (this.layout[index].children || [])[tabIndex].children.findIndex(
@@ -270,11 +274,13 @@ export const dashboardStore = defineStore({
this.setCurrentTabItems((this.layout[index].children || [])[tabIndex].children); this.setCurrentTabItems((this.layout[index].children || [])[tabIndex].children);
return; return;
} }
this.layout[index] = { const layout = JSON.parse(JSON.stringify(this.layout));
...this.layout[index], layout[index] = {
...layout[index],
...param, ...param,
}; };
this.selectedGrid = this.layout[index]; this.setLayouts(layout);
this.selectedGrid = layout[index];
}, },
setWidget(param: LayoutConfig) { setWidget(param: LayoutConfig) {
for (let i = 0; i < this.layout.length; i++) { for (let i = 0; i < this.layout.length; i++) {

View File

@@ -36,8 +36,8 @@ export const eventStore = defineStore({
state: (): eventState => ({ state: (): eventState => ({
loading: false, loading: false,
events: [], events: [],
instances: [{ value: "", label: "All" }], instances: [{ value: "0", label: "All" }],
endpoints: [{ value: "", label: "All" }], endpoints: [{ value: "0", label: "All" }],
condition: null, condition: null,
}), }),
actions: { actions: {
@@ -58,7 +58,7 @@ export const eventStore = defineStore({
if (response.errors) { if (response.errors) {
return response; return response;
} }
this.instances = [{ value: "", label: "All" }, ...response.data.pods]; this.instances = [{ value: "0", label: "All" }, ...response.data.pods];
return response; return response;
}, },
async getEndpoints(keyword?: string) { async getEndpoints(keyword?: string) {
@@ -75,7 +75,7 @@ export const eventStore = defineStore({
if (response.errors) { if (response.errors) {
return response; return response;
} }
this.endpoints = [{ value: "", label: "All" }, ...response.data.pods]; this.endpoints = [{ value: "0", label: "All" }, ...response.data.pods];
return response; return response;
}, },
async getEvents() { async getEvents() {

View File

@@ -48,6 +48,7 @@ export interface LayoutConfig {
id?: string; id?: string;
associate?: { widgetId: string }[]; associate?: { widgetId: string }[];
eventAssociate?: boolean; eventAssociate?: boolean;
eventDefaultCollapse?: boolean;
filters?: Filters; filters?: Filters;
relatedTrace?: RelatedTrace; relatedTrace?: RelatedTrace;
subExpressions?: string[]; subExpressions?: string[];

View File

@@ -15,6 +15,10 @@ limitations under the License. -->
<div class="config-label flex-h mr-20">{{ t("enableAssociate") }}</div> <div class="config-label flex-h mr-20">{{ t("enableAssociate") }}</div>
<el-switch v-model="eventAssociate" active-text="Yes" inactive-text="No" @change="updateConfig" /> <el-switch v-model="eventAssociate" active-text="Yes" inactive-text="No" @change="updateConfig" />
</div> </div>
<div class="config-item flex-h">
<div class="config-label flex-h mr-20">{{ t("eventDefaultCollapse") }}</div>
<el-switch v-model="eventDefaultCollapse" active-text="Yes" inactive-text="No" @change="updateConfig" />
</div>
<ConfigurationFooter /> <ConfigurationFooter />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@@ -28,10 +32,19 @@ limitations under the License. -->
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const eventAssociate = ref(dashboardStore.selectedGrid?.eventAssociate || false); const eventAssociate = ref(dashboardStore.selectedGrid?.eventAssociate || false);
const eventDefaultCollapse = ref(
dashboardStore.selectedGrid?.eventDefaultCollapse === undefined
? true
: dashboardStore.selectedGrid?.eventDefaultCollapse,
);
function updateConfig() { function updateConfig() {
const { selectedGrid } = dashboardStore; const { selectedGrid } = dashboardStore;
dashboardStore.selectWidget({ ...selectedGrid, eventAssociate: eventAssociate.value } as LayoutConfig); dashboardStore.selectWidget({
...selectedGrid,
eventAssociate: eventAssociate.value,
eventDefaultCollapse: eventDefaultCollapse.value,
} as LayoutConfig);
} }
</script> </script>

View File

@@ -14,28 +14,37 @@ See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div class="event-wrapper flex-v"> <div class="event-wrapper flex-v">
<el-popover placement="bottom" trigger="click" :width="100" v-if="dashboardStore.editMode"> <div class="operations">
<template #reference> <span class="cp" @click="handleCollapse">
<span class="delete cp"> <Icon iconName="sort" size="middle" />
<Icon iconName="ellipsis_v" size="middle" class="operation" /> </span>
</span> <el-popover placement="bottom" trigger="click" :width="100" v-if="dashboardStore.editMode">
</template> <template #reference>
<div class="tools" @click="editConfig"> <span class="cp">
<span>{{ t("edit") }}</span> <Icon iconName="ellipsis_v" size="middle" />
</div> </span>
<div class="tools" @click="removeWidget"> </template>
<span>{{ t("delete") }}</span> <div class="tools" @click="editConfig">
</div> <span>{{ t("edit") }}</span>
</el-popover> </div>
<div class="header"> <div class="tools" @click="removeWidget">
<Header :needQuery="needQuery" /> <span>{{ t("delete") }}</span>
</div> </div>
<div class="event"> </el-popover>
<Content :data="data" />
</div> </div>
<div class="event-inspector" v-if="collapsedState"> Event Timeline Inspector </div>
<Transition name="collapse" v-else>
<div class="timeline">
<Header :needQuery="needQuery" />
<div class="event mt-10">
<Content :data="data" />
</div>
</div>
</Transition>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch, onMounted, nextTick } from "vue";
import type { PropType } from "vue"; import type { PropType } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
@@ -54,6 +63,17 @@ limitations under the License. -->
}); });
const { t } = useI18n(); const { t } = useI18n();
const dashboardStore = useDashboardStore(); const dashboardStore = useDashboardStore();
const collapsedState = ref(true);
const originalState = ref({ h: props.data.h });
onMounted(() => {
collapsedState.value = props.data.eventDefaultCollapse === undefined ? true : props.data.eventDefaultCollapse;
if (collapsedState.value) {
dashboardStore.setConfigs({ ...props.data, ...{ h: 3 } }, props.data.i);
} else {
dashboardStore.setConfigs({ ...props.data, ...originalState.value }, props.data.i);
}
});
function removeWidget() { function removeWidget() {
dashboardStore.removeControls(props.data); dashboardStore.removeControls(props.data);
@@ -62,6 +82,24 @@ limitations under the License. -->
dashboardStore.setConfigPanel(true); dashboardStore.setConfigPanel(true);
dashboardStore.selectWidget(props.data); dashboardStore.selectWidget(props.data);
} }
function handleCollapse() {
dashboardStore.activeGridItem(props.data.i);
collapsedState.value = !collapsedState.value;
if (collapsedState.value) {
dashboardStore.setConfigs({ ...props.data, ...{ h: 3 } });
} else {
dashboardStore.setConfigs({ ...props.data, ...originalState.value });
}
}
watch(
() => props.data.h,
(newHeight) => {
if (!collapsedState.value) {
originalState.value = { h: newHeight };
}
},
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.event-wrapper { .event-wrapper {
@@ -72,14 +110,14 @@ limitations under the License. -->
overflow: auto; overflow: auto;
} }
.delete { .operations {
position: absolute; position: absolute;
top: 5px; top: 5px;
right: 3px; right: 3px;
z-index: 9999; z-index: 9999;
} }
.header { .timeline {
padding: 10px; padding: 10px;
font-size: $font-size-smaller; font-size: $font-size-smaller;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
@@ -103,4 +141,31 @@ limitations under the License. -->
width: 100%; width: 100%;
height: calc(100% - 80px); height: calc(100% - 80px);
} }
.collapse-enter-active,
.collapse-leave-active {
transition: all 0.8s ease;
overflow: hidden;
}
.collapse-enter-from,
.collapse-leave-to {
opacity: 0;
max-height: 0;
transform: translateY(-10px);
}
.collapse-enter-to,
.collapse-leave-from {
opacity: 1;
max-height: 1000px;
transform: translateY(0);
}
.event-inspector {
text-align: center;
line-height: 45px;
font-size: $font-size-normal;
font-weight: bold;
}
</style> </style>

View File

@@ -95,6 +95,8 @@ limitations under the License. -->
:is-draggable="dashboardStore.editMode" :is-draggable="dashboardStore.editMode"
:is-resizable="dashboardStore.editMode" :is-resizable="dashboardStore.editMode"
@layout-updated="layoutUpdatedEvent" @layout-updated="layoutUpdatedEvent"
:vertical-compact="true"
:auto-size="true"
> >
<grid-item <grid-item
v-for="item in dashboardStore.currentTabItems" v-for="item in dashboardStore.currentTabItems"

View File

@@ -122,10 +122,10 @@ limitations under the License. -->
}; };
const metrics: { [key: string]: { source: { [key: string]: unknown }; typesOfMQE: string[] } } = const metrics: { [key: string]: { source: { [key: string]: unknown }; typesOfMQE: string[] } } =
(await useDashboardQueryProcessor([config])) || {}; (await useDashboardQueryProcessor([config])) || {};
const params = metrics[data.value.i]; const params = metrics[data.value.i] || {};
loading.value = false; loading.value = false;
state.source = params.source || {}; state.source = params.source || {};
typesOfMQE.value = params.typesOfMQE; typesOfMQE.value = params.typesOfMQE || [];
} }
function removeWidget() { function removeWidget() {

View File

@@ -23,6 +23,8 @@ limitations under the License. -->
v-loading.fullscreen.lock="loading" v-loading.fullscreen.lock="loading"
element-loading-text="Loading..." element-loading-text="Loading..."
element-loading-background="rgba(122, 122, 122, 0.8)" element-loading-background="rgba(122, 122, 122, 0.8)"
:vertical-compact="true"
:auto-size="true"
> >
<grid-item <grid-item
v-for="item in dashboardStore.layout" v-for="item in dashboardStore.layout"

View File

@@ -51,6 +51,7 @@ limitations under the License. -->
width: 0, width: 0,
height: 0, height: 0,
}; };
visTimeline();
useThrottleFn(resize, 500)(); useThrottleFn(resize, 500)();
}); });

View File

@@ -86,8 +86,8 @@ limitations under the License. -->
const pageSize = 20; const pageSize = 20;
const pageNum = ref<number>(1); const pageNum = ref<number>(1);
const state = reactive<any>({ const state = reactive<any>({
instance: { value: "", label: "All", id: "" }, instance: { value: "0", label: "All" },
endpoint: { value: "", label: "All", id: "" }, endpoint: { value: "0", label: "All" },
eventType: { value: "", label: "All" }, eventType: { value: "", label: "All" },
}); });
const total = computed(() => const total = computed(() =>
@@ -99,8 +99,8 @@ limitations under the License. -->
async function init() { async function init() {
fetchSelectors(); fetchSelectors();
await queryEvents(); await queryEvents();
state.instance = { value: "", label: "All" }; state.instance = { value: "0", label: "All" };
state.endpoint = { value: "", label: "All" }; state.endpoint = { value: "0", label: "All" };
} }
function fetchSelectors() { function fetchSelectors() {
@@ -138,13 +138,13 @@ limitations under the License. -->
state.instance = eventStore.instances[0]; state.instance = eventStore.instances[0];
} }
async function queryEvents() { async function queryEvents() {
let endpoint = state.endpoint.value, let endpoint = state.endpoint.value === "0" ? undefined : state.endpoint.value,
instance = state.instance.value; instance = state.instance.value === "0" ? undefined : state.instance.value;
if (dashboardStore.entity === EntityType[2].value) { if (dashboardStore.entity === EntityType[2].value) {
endpoint = selectorStore.currentPod && selectorStore.currentPod.id; endpoint = selectorStore.currentPod?.id;
} }
if (dashboardStore.entity === EntityType[3].value) { if (dashboardStore.entity === EntityType[3].value) {
instance = selectorStore.currentPod && selectorStore.currentPod.id; instance = selectorStore.currentPod?.id;
} }
if (!selectorStore.currentService) { if (!selectorStore.currentService) {
return; return;