From 22f245de508f9e0410574da94e73310badebe012 Mon Sep 17 00:00:00 2001 From: HynoR <20227709+HynoR@users.noreply.github.com> Date: Tue, 18 Nov 2025 01:07:17 +0800 Subject: [PATCH 01/12] feat: Add ComposeDetail view for enhanced container management --- .../src/components/log/container/index.vue | 2 +- frontend/src/routers/modules/container.ts | 14 + .../views/container/compose/detail/index.vue | 524 ++++++++++++++++++ .../src/views/container/compose/index.vue | 2 +- 4 files changed, 540 insertions(+), 2 deletions(-) create mode 100644 frontend/src/views/container/compose/detail/index.vue diff --git a/frontend/src/components/log/container/index.vue b/frontend/src/components/log/container/index.vue index dbf59509bfce..e9b9806c53b7 100644 --- a/frontend/src/components/log/container/index.vue +++ b/frontend/src/components/log/container/index.vue @@ -41,7 +41,7 @@ import { cleanContainerLog, DownloadFile } from '@/api/modules/container'; import i18n from '@/lang'; import { dateFormatForName } from '@/utils/util'; -import { onUnmounted, reactive, ref } from 'vue'; +import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'; import { ElMessageBox } from 'element-plus'; import { MsgError, MsgSuccess } from '@/utils/message'; import hightlight from '@/components/log/custom-hightlight/index.vue'; diff --git a/frontend/src/routers/modules/container.ts b/frontend/src/routers/modules/container.ts index 7cdc31eebdee..4dcb0abc3bb4 100644 --- a/frontend/src/routers/modules/container.ts +++ b/frontend/src/routers/modules/container.ts @@ -116,6 +116,20 @@ const containerRouter = { title: 'container.compose', }, }, + { + path: 'compose/detail', + name: 'ComposeDetail', + component: () => import('@/views/container/compose/detail/index.vue'), + props: true, + hidden: true, + meta: { + activeMenu: '/containers', + requiresAuth: false, + parent: 'menu.container', + title: 'container.compose', + ignoreTab: true, + }, + }, { path: 'template', name: 'ComposeTemplate', diff --git a/frontend/src/views/container/compose/detail/index.vue b/frontend/src/views/container/compose/detail/index.vue new file mode 100644 index 000000000000..875b731f61d8 --- /dev/null +++ b/frontend/src/views/container/compose/detail/index.vue @@ -0,0 +1,524 @@ + + + + + diff --git a/frontend/src/views/container/compose/index.vue b/frontend/src/views/container/compose/index.vue index 93b7aaf616de..5beb677c4007 100644 --- a/frontend/src/views/container/compose/index.vue +++ b/frontend/src/views/container/compose/index.vue @@ -155,7 +155,7 @@ const search = async () => { }; const loadDetail = async (row: Container.ComposeInfo) => { - routerToNameWithQuery('ContainerItem', { filters: 'com.docker.compose.project=' + row.name }); + routerToNameWithQuery('ComposeDetail', { name: row.name, path: row.path }); }; const dialogRef = ref(); From 9f7c8d7ea2748c163ee77d56148d4440eb4d27b8 Mon Sep 17 00:00:00 2001 From: HynoR <20227709+HynoR@users.noreply.github.com> Date: Tue, 18 Nov 2025 10:58:23 +0800 Subject: [PATCH 02/12] feat: Update container log management with new table layout and improved stats display --- .../src/components/log/container/index.vue | 4 +- .../views/container/compose/detail/index.vue | 372 ++++++++++-------- 2 files changed, 214 insertions(+), 162 deletions(-) diff --git a/frontend/src/components/log/container/index.vue b/frontend/src/components/log/container/index.vue index e9b9806c53b7..bb9ead67a9cb 100644 --- a/frontend/src/components/log/container/index.vue +++ b/frontend/src/components/log/container/index.vue @@ -82,7 +82,7 @@ let eventSource: EventSource | null = null; const logSearch = reactive({ isWatch: true, container: '', - mode: 'all', + mode: '10m', tail: 100, compose: '', }); @@ -235,7 +235,7 @@ onMounted(() => { logVisible.value = true; logSearch.tail = 100; - logSearch.mode = 'all'; + logSearch.mode = '10m'; logSearch.isWatch = true; nextTick(() => { diff --git a/frontend/src/views/container/compose/detail/index.vue b/frontend/src/views/container/compose/detail/index.vue index 875b731f61d8..54f514ff9d37 100644 --- a/frontend/src/views/container/compose/detail/index.vue +++ b/frontend/src/views/container/compose/detail/index.vue @@ -9,7 +9,7 @@ @@ -181,11 +271,24 @@ import NoSuchService from '@/components/layout-content/no-such-service.vue'; import CodemirrorPro from '@/components/codemirror-pro/index.vue'; import ContainerLog from '@/components/log/container/index.vue'; import ContainerInspectDialog from '@/views/container/container/inspect/index.vue'; -import { composeOperator, composeUpdate, inspect, loadDockerStatus, searchCompose } from '@/api/modules/container'; +import ComplexTable from '@/components/complex-table/index.vue'; +import Status from '@/components/status/index.vue'; +import TerminalDialog from '@/views/container/container/terminal/index.vue'; +import ContainerLogDialog from '@/components/log/container-drawer/index.vue'; +import { + composeOperator, + composeUpdate, + containerListStats, + inspect, + loadDockerStatus, + searchCompose, +} from '@/api/modules/container'; import { Container } from '@/api/interface/container'; import { routerToFileWithPath, routerToName } from '@/utils/router'; import { MsgError, MsgSuccess } from '@/utils/message'; +import { computeCPU, computeSize2, computeSizeForDocker } from '@/utils/util'; import i18n from '@/lang'; +import { Histogram } from '@element-plus/icons-vue'; const route = useRoute(); @@ -204,6 +307,10 @@ const currentOperation = ref(''); const logKey = ref(0); const logHeightDiff = 220; const containerInspectRef = ref(); +const containerStats = ref([]); +const terminalDialogRef = ref(); +const containerLogDialogRef = ref(); +const shouldLoadLog = ref(false); const pageLoading = computed(() => dockerLoading.value || detailLoading.value); const composeTitle = computed(() => { @@ -221,6 +328,26 @@ const disableOperate = computed( () => !composeInfo.value || !composePath.value || !isActive.value || !isExist.value || operateLoading.value, ); +const tableData = computed(() => { + return composeContainers.value.map((container) => { + const stats = containerStats.value.find((s) => s.containerID === container.containerID); + return { + ...container, + hasLoad: !!stats, + cpuPercent: stats?.cpuPercent || 0, + memoryPercent: stats?.memoryPercent || 0, + cpuTotalUsage: stats?.cpuTotalUsage || 0, + systemUsage: stats?.systemUsage || 0, + percpuUsage: stats?.percpuUsage || 0, + memoryCache: stats?.memoryCache || 0, + memoryUsage: stats?.memoryUsage || 0, + memoryLimit: stats?.memoryLimit || 0, + sizeRw: stats?.sizeRw || 0, + sizeRootFs: stats?.sizeRootFs || 0, + }; + }); +}); + const syncRouteParams = () => { composeName.value = (route.query.name as string) || (route.params.name as string) || ''; }; @@ -232,7 +359,7 @@ const loadStatus = async () => { isActive.value = res.data.isActive; isExist.value = res.data.isExist; dockerLoading.value = false; - refreshDetail(); + loadInitialDetail(); }) .catch(() => { dockerLoading.value = false; @@ -241,17 +368,55 @@ const loadStatus = async () => { }); }; -const refreshDetail = async () => { +const loadInitialDetail = async () => { if (!composeName.value || !isActive.value || !isExist.value) { return; } detailLoading.value = true; + shouldLoadLog.value = false; try { - await loadComposeInfo(); + // Step 1: 先加载编排文件内容 await loadComposeContent(); + + // Step 2: 加载编排信息 + await new Promise((resolve) => setTimeout(resolve, 100)); + await loadComposeInfo(); + + // Step 3: 主要内容已加载完成,取消 loading 状态 + detailLoading.value = false; + + // Step 4: 延迟后加载日志 + await new Promise((resolve) => setTimeout(resolve, 100)); + shouldLoadLog.value = true; logKey.value++; - } finally { + + // Step 5: 加载容器统计数据 + await new Promise((resolve) => setTimeout(resolve, 100)); + await loadContainerStats(); + } catch (error) { detailLoading.value = false; + throw error; + } +}; + +const refreshDetail = async () => { + if (!composeName.value || !isActive.value || !isExist.value) { + return; + } + detailLoading.value = true; + try { + // 只刷新容器信息,不重新加载编排文件和日志 + await loadComposeInfo(); + + // 主要内容已加载完成,取消 loading 状态 + detailLoading.value = false; + + // 最后加载容器统计数据(最耗时的操作) + await new Promise((resolve) => setTimeout(resolve, 300)); + await loadContainerStats(); + } catch (error) { + detailLoading.value = false; + throw error; } }; @@ -279,6 +444,15 @@ const loadComposeContent = async () => { composeContent.value = res.data; }; +const loadContainerStats = async () => { + try { + const res = await containerListStats(); + containerStats.value = res.data || []; + } catch (error) { + containerStats.value = []; + } +}; + const onSubmitEdit = async () => { if (!composeInfo.value || !composePath.value || disableEdit.value) { return; @@ -292,8 +466,10 @@ const onSubmitEdit = async () => { }; saving.value = true; await composeUpdate(param) - .then(() => { + .then(async () => { MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); + // 保存后重新加载编排文件内容 + await loadComposeContent(); refreshDetail(); }) .finally(() => { @@ -353,6 +529,14 @@ const onInspectContainer = async (item: any) => { containerInspectRef.value!.acceptParams({ data: res.data, ports: item.ports || [] }); }; +const onOpenTerminal = (row: any) => { + terminalDialogRef.value?.acceptParams({ container: row.name }); +}; + +const onOpenLog = (row: any) => { + containerLogDialogRef.value?.acceptParams({ container: row.name }); +}; + onMounted(() => { syncRouteParams(); loadStatus(); @@ -362,41 +546,19 @@ watch( () => route.fullPath, () => { syncRouteParams(); - refreshDetail(); + loadInitialDetail(); }, ); - -watch([isActive, isExist], () => { - refreshDetail(); -}); From 2a1fe0d31312d851d3a83666f99aa04bb4ed549a Mon Sep 17 00:00:00 2001 From: HynoR <20227709+HynoR@users.noreply.github.com> Date: Tue, 18 Nov 2025 11:01:49 +0800 Subject: [PATCH 03/12] feat: Update log search mode to 'all' for comprehensive log retrieval --- frontend/src/components/log/container/index.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/log/container/index.vue b/frontend/src/components/log/container/index.vue index bb9ead67a9cb..e9b9806c53b7 100644 --- a/frontend/src/components/log/container/index.vue +++ b/frontend/src/components/log/container/index.vue @@ -82,7 +82,7 @@ let eventSource: EventSource | null = null; const logSearch = reactive({ isWatch: true, container: '', - mode: '10m', + mode: 'all', tail: 100, compose: '', }); @@ -235,7 +235,7 @@ onMounted(() => { logVisible.value = true; logSearch.tail = 100; - logSearch.mode = '10m'; + logSearch.mode = 'all'; logSearch.isWatch = true; nextTick(() => { From 7bd9b0c9a9081dc829cdc91c043c587e926dac0a Mon Sep 17 00:00:00 2001 From: HynoR <20227709+HynoR@users.noreply.github.com> Date: Tue, 18 Nov 2025 11:13:45 +0800 Subject: [PATCH 04/12] cleanup --- .../src/views/container/compose/detail/index.vue | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/frontend/src/views/container/compose/detail/index.vue b/frontend/src/views/container/compose/detail/index.vue index 54f514ff9d37..f2b493dfcd24 100644 --- a/frontend/src/views/container/compose/detail/index.vue +++ b/frontend/src/views/container/compose/detail/index.vue @@ -10,7 +10,6 @@