+
+
+
@@ -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 @@
-
@@ -69,7 +68,6 @@
-
@@ -235,7 +233,6 @@
-
@@ -375,22 +372,17 @@ const loadInitialDetail = async () => {
detailLoading.value = true;
shouldLoadLog.value = false;
try {
- // 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++;
- // Step 5: 加载容器统计数据
await new Promise((resolve) => setTimeout(resolve, 100));
await loadContainerStats();
} catch (error) {
@@ -405,13 +397,10 @@ const refreshDetail = async () => {
}
detailLoading.value = true;
try {
- // 只刷新容器信息,不重新加载编排文件和日志
await loadComposeInfo();
- // 主要内容已加载完成,取消 loading 状态
detailLoading.value = false;
- // 最后加载容器统计数据(最耗时的操作)
await new Promise((resolve) => setTimeout(resolve, 300));
await loadContainerStats();
} catch (error) {
@@ -468,7 +457,6 @@ const onSubmitEdit = async () => {
await composeUpdate(param)
.then(async () => {
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
- // 保存后重新加载编排文件内容
await loadComposeContent();
refreshDetail();
})
From 912a9ad6b41253c64ff4497cf45dc2e7cacf64b1 Mon Sep 17 00:00:00 2001
From: HynoR <20227709+HynoR@users.noreply.github.com>
Date: Tue, 18 Nov 2025 19:42:36 +0800
Subject: [PATCH 05/12] feat: Enhance log container and compose management with
new UI components and improved functionality
---
.../src/components/log/container/index.vue | 14 +-
.../views/container/compose/detail/index.vue | 564 --------------
.../src/views/container/compose/index.vue | 732 +++++++++++++-----
3 files changed, 562 insertions(+), 748 deletions(-)
delete 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 e9b9806c53b7..325f2fc6984c 100644
--- a/frontend/src/components/log/container/index.vue
+++ b/frontend/src/components/log/container/index.vue
@@ -1,5 +1,5 @@
-
+
{{ $t('container.fetch') }}
@@ -69,6 +69,14 @@ const props = defineProps({
type: String,
default: '',
},
+ showControl: {
+ type: Boolean,
+ default: true,
+ },
+ defaultFollow: {
+ type: Boolean,
+ default: false,
+ },
});
const styleVars = computed(() => ({
@@ -80,10 +88,10 @@ const logContainer = ref(null);
const logs = ref([]);
let eventSource: EventSource | null = null;
const logSearch = reactive({
- isWatch: true,
+ isWatch: props.defaultFollow ? true : true,
container: '',
mode: 'all',
- tail: 100,
+ tail: props.defaultFollow ? 0 : 100,
compose: '',
});
const logHeight = 20;
diff --git a/frontend/src/views/container/compose/detail/index.vue b/frontend/src/views/container/compose/detail/index.vue
deleted file mode 100644
index f2b493dfcd24..000000000000
--- a/frontend/src/views/container/compose/detail/index.vue
+++ /dev/null
@@ -1,564 +0,0 @@
-
-
-
- {{ $t('container.serviceUnavailable') }}
- 【 {{ $t('container.setting') }} 】
- {{ $t('container.startIn') }}
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('commons.operate.start') }}
-
-
-
- {{ $t('commons.operate.stop') }}
-
-
-
- {{ $t('commons.operate.restart') }}
-
-
-
- {{ $t('commons.button.refresh') }}
-
-
-
- {{ $t('container.composeDirectory') }}
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('container.containerStatus') }}
- ( {{ composeInfo?.containerCount || 0 }} / {{ composeInfo?.runningCount || 0 }} )
-
-
-
-
-
-
-
- {{ row.name }}
-
-
-
-
-
-
-
-
-
-
-
-
-
CPU: {{ row.cpuPercent.toFixed(2) }}%
-
- {{ $t('monitor.memory') }}: {{ row.memoryPercent.toFixed(2) }}%
-
-
-
-
-
-
-
-
-
-
-
- {{ computeCPU(row.cpuTotalUsage) }}
-
-
- {{ computeCPU(row.systemUsage) }}
-
-
- {{ row.percpuUsage }}
-
-
-
- {{ computeSizeForDocker(row.memoryUsage) }}
-
-
- {{ computeSizeForDocker(row.memoryCache) }}
-
-
- {{ computeSizeForDocker(row.memoryLimit) }}
-
-
-
-
- {{ $t('container.sizeRw') }}
-
-
-
-
- {{ computeSize2(row.sizeRw) }}
-
-
-
- {{ $t('container.sizeRootFs') }}
-
-
-
-
- {{ computeSize2(row.sizeRootFs) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('menu.terminal') }}
-
-
-
- {{ $t('commons.button.log') }}
-
-
-
-
-
-
-
-
-
-
- {{ $t('container.composeTemplate') }}
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('container.editComposeHelper') }}
-
-
-
-
-
-
-
- {{ $t('commons.button.save') }}
-
-
-
-
-
-
-
- {{ $t('commons.button.log') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/frontend/src/views/container/compose/index.vue b/frontend/src/views/container/compose/index.vue
index 5beb677c4007..eeef7516d053 100644
--- a/frontend/src/views/container/compose/index.vue
+++ b/frontend/src/views/container/compose/index.vue
@@ -13,124 +13,440 @@
{{ $t('container.createCompose') }}
-
-
-
-
-
-
-
-
-
- {{ row.name }}
-
-
-
-
-
- {{ $t('commons.table.local') }}
- {{ $t('menu.apps') }}
- 1Panel
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('container.exited') }}
-
-
-
-
- {{ $t('container.running', [row.runningCount, row.containerCount]) }}
-
+
+
+
+
+
+
+
+
+
+
+
+
{{ item.name }}
+
+
+ {{ $t('container.exited') }}
+
+
+ {{ $t('container.running', [item.runningCount, item.containerCount]) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('app.source') }}:
+
+ {{ $t('commons.table.local') }}
+
+
+ {{ $t('menu.apps') }}
+
+
+ 1Panel
+
+
+
+ {{ $t('commons.table.createdAt') }}: {{ composeInfo?.createdAt }}
+
+
+
+
+
+
+ {{ $t('commons.operate.start') }}
+
+
+ {{ $t('commons.operate.stop') }}
+
+
+ {{ $t('commons.operate.restart') }}
+
+
+
+ {{ $t('commons.button.refresh') }}
+
+
+
+ {{ $t('container.composeDirectory') }}
+
+
+
+ {{ $t('commons.button.delete') }}
+
+
+
+
+
+
+
+
+ {{ $t('container.containerStatus') }}
+ ({{ composeInfo?.runningCount || 0 }}/{{ composeInfo?.containerCount || 0 }})
+
-
-
{{ item.name }}
-
+
+
+
+
+ {{ row.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
CPU: {{ row.cpuPercent.toFixed(2) }}%
+
+ {{ $t('monitor.memory') }}: {{ row.memoryPercent.toFixed(2) }}%
+
+
+
+
+
+
+
+
+
+
+
+ {{ computeCPU(row.cpuTotalUsage) }}
+
+
+ {{ computeCPU(row.systemUsage) }}
+
+
+ {{ row.percpuUsage }}
+
+
+ {{ computeSizeForDocker(row.memoryUsage) }}
+
+
+ {{ computeSizeForDocker(row.memoryCache) }}
+
+
+ {{ computeSizeForDocker(row.memoryLimit) }}
+
+
+
+ {{ $t('container.sizeRw') }}
+
+
+
+
+
+
+ {{ computeSize2(row.sizeRw) }}
+
+
+
+ {{ $t('container.sizeRootFs') }}
+
+
+
+
+
+
+ {{ computeSize2(row.sizeRootFs) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('menu.terminal') }}
+
+
+
+ {{ $t('commons.button.log') }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('container.compose') }}
+
+ {{ $t('commons.button.save') }}
+
+
+
+
+
+
+
+
+
+
+
+ .env
+
+ {{ $t('commons.button.save') }}
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+ {{ $t('commons.button.log') }}
+
+ {{ $t('commons.button.view') }}
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
From b7cb1b02eec601dc2aeb1e14f18572e2950a8c98 Mon Sep 17 00:00:00 2001
From: HynoR <20227709+HynoR@users.noreply.github.com>
Date: Tue, 18 Nov 2025 23:23:54 +0800
Subject: [PATCH 06/12] refactor: Remove unused ComposeDetail route and enhance
UI components in container management
---
frontend/src/routers/modules/container.ts | 14 --
.../src/views/container/compose/index.vue | 132 +++++++++---------
2 files changed, 66 insertions(+), 80 deletions(-)
diff --git a/frontend/src/routers/modules/container.ts b/frontend/src/routers/modules/container.ts
index 4dcb0abc3bb4..7cdc31eebdee 100644
--- a/frontend/src/routers/modules/container.ts
+++ b/frontend/src/routers/modules/container.ts
@@ -116,20 +116,6 @@ 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/index.vue b/frontend/src/views/container/compose/index.vue
index eeef7516d053..701f626c73c5 100644
--- a/frontend/src/views/container/compose/index.vue
+++ b/frontend/src/views/container/compose/index.vue
@@ -63,90 +63,77 @@
-
-
-
- {{ $t('app.source') }}:
-
- {{ $t('commons.table.local') }}
-
-
- {{ $t('menu.apps') }}
-
-
- 1Panel
-
-
-
- {{ $t('commons.table.createdAt') }}: {{ composeInfo?.createdAt }}
-
+
+
+
{{ composeInfo?.name }}
+
+
+ {{ $t('app.source') }}:
+
+ {{ $t('commons.table.local') }}
+
+
+ {{ $t('menu.apps') }}
+
+
+ 1Panel
+
+
+ {{ $t('commons.table.createdAt') }}: {{ composeInfo?.createdAt }}
+
+
-
-
-
+
+
+
+
{{ $t('commons.operate.start') }}
-
- {{ $t('commons.operate.stop') }}
-
+
{{ $t('commons.operate.restart') }}
-
- {{ $t('commons.button.refresh') }}
-
-
-
- {{ $t('container.composeDirectory') }}
-
-
-
- {{ $t('commons.button.delete') }}
+
+ {{ $t('commons.operate.stop') }}
-
+
+
+
+ {{ $t('commons.button.refresh') }}
+
+
+
+ {{ $t('container.composeDirectory') }}
+
+
+
+ {{ $t('commons.button.delete') }}
+
@@ -381,7 +368,16 @@ import { Container } from '@/api/interface/container';
import { routerToFileWithPath } from '@/utils/router';
import { MsgError, MsgSuccess } from '@/utils/message';
import { computeCPU, computeSize2, computeSizeForDocker } from '@/utils/util';
-import { Histogram, Search } from '@element-plus/icons-vue';
+import {
+ Delete,
+ Folder,
+ Histogram,
+ Refresh,
+ RefreshRight,
+ Search,
+ VideoPause,
+ VideoPlay,
+} from '@element-plus/icons-vue';
const data = ref
([]);
const loading = ref(false);
@@ -625,7 +621,11 @@ const onInspectContainer = async (item: any) => {
};
const onOpenTerminal = (row: any) => {
- terminalDialogRef.value?.acceptParams({ container: row.name });
+ if (!row.containerID) {
+ return;
+ }
+ const title = i18n.global.t('menu.container') + ' ' + row.name;
+ terminalDialogRef.value?.acceptParams({ containerID: row.containerID, title });
};
const onOpenLog = (row: any) => {
From 491de5ccc921003d3aa45a1d0693679a2720cbff Mon Sep 17 00:00:00 2001
From: HynoR <20227709+HynoR@users.noreply.github.com>
Date: Tue, 18 Nov 2025 23:45:54 +0800
Subject: [PATCH 07/12] refactor: Improve UI styling and interaction for
compose list items in container management
---
.../src/views/container/compose/index.vue | 44 ++++++++++++++-----
1 file changed, 34 insertions(+), 10 deletions(-)
diff --git a/frontend/src/views/container/compose/index.vue b/frontend/src/views/container/compose/index.vue
index 701f626c73c5..838a07e517a7 100644
--- a/frontend/src/views/container/compose/index.vue
+++ b/frontend/src/views/container/compose/index.vue
@@ -34,10 +34,11 @@
v-for="item in data"
:key="item.name"
class="compose-list-item p-3 mb-2 rounded cursor-pointer border"
- :class="{
- 'border-primary bg-blue-50': selectedCompose?.name === item.name,
- 'border-transparent hover:bg-gray-50': selectedCompose?.name !== item.name,
- }"
+ :class="[
+ selectedCompose?.name === item.name
+ ? 'border-primary compose-list-item--selected'
+ : 'border-transparent compose-list-item--default',
+ ]"
@click="loadDetail(item)"
>
@@ -100,7 +101,7 @@
:disabled="disableOperate"
@click="handleComposeOperate('up')"
>
-
+
{{ $t('commons.operate.start') }}
-
+
{{ $t('commons.operate.restart') }}
-
+
{{ $t('commons.operate.stop') }}
-
+
{{ $t('commons.button.refresh') }}
-
+
{{ $t('container.composeDirectory') }}
-
+
{{ $t('commons.button.delete') }}
@@ -641,3 +642,26 @@ const openComposeLogDrawer = () => {
});
};
+
+
From 31e2fdbbc32ed77b0e32fefbe594bdb7872dafb0 Mon Sep 17 00:00:00 2001
From: HynoR <20227709+HynoR@users.noreply.github.com>
Date: Tue, 18 Nov 2025 23:56:00 +0800
Subject: [PATCH 08/12] refactor: Update styling and hover effects for compose
list items in container management
---
.../src/views/container/compose/index.vue | 29 ++-----------------
1 file changed, 3 insertions(+), 26 deletions(-)
diff --git a/frontend/src/views/container/compose/index.vue b/frontend/src/views/container/compose/index.vue
index 838a07e517a7..7d6a35f3be12 100644
--- a/frontend/src/views/container/compose/index.vue
+++ b/frontend/src/views/container/compose/index.vue
@@ -33,11 +33,11 @@
@@ -642,26 +642,3 @@ const openComposeLogDrawer = () => {
});
};
-
-
From dcd17fd1422a06022b37ccf8f5579878982b0f12 Mon Sep 17 00:00:00 2001
From: HynoR <20227709+HynoR@users.noreply.github.com>
Date: Wed, 19 Nov 2025 08:41:21 +0800
Subject: [PATCH 09/12] refactor: Enhance container compose header with
additional context and improve port handling in inspect view
---
frontend/src/views/container/compose/index.vue | 4 +++-
frontend/src/views/container/container/inspect/index.vue | 4 ++++
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/frontend/src/views/container/compose/index.vue b/frontend/src/views/container/compose/index.vue
index 7d6a35f3be12..cffe01adf92d 100644
--- a/frontend/src/views/container/compose/index.vue
+++ b/frontend/src/views/container/compose/index.vue
@@ -66,7 +66,9 @@
-
{{ composeInfo?.name }}
+
+ {{ composeInfo?.name }} - {{ $t('container.compose') }}
+
{{ $t('app.source') }}:
diff --git a/frontend/src/views/container/container/inspect/index.vue b/frontend/src/views/container/container/inspect/index.vue
index 4da5765b2501..b0b62be13a77 100644
--- a/frontend/src/views/container/container/inspect/index.vue
+++ b/frontend/src/views/container/container/inspect/index.vue
@@ -192,6 +192,10 @@ const acceptParams = (props: DialogProps): void => {
inspectData.value = props.data;
}
rawJson.value = JSON.stringify(inspectData.value, null, 2);
+
+ if (!ports.value.length) {
+ ports.value = Object.keys(inspectData.value?.Config?.ExposedPorts || {});
+ }
} catch (e) {
console.error('Failed to parse inspect data:', e);
}
From d67334d1b4138167fa4186a58f2572d06b04722e Mon Sep 17 00:00:00 2001
From: HynoR <20227709+HynoR@users.noreply.github.com>
Date: Wed, 19 Nov 2025 08:51:43 +0800
Subject: [PATCH 10/12] refactor: Update .env label to use translation for
improved localization in container compose view
---
frontend/src/views/container/compose/index.vue | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frontend/src/views/container/compose/index.vue b/frontend/src/views/container/compose/index.vue
index cffe01adf92d..888e400aa9a9 100644
--- a/frontend/src/views/container/compose/index.vue
+++ b/frontend/src/views/container/compose/index.vue
@@ -293,7 +293,7 @@
- .env
+ {{ $t('container.env') }}
-
+
Date: Wed, 19 Nov 2025 09:02:08 +0800
Subject: [PATCH 11/12] refactor: Adjust height and placeholder text for YAML
editor in container compose view
---
frontend/src/views/container/compose/index.vue | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frontend/src/views/container/compose/index.vue b/frontend/src/views/container/compose/index.vue
index 888e400aa9a9..3ddd31c75b77 100644
--- a/frontend/src/views/container/compose/index.vue
+++ b/frontend/src/views/container/compose/index.vue
@@ -284,8 +284,8 @@
v-model="composeContent"
mode="yaml"
:disabled="disableEdit"
- :heightDiff="100"
- placeholder="#Define or paste the content of your docker-compose file here"
+ :heightDiff="190"
+ placeholder="docker-compose.yml"
/>
From 480ce669cd79d88bc6d01d420a8f43eb7746e54c Mon Sep 17 00:00:00 2001
From: ssongliu
Date: Fri, 21 Nov 2025 18:10:54 +0800
Subject: [PATCH 12/12] refactor: Adjust docker compose layout
---
.../src/views/container/compose/index.vue | 743 +++++++-----------
1 file changed, 287 insertions(+), 456 deletions(-)
diff --git a/frontend/src/views/container/compose/index.vue b/frontend/src/views/container/compose/index.vue
index 3ddd31c75b77..aebefbedbc66 100644
--- a/frontend/src/views/container/compose/index.vue
+++ b/frontend/src/views/container/compose/index.vue
@@ -4,7 +4,7 @@
v-model:isActive="isActive"
v-model:isExist="isExist"
v-model:loading="loading"
- @search="search"
+ @search="search(true)"
/>
@@ -14,343 +14,254 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
{{ item.name }}
-
-
- {{ $t('container.exited') }}
-
-
- {{ $t('container.running', [item.runningCount, item.containerCount]) }}
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
- {{ composeInfo?.name }} - {{ $t('container.compose') }}
-
-
-
-
{{ $t('app.source') }}:
-
- {{ $t('commons.table.local') }}
-
-
- {{ $t('menu.apps') }}
+
+
+
+
+
+ {{ row.name }}
+
+
+ {{ $t('container.exited') }}
- 1Panel
+ {{
+ $t('container.running', [row.runningCount, row.containerCount])
+ }}
- {{ $t('commons.table.createdAt') }}: {{ composeInfo?.createdAt }}
-
-
-
-
-
-
-
-
- {{ $t('commons.operate.start') }}
-
-
-
- {{ $t('commons.operate.restart') }}
-
-
-
- {{ $t('commons.operate.stop') }}
-
-
-
-
- {{ $t('commons.button.refresh') }}
-
-
-
- {{ $t('container.composeDirectory') }}
-
-
-
- {{ $t('commons.button.delete') }}
-
-
-
-
-
-
-
- {{ $t('container.containerStatus') }}
- ({{ composeInfo?.runningCount || 0 }}/{{ composeInfo?.containerCount || 0 }})
-
-
-
-
-
-
- {{ row.name }}
-
-
-
-
-
-
-
-
-
-
-
-
-
CPU: {{ row.cpuPercent.toFixed(2) }}%
-
- {{ $t('monitor.memory') }}: {{ row.memoryPercent.toFixed(2) }}%
-
-
-
-
-
-
-
-
-
-
-
- {{ computeCPU(row.cpuTotalUsage) }}
-
-
- {{ computeCPU(row.systemUsage) }}
-
-
- {{ row.percpuUsage }}
-
-
- {{ computeSizeForDocker(row.memoryUsage) }}
-
-
- {{ computeSizeForDocker(row.memoryCache) }}
-
-
- {{ computeSizeForDocker(row.memoryLimit) }}
-
-
-
- {{ $t('container.sizeRw') }}
-
-
-
-
-
-
- {{ computeSize2(row.sizeRw) }}
-
-
-
- {{ $t('container.sizeRootFs') }}
-
-
-
-
-
-
- {{ computeSize2(row.sizeRootFs) }}
-
-
-
-
+
-
-
+
+ {{ loadFrom(row) }}
+
+ {{ row.createdAt }}
+
-
-
-
-
-
- {{ $t('menu.terminal') }}
+
+ {{ $t('commons.operate.start') }}
-
-
- {{ $t('commons.button.log') }}
+
+ {{ $t('commons.operate.stop') }}
+
+
+ {{ $t('commons.operate.restart') }}
+
+
+ {{ $t('commons.operate.delete') }}
-
-
-
-
-
-
-
-
-
-
- {{ $t('container.compose') }}
-
- {{ $t('commons.button.save') }}
-
-
-
-
-
-
-
-
-
-
-
{{ $t('container.env') }}
-
- {{ $t('commons.button.save') }}
-
+
+
+
+
+
+
+
+
+
+
+
+ {{ row.name }}
+
+
+
+
+
+
+
+
+
+
+
+
CPU: {{ row.cpuPercent.toFixed(2) }}%
+
+ {{ $t('monitor.memory') }}: {{ row.memoryPercent.toFixed(2) }}%
-
-
-
-
-
-
+
+
+
+
+
+
+
+ {{ computeCPU(row.cpuTotalUsage) }}
+
+
+ {{ computeCPU(row.systemUsage) }}
+
+
+ {{ row.percpuUsage }}
+
-
-
-
- {{ $t('commons.button.log') }}
-
- {{ $t('commons.button.view') }}
-
+
+ {{ computeSizeForDocker(row.memoryUsage) }}
+
+
+ {{ computeSizeForDocker(row.memoryCache) }}
+
+
+ {{ computeSizeForDocker(row.memoryLimit) }}
+
+
+
+
+ {{ $t('container.sizeRw') }}
+
+
+
+
+ {{ computeSize2(row.sizeRw) }}
+
+
+
+ {{ $t('container.sizeRootFs') }}
+
+
+
+
+ {{ computeSize2(row.sizeRootFs) }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('menu.terminal') }}
+
+
+ {{ $t('commons.button.log') }}
+
-
-
+
+
+
+ {{ $t('container.compose') }}
+ {{ $t('commons.button.log') }}
+
+
+
+
+
+
-
-
-
+
+
+ {{ $t('container.editComposeHelper') }}
+
+
+
+
+ {{ $t('commons.button.save') }}
+
+
+
+
+
-
-
-
+
+
+
+
-
-
+
+
-
@@ -363,56 +274,37 @@ import TerminalDialog from '@/views/container/container/terminal/index.vue';
import ContainerLogDialog from '@/components/log/container-drawer/index.vue';
import CreateDialog from '@/views/container/compose/create/index.vue';
import DeleteDialog from '@/views/container/compose/delete/index.vue';
-import ComposeLogs from '@/components/log/compose/index.vue';
import { composeOperator, composeUpdate, containerListStats, inspect, searchCompose } from '@/api/modules/container';
import DockerStatus from '@/views/container/docker-status/index.vue';
import i18n from '@/lang';
import { Container } from '@/api/interface/container';
import { routerToFileWithPath } from '@/utils/router';
-import { MsgError, MsgSuccess } from '@/utils/message';
+import { MsgSuccess } from '@/utils/message';
import { computeCPU, computeSize2, computeSizeForDocker } from '@/utils/util';
-import {
- Delete,
- Folder,
- Histogram,
- Refresh,
- RefreshRight,
- Search,
- VideoPause,
- VideoPlay,
-} from '@element-plus/icons-vue';
+import { Search } from '@element-plus/icons-vue';
const data = ref([]);
const loading = ref(false);
-const selectedCompose = ref(null);
const detailLoading = ref(false);
-const operateLoading = ref(false);
-const currentOperation = ref('');
-const saving = ref(false);
-const composeName = ref('');
+const currentCompose = ref(null);
+const composeContainers = ref([]);
const composeContent = ref('');
const envStr = ref('');
-const composeInfo = ref();
-const containerStats = ref([]);
-const logKey = ref(0);
-const shouldLoadLog = ref(false);
+const env = ref('env_file:\n - 1panel.env');
+
+const dialogCreateRef = ref();
+const dialogDelRef = ref();
const containerInspectRef = ref();
const terminalDialogRef = ref();
const containerLogDialogRef = ref();
-const composeLogRef = ref();
+
const searchName = ref('');
+const showType = ref('compose');
+const containerStats = ref([]);
const isActive = ref(false);
const isExist = ref(false);
-const composePath = computed(() => composeInfo.value?.path || selectedCompose.value?.path || '');
-const composeContainers = computed(() => composeInfo.value?.containers || []);
-const disableEdit = computed(() => composeInfo.value?.createdBy === 'Local');
-const showEnvSetting = computed(() => composeInfo.value?.createdBy === '1Panel');
-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);
@@ -433,22 +325,18 @@ const tableData = computed(() => {
});
});
-const closeDetail = () => {
- selectedCompose.value = null;
- composeName.value = '';
- composeInfo.value = undefined;
- composeContent.value = '';
- envStr.value = '';
- shouldLoadLog.value = false;
-};
-
-const openComposeFolder = () => {
- if (composeInfo.value?.workdir) {
- routerToFileWithPath(composeInfo.value.workdir);
+const loadFrom = (row: any) => {
+ switch (row.createdBy) {
+ case '1Panel':
+ return '1Panel';
+ case 'Apps':
+ return i18n.global.t('menu.apps');
+ default:
+ return i18n.global.t('commons.table.local');
}
};
-const search = async () => {
+const search = async (withRefreshDetail?: boolean) => {
if (!isActive.value || !isExist.value) {
return;
}
@@ -462,59 +350,27 @@ const search = async () => {
.then((res) => {
loading.value = false;
data.value = res.data.items || [];
+ if (data.value.length > 0 && withRefreshDetail) {
+ loadDetail(data.value[0], true);
+ }
})
.finally(() => {
loading.value = false;
});
};
-const loadDetail = async (row: Container.ComposeInfo) => {
- if (selectedCompose.value?.name === row.name) {
- closeDetail();
+const loadDetail = async (row: Container.ComposeInfo, withRefresh: boolean) => {
+ if (currentCompose.value?.name === row.name && withRefresh !== true) {
return;
}
- selectedCompose.value = row;
- composeName.value = row.name;
detailLoading.value = true;
- shouldLoadLog.value = false;
- try {
- await loadComposeContent();
- await new Promise((resolve) => setTimeout(resolve, 100));
- await loadComposeInfo();
- detailLoading.value = false;
- await new Promise((resolve) => setTimeout(resolve, 100));
- shouldLoadLog.value = true;
- logKey.value++;
- await new Promise((resolve) => setTimeout(resolve, 100));
- await loadContainerStats();
- } catch (error) {
+ currentCompose.value = row;
+ composeContainers.value = row.containers || [];
+ await inspect({ id: currentCompose.value.name, type: 'compose' }).then((res) => {
+ composeContent.value = res.data;
detailLoading.value = false;
- throw error;
- }
-};
-
-const loadComposeInfo = async () => {
- const params = {
- info: composeName.value,
- page: 1,
- pageSize: 1,
- };
- const res = await searchCompose(params);
- const items = res.data?.items || [];
- const target = items.find((item) => item.name === composeName.value) || items[0];
- if (!target) {
- composeInfo.value = undefined;
- envStr.value = '';
- MsgError(i18n.global.t('commons.msg.noneData'));
- return;
- }
- composeInfo.value = target;
- envStr.value = (target.env || []).join('\n');
-};
-
-const loadComposeContent = async () => {
- const res = await inspect({ id: composeName.value, type: 'compose' });
- composeContent.value = res.data;
+ });
+ loadContainerStats();
};
const loadContainerStats = async () => {
@@ -526,43 +382,20 @@ const loadContainerStats = async () => {
}
};
-const refreshDetail = async () => {
- if (!composeName.value || !isActive.value || !isExist.value) {
- return;
- }
- detailLoading.value = true;
- try {
- await loadComposeInfo();
- detailLoading.value = false;
- await new Promise((resolve) => setTimeout(resolve, 300));
- await loadContainerStats();
- } catch (error) {
- detailLoading.value = false;
- throw error;
- }
-};
-
-const dialogRef = ref();
const onOpenDialog = async () => {
- dialogRef.value!.acceptParams();
+ dialogCreateRef.value!.acceptParams();
};
-const onDeleteCompose = () => {
- if (!selectedCompose.value) return;
+const onDelete = (row: any) => {
dialogDelRef.value.acceptParams({
- name: selectedCompose.value.name,
- path: selectedCompose.value.path,
+ name: row.name,
+ path: row.path,
});
};
-const dialogDelRef = ref();
-
-const handleComposeOperate = async (operation: 'up' | 'stop' | 'restart') => {
- if (!composeInfo.value || !composePath.value) {
- return;
- }
+const handleComposeOperate = async (operation: 'up' | 'stop' | 'restart', row: any) => {
const mes = i18n.global.t('container.composeOperatorHelper', [
- composeInfo.value.name,
+ row.name,
i18n.global.t('commons.operate.' + operation),
]);
ElMessageBox.confirm(mes, i18n.global.t('commons.operate.' + operation), {
@@ -570,11 +403,10 @@ const handleComposeOperate = async (operation: 'up' | 'stop' | 'restart') => {
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
}).then(async () => {
- currentOperation.value = operation;
- operateLoading.value = true;
+ loading.value = true;
const params = {
- name: composeInfo.value!.name,
- path: composePath.value,
+ name: row.name,
+ path: currentCompose.value.path,
operation: operation,
withFile: false,
force: false,
@@ -582,39 +414,41 @@ const handleComposeOperate = async (operation: 'up' | 'stop' | 'restart') => {
await composeOperator(params)
.then(() => {
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
- refreshDetail();
search();
+ if (row.name === currentCompose.value?.name) {
+ loadDetail(currentCompose.value, true);
+ }
})
.finally(() => {
- operateLoading.value = false;
- currentOperation.value = '';
+ loading.value = false;
});
});
};
const onSubmitEdit = async () => {
- if (!composeInfo.value || !composePath.value || disableEdit.value) {
- return;
- }
const param = {
- name: composeName.value,
- path: composePath.value,
+ name: currentCompose.value.name,
+ path: currentCompose.value.path,
content: composeContent.value,
- createdBy: composeInfo.value.createdBy,
+ createdBy: currentCompose.value.createdBy,
env: envStr.value ? envStr.value.split('\n') : [],
};
- saving.value = true;
+ loading.value = true;
await composeUpdate(param)
.then(async () => {
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
- await loadComposeContent();
- refreshDetail();
+ await loadDetail(currentCompose.value, true);
})
.finally(() => {
- saving.value = false;
+ loading.value = false;
});
};
+const openComposeFolder = () => {
+ if (currentCompose.value?.workdir) {
+ routerToFileWithPath(currentCompose.value.workdir);
+ }
+};
const onInspectContainer = async (item: any) => {
if (!item.containerID) {
return;
@@ -622,7 +456,6 @@ const onInspectContainer = async (item: any) => {
const res = await inspect({ id: item.containerID, type: 'container' });
containerInspectRef.value!.acceptParams({ data: res.data, ports: item.ports || [] });
};
-
const onOpenTerminal = (row: any) => {
if (!row.containerID) {
return;
@@ -630,17 +463,15 @@ const onOpenTerminal = (row: any) => {
const title = i18n.global.t('menu.container') + ' ' + row.name;
terminalDialogRef.value?.acceptParams({ containerID: row.containerID, title });
};
-
const onOpenLog = (row: any) => {
containerLogDialogRef.value?.acceptParams({ container: row.name });
};
-
-const openComposeLogDrawer = () => {
- if (!composePath.value || !composeName.value) return;
- composeLogRef.value?.acceptParams({
- compose: composePath.value,
- resource: composeName.value,
- container: '',
- });
-};
+
+