diff --git a/frontend-react/src/api/client.js b/frontend-react/src/api/client.js
index b5415169..4a1dda2a 100644
--- a/frontend-react/src/api/client.js
+++ b/frontend-react/src/api/client.js
@@ -141,8 +141,30 @@ export const containerAPI = {
removeContainer: (id, force = false, removeVolumes = false, hostId) =>
apiClient.delete(`/api/container/${id}?force=${force}&removeVolumes=${removeVolumes}${hostQ(hostId, '&')}`),
inspectContainer: (id, hostId) => apiClient.get(`/api/container/${id}/inspect${hostQ(hostId)}`),
+ // 日志请求单独放宽超时到 60s:日志量大时后端从日志文件末尾回扫定位 tail 行 + 解复用流较慢,
+ // 全局 10s 会在“读取头部”阶段就超时。此处按请求覆盖 timeout,不影响其他快接口。
+ // 说明:此一次性接口现仅用于「下载完整日志」,页面查看已改走下方 SSE 流式接口。
getContainerLogs: (id, { tail = 200, timestamps = false, since = '' } = {}, hostId) =>
- apiClient.get(`/api/container/${id}/logs?tail=${tail}×tamps=${timestamps}&since=${encodeURIComponent(since)}${hostQ(hostId, '&')}`),
+ apiClient.get(`/api/container/${id}/logs?tail=${tail}×tamps=${timestamps}&since=${encodeURIComponent(since)}${hostQ(hostId, '&')}`, { timeout: 60000 }),
+ // 构造 SSE 流式日志的完整 URL(EventSource 无法带 Authorization 头,token 走 query)。
+ // 边读边推:首行秒级到达;search 交给后端 grep,follow 为实时跟随(-f)。
+ buildLogsStreamURL: (id, { tail = 200, timestamps = false, since = '', follow = false, search = '' } = {}, hostId) => {
+ const token = localStorage.getItem('docker_copilot_token') || ''
+ const base =
+ (typeof window !== 'undefined' && window.__API_BASE_URL) ||
+ localStorage.getItem('api_base_url') ||
+ (typeof window !== 'undefined' ? `${window.location.protocol}//${window.location.host}` : '')
+ const p = new URLSearchParams({
+ tail: String(tail),
+ timestamps: String(timestamps),
+ follow: String(follow),
+ token,
+ })
+ if (since) p.set('since', since)
+ if (search) p.set('search', search)
+ if (hostId) p.set('hostId', hostId)
+ return `${base}/api/container/${id}/logs/stream?${p.toString()}`
+ },
execContainer: (id, cmd, workDir = '', user = '', hostId) =>
apiClient.post(`/api/container/${id}/exec`, { cmd, workDir, user, hostId }),
topContainer: (id, hostId) => apiClient.get(`/api/container/${id}/top${hostQ(hostId)}`),
@@ -299,6 +321,8 @@ export const dockerHostAPI = {
remove: (id) => apiClient.delete(`/api/docker/hosts/${id}`),
// 测试指定主机连通性
ping: (id) => apiClient.post(`/api/docker/hosts/${id}/ping`),
+ // 获取指定主机的 Docker 详细信息(docker info + version)
+ info: (id) => apiClient.get(`/api/docker/hosts/${id}/info`),
}
// GitHub API - 用于检查前端更新
diff --git a/frontend-react/src/components/ContainerListRow.jsx b/frontend-react/src/components/ContainerListRow.jsx
index 27e32158..db6312ab 100644
--- a/frontend-react/src/components/ContainerListRow.jsx
+++ b/frontend-react/src/components/ContainerListRow.jsx
@@ -32,7 +32,11 @@ export function ContainerListRow({
}
}}
className={cn(
- "relative overflow-hidden flex items-center gap-3 px-3 py-2.5 bg-white dark:bg-gray-800 border rounded-xl cursor-pointer transition-all hover:shadow-sm",
+ // 注意:这里不能加 overflow-hidden,否则会裁剪小屏三点下拉菜单(absolute top-full 向下溢出行边界)。
+ // 整行更新进度条的圆角裁剪由其自身容器(下方 rounded-xl overflow-hidden)负责,无需行级 overflow。
+ // menuOpen 时提升行层级(z-30),确保展开的菜单浮于相邻列表行之上。
+ "relative flex items-center gap-3 px-3 py-2.5 bg-white dark:bg-gray-800 border rounded-xl cursor-pointer transition-all hover:shadow-sm",
+ menuOpen && "z-30",
selected ? "border-primary-400 dark:border-primary-600 ring-1 ring-primary-300" : "border-gray-200 dark:border-gray-700"
)}
>
@@ -202,10 +206,10 @@ export function ContainerListRow({
{menuOpen && (
<>
- {/* 遮罩:点击关闭菜单 */}
-
setMenuOpen(false)} />
- {/* 下拉菜单 */}
-
+ {/* 遮罩:全屏铺满、点击关闭菜单。z-40 高于被提升的行(z-30),保证任意点击都能命中遮罩 */}
+
{ e.stopPropagation(); setMenuOpen(false) }} />
+ {/* 下拉菜单:z-50 浮于遮罩之上,为最上层 */}
+