Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion frontend-react/src/api/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}&timestamps=${timestamps}&since=${encodeURIComponent(since)}${hostQ(hostId, '&')}`),
apiClient.get(`/api/container/${id}/logs?tail=${tail}&timestamps=${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)}`),
Expand Down Expand Up @@ -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 - 用于检查前端更新
Expand Down
14 changes: 9 additions & 5 deletions frontend-react/src/components/ContainerListRow.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)}
>
Expand Down Expand Up @@ -202,10 +206,10 @@ export function ContainerListRow({
</button>
{menuOpen && (
<>
{/* 遮罩:点击关闭菜单 */}
<div className="fixed inset-0 z-10" onClick={() => setMenuOpen(false)} />
{/* 下拉菜单 */}
<div className="absolute right-0 top-full mt-1 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 py-1 z-20 min-w-[140px]">
{/* 遮罩:全屏铺满、点击关闭菜单。z-40 高于被提升的行(z-30),保证任意点击都能命中遮罩 */}
<div className="fixed inset-0 z-40" onClick={(e) => { e.stopPropagation(); setMenuOpen(false) }} />
{/* 下拉菜单:z-50 浮于遮罩之上,为最上层 */}
<div className="absolute right-0 top-full mt-1 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 py-1 z-50 min-w-[140px]">
<MenuItem onClick={() => { onOpen(container); setMenuOpen(false) }} icon={Info} text="详情" />
<div className="h-px bg-gray-200 dark:bg-gray-700 my-1" />
{running ? (
Expand Down
Loading
Loading