From 5f29e6e696be5a7ef79d93d36f6c939fed6b8d91 Mon Sep 17 00:00:00 2001 From: l429609201 <429609201@qq.com> Date: Sat, 29 Aug 2026 08:49:11 +0800 Subject: [PATCH 01/12] =?UTF-8?q?```=20feat(api):=20=E6=B7=BB=E5=8A=A0=20D?= =?UTF-8?q?ocker=20=E4=B8=BB=E6=9C=BA=E8=AF=A6=E7=BB=86=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 /api/docker/hosts/:id/info 接口,用于获取指定 Docker 主机的详细信息,包括版本、运行时、资源使用情况等。 feat(frontend): 添加 Docker 主机详细信息查看功能 - 在主机列表页面添加信息按钮,点击可查看主机详细信息 - 实现 StructuredLogRow 组件优化日志显示格式 - 新增 HostInfoModal 弹窗组件展示 Docker 信息详情 - 添加 info API 接口调用方法 chore(version): 更新版本号至 v1.0.4 ``` --- frontend-react/src/api/client.js | 2 + .../src/components/ContainerOps.jsx | 63 +++++--- frontend-react/src/components/DockerHosts.jsx | 144 +++++++++++++++++- internal/handler/ops/dockerhosthandler.go | 18 +++ internal/handler/routes.go | 6 + internal/logic/ops/dockerhostlogic.go | 121 +++++++++++++++ version | 2 +- 7 files changed, 332 insertions(+), 24 deletions(-) diff --git a/frontend-react/src/api/client.js b/frontend-react/src/api/client.js index b5415169..594f7a2c 100644 --- a/frontend-react/src/api/client.js +++ b/frontend-react/src/api/client.js @@ -299,6 +299,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/ContainerOps.jsx b/frontend-react/src/components/ContainerOps.jsx index 076c712b..a6632032 100644 --- a/frontend-react/src/components/ContainerOps.jsx +++ b/frontend-react/src/components/ContainerOps.jsx @@ -225,6 +225,44 @@ const LEVEL_COLOR = { debug: 'text-gray-500', } +// 单行结构化日志:时间 | 位置 | 内容 三列对齐 +// 正文默认单行截断避免超长 User-Agent 刷屏,点击整行可展开/收起完整内容 +function StructuredLogRow({ obj, kw }) { + const [expanded, setExpanded] = useState(false) + return ( +
setExpanded((v) => !v)} + className="group flex gap-3 px-3 py-1 border-l-2 border-transparent hover:border-sky-500/60 hover:bg-gray-800/50 cursor-pointer transition-colors" + title={expanded ? '点击收起' : '点击展开完整内容'} + > + {/* 时间列:固定宽度 + 等宽数字,保证纵向对齐 */} + + {formatLogTime(obj.time)} + + {/* 位置列:缩窄并弱化,默认极淡,hover 时才提亮,避免抢占正文视线 */} + + {obj.caller ? highlightLine(obj.caller, kw) : '—'} + + {/* 内容列:默认单行截断,展开后完整换行显示 */} + + {highlightLine(obj.content, kw)} + +
+ ) +} + // 日志面板:支持行数/时间戳、关键词搜索过滤+高亮、日志下载 function LogsPanel({ id, name, hostId }) { const [logs, setLogs] = useState('') @@ -339,31 +377,16 @@ function LogsPanel({ id, name, hostId }) {
匹配 {shownLines.length} 行
)}
+ className="flex-1 min-h-[300px] overflow-auto text-xs font-mono py-1 bg-gray-900 text-gray-100 rounded-lg leading-relaxed"> {loading - ? '加载中...' + ?
加载中...
: (kw && shownLines.length === 0) - ? '(无匹配行)' + ?
(无匹配行)
: rows.map(({ raw, obj }, i) => ( (structured && pretty && obj) ? ( - // 三列布局:时间 | 位置 | 内容,行间用细分隔线区分 -
- - {formatLogTime(obj.time)} - - {/* 无 caller 的行(如标准库 log 输出)用占位符保持三列对齐 */} - - {obj.caller ? highlightLine(obj.caller, kw) : '—'} - - - {highlightLine(obj.content, kw)} - -
+ ) : ( -
{highlightLine(raw, kw)}
+
{highlightLine(raw, kw)}
) ))}
diff --git a/frontend-react/src/components/DockerHosts.jsx b/frontend-react/src/components/DockerHosts.jsx index 319e99ee..d23659e5 100644 --- a/frontend-react/src/components/DockerHosts.jsx +++ b/frontend-react/src/components/DockerHosts.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect, useCallback } from 'react' import { dockerHostAPI } from '../api/client.js' -import { Server, Plus, Trash2, RefreshCw, Wifi, WifiOff, Loader2, Save, X, HardDrive, AlertCircle } from 'lucide-react' +import { Server, Plus, Trash2, RefreshCw, Wifi, WifiOff, Loader2, Save, X, HardDrive, AlertCircle, Info } from 'lucide-react' // 多 Docker 管理页面:第一个恒为本地主机(不可删、地址固定),其余为远程 tcp:// 主机。 export function DockerHosts() { @@ -8,6 +8,7 @@ export function DockerHosts() { const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [editing, setEditing] = useState(null) // 正在编辑/新建的主机对象 + const [detailHost, setDetailHost] = useState(null) // 正在查看详情的主机 const [pingState, setPingState] = useState({}) // { [id]: 'testing'|'ok'|'fail' } const load = useCallback(async () => { @@ -71,7 +72,8 @@ export function DockerHosts() {
{hosts.map((h) => ( setEditing({ ...h })} onDelete={() => remove(h)} onPing={() => testPing(h.id)} /> + onEdit={() => setEditing({ ...h })} onDelete={() => remove(h)} onPing={() => testPing(h.id)} + onInfo={() => setDetailHost(h)} /> ))}
)} @@ -79,6 +81,9 @@ export function DockerHosts() { setEditing(null)} onSaved={async () => { setEditing(null); await load() }} /> )} + {detailHost && ( + setDetailHost(null)} /> + )} ) } @@ -110,7 +115,7 @@ function HostsHeader({ onAdd, onRefresh }) { } // 单个主机行 -function HostRow({ host, pingState, onEdit, onDelete, onPing }) { +function HostRow({ host, pingState, onEdit, onDelete, onPing, onInfo }) { const isLocal = host.local || host.type === 'local' const online = pingState ? pingState === 'ok' : host.online return ( @@ -133,6 +138,10 @@ function HostRow({ host, pingState, onEdit, onDelete, onPing }) { {pingState === 'testing' ? : online ? : } {pingState === 'testing' ? '测试中' : online ? '在线' : '离线'} + + +
+ {loading ? ( +
+ 加载中... +
+ ) : err ? ( +
+ {err} +
+ ) : data ? ( + + ) : null} +
+ + + ) +} + +// 详情正文:按「版本 / 运行时 / 资源 / Registry / 其它」分组展示 +function HostInfoBody({ d }) { + return ( +
+ + + + + {Array.isArray(d.insecureRegistries) && d.insecureRegistries.length > 0 && ( + + )} +
+ ) +} + +// 信息分组:rows 为键值对表格;list 为纯列表(如 Mirrors) +function InfoSection({ title, rows, list, emptyText }) { + return ( +
+

{title}

+ {rows && ( +
+ {rows.filter(([, v]) => v !== undefined && v !== null && v !== '').map(([k, v]) => ( +
+ {k} + {v} +
+ ))} +
+ )} + {list !== undefined && ( +
+ {Array.isArray(list) && list.length > 0 ? ( +
    + {list.map((item, i) => ( +
  • {item}
  • + ))} +
+ ) : ( +

{emptyText || '无'}

+ )} +
+ )} +
+ ) +} + // 主机新建/编辑弹窗。本地主机仅可改名/备注,地址与类型锁定。 function HostEditModal({ host, onClose, onSaved }) { const isLocal = host.local || host.type === 'local' || host.id === 'local' diff --git a/internal/handler/ops/dockerhosthandler.go b/internal/handler/ops/dockerhosthandler.go index faa08cf4..05e69bbb 100644 --- a/internal/handler/ops/dockerhosthandler.go +++ b/internal/handler/ops/dockerhosthandler.go @@ -75,3 +75,21 @@ func DockerHostPingHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { httpx.OkJsonCtx(r.Context(), w, resp) } } + +// DockerHostInfoHandler 返回指定 Docker 主机的详细信息(docker info + version)。 +func DockerHostInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.DockerHostIDReq + if err := httpx.Parse(r, &req); err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + l := ops.NewDockerHostLogic(r.Context(), svcCtx) + resp, err := l.Info(&req) + if err != nil { + httpx.ErrorCtx(r.Context(), w, err) + return + } + httpx.OkJsonCtx(r.Context(), w, resp) + } +} diff --git a/internal/handler/routes.go b/internal/handler/routes.go index a040432c..82cd1555 100644 --- a/internal/handler/routes.go +++ b/internal/handler/routes.go @@ -389,6 +389,12 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/docker/hosts/:id/ping", Handler: ops.DockerHostPingHandler(serverCtx), }, + { + // 获取指定 Docker 主机的详细信息(docker info + version) + Method: http.MethodGet, + Path: "/docker/hosts/:id/info", + Handler: ops.DockerHostInfoHandler(serverCtx), + }, }, rest.WithJwt(serverCtx.Config.Auth.AccessSecret), rest.WithPrefix("/api"), diff --git a/internal/logic/ops/dockerhostlogic.go b/internal/logic/ops/dockerhostlogic.go index 2dcbe6fe..839cf8c2 100644 --- a/internal/logic/ops/dockerhostlogic.go +++ b/internal/logic/ops/dockerhostlogic.go @@ -3,7 +3,10 @@ package ops import ( "context" "strings" + "time" + dockertypes "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/system" "github.com/google/uuid" "github.com/l429609201/dockerCopilot/internal/module/appconfig" "github.com/l429609201/dockerCopilot/internal/svc" @@ -206,3 +209,121 @@ func (l *DockerHostLogic) Ping(req *types.DockerHostIDReq) (*types.Resp, error) } return &types.Resp{Code: 200, Msg: "success", Data: map[string]interface{}{"online": true}}, nil } + +// hostInfoView Docker 主机详细信息对外视图,字段按前端展示分组。 +type hostInfoView struct { + Online bool `json:"online"` + // 版本 + DockerVersion string `json:"dockerVersion"` + APIVersion string `json:"apiVersion"` + GoVersion string `json:"goVersion"` + GitCommit string `json:"gitCommit"` + OsType string `json:"osType"` + Architecture string `json:"architecture"` + KernelVersion string `json:"kernelVersion"` + // 运行时 + OperatingSystem string `json:"operatingSystem"` + Containers int `json:"containers"` + ContainersRunning int `json:"containersRunning"` + ContainersPaused int `json:"containersPaused"` + ContainersStopped int `json:"containersStopped"` + Images int `json:"images"` + ContainerdCommit string `json:"containerdCommit"` + RuncCommit string `json:"runcCommit"` + CgroupVersion string `json:"cgroupVersion"` + CgroupDriver string `json:"cgroupDriver"` + // 资源 + NCPU int `json:"ncpu"` + MemTotal int64 `json:"memTotal"` + DockerRootDir string `json:"dockerRootDir"` + StorageDriver string `json:"storageDriver"` + // Registry(运维页面明文展示镜像加速器) + Mirrors []string `json:"mirrors"` + InsecureRegistries []string `json:"insecureRegistries"` + // 其它 + HostName string `json:"hostName"` + DockerName string `json:"dockerName"` + SwarmActive bool `json:"swarmActive"` + DefaultRuntime string `json:"defaultRuntime"` +} + +// buildHostInfoView 把 docker info + version 组装成前端视图。 +// version 可能为零值(获取失败),此时版本类字段用 info.ServerVersion 兜底。 +func buildHostInfoView(_ appconfig.DockerHost, info system.Info, ver dockertypes.Version) hostInfoView { + v := hostInfoView{ + Online: true, + DockerVersion: info.ServerVersion, + APIVersion: ver.APIVersion, + GoVersion: ver.GoVersion, + GitCommit: ver.GitCommit, + OsType: info.OSType, + Architecture: info.Architecture, + KernelVersion: info.KernelVersion, + OperatingSystem: info.OperatingSystem, + Containers: info.Containers, + ContainersRunning: info.ContainersRunning, + ContainersPaused: info.ContainersPaused, + ContainersStopped: info.ContainersStopped, + Images: info.Images, + ContainerdCommit: info.ContainerdCommit.ID, + RuncCommit: info.RuncCommit.ID, + CgroupVersion: info.CgroupVersion, + CgroupDriver: info.CgroupDriver, + NCPU: info.NCPU, + MemTotal: info.MemTotal, + DockerRootDir: info.DockerRootDir, + StorageDriver: info.Driver, + HostName: info.Name, + DockerName: info.Name, + SwarmActive: info.Swarm.LocalNodeState == "active", + DefaultRuntime: info.DefaultRuntime, + } + // version 成功时优先用其 Version(与 info.ServerVersion 一致,但更权威) + if ver.Version != "" { + v.DockerVersion = ver.Version + } + // 镜像加速器 / 非安全 registry:明文展示(运维页面) + if info.RegistryConfig != nil { + v.Mirrors = info.RegistryConfig.Mirrors + for _, cidr := range info.RegistryConfig.InsecureRegistryCIDRs { + if cidr != nil { + v.InsecureRegistries = append(v.InsecureRegistries, cidr.String()) + } + } + } + return v +} + +// Info 返回指定主机的 Docker 详细信息(docker info + docker version 组合)。 +// 供多 Docker 页面「详情」弹窗展示版本、运行时、资源、镜像加速器等。 +// 主机离线或无连接时返回可读的错误原因,不视为接口失败(前端据此提示"无法连接")。 +func (l *DockerHostLogic) Info(req *types.DockerHostIDReq) (*types.Resp, error) { + host, ok := l.svcCtx.AppConfig.FindDockerHost(req.ID) + if !ok { + return &types.Resp{Code: 400, Msg: "主机不存在", Data: map[string]interface{}{}}, nil + } + cli, ok := l.svcCtx.DockerManager.GetClient(req.ID) + if !ok || cli == nil { + return &types.Resp{Code: 200, Msg: "success", Data: map[string]interface{}{ + "online": false, "reason": "主机无可用连接(未启用或连接失败)", + }}, nil + } + + ctx, cancel := context.WithTimeout(l.ctx, 8*time.Second) + defer cancel() + + info, err := cli.Info(ctx) + if err != nil { + return &types.Resp{Code: 200, Msg: "success", Data: map[string]interface{}{ + "online": false, "reason": "获取信息失败:" + err.Error(), + }}, nil + } + // version 获取失败不阻断:info 里也有 ServerVersion 兜底 + ver, verErr := cli.ServerVersion(ctx) + if verErr != nil { + logx.Errorf("获取主机[%s]版本失败: %v", req.ID, verErr) + } + + detail := buildHostInfoView(host, info, ver) + return &types.Resp{Code: 200, Msg: "success", Data: detail}, nil +} diff --git a/version b/version index e946d6bb..3e7bcf08 100644 --- a/version +++ b/version @@ -1 +1 @@ -v1.0.3 +v1.0.4 From 5e03cc2f414e9636fc5243b01a7e62b883a79f9c Mon Sep 17 00:00:00 2001 From: l429609201 <429609201@qq.com> Date: Sat, 29 Aug 2026 08:56:49 +0800 Subject: [PATCH 02/12] =?UTF-8?q?```=20feat(container-ops):=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=97=A5=E5=BF=97=E9=9D=A2=E6=9D=BFUI=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E5=92=8C=E4=BA=A4=E4=BA=92=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 调整StructuredLogRow组件的布局样式,使用items-baseline对齐方式, 增加内边距提升点击区域可访问性 - 优化位置列显示逻辑,改为左对齐且移除右对齐样式,调整宽度为44字符 - 更新LogsPanel组件的内边距样式,统一使用px-4 py-2间距 - 调整非结构化日志行的内边距,从px-3 py-0.5改为px-4 py-1保持一致 - 优化加载状态和无匹配行提示的内边距样式 ``` --- frontend-react/src/components/ContainerOps.jsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend-react/src/components/ContainerOps.jsx b/frontend-react/src/components/ContainerOps.jsx index a6632032..22ef1c06 100644 --- a/frontend-react/src/components/ContainerOps.jsx +++ b/frontend-react/src/components/ContainerOps.jsx @@ -232,17 +232,17 @@ function StructuredLogRow({ obj, kw }) { return (
setExpanded((v) => !v)} - className="group flex gap-3 px-3 py-1 border-l-2 border-transparent hover:border-sky-500/60 hover:bg-gray-800/50 cursor-pointer transition-colors" + className="group flex items-baseline gap-x-4 px-4 py-1.5 border-l-2 border-transparent hover:border-sky-500/60 hover:bg-gray-800/50 cursor-pointer transition-colors" title={expanded ? '点击收起' : '点击展开完整内容'} > {/* 时间列:固定宽度 + 等宽数字,保证纵向对齐 */} {formatLogTime(obj.time)} - {/* 位置列:缩窄并弱化,默认极淡,hover 时才提亮,避免抢占正文视线 */} + {/* 位置列:左对齐(尾部溢出才省略)+ 弱化配色,默认极淡、hover 提亮,避免抢占正文视线 */} 匹配 {shownLines.length} 行
)}
+ className="flex-1 min-h-[300px] overflow-auto text-xs font-mono py-2 bg-gray-900 text-gray-100 rounded-lg leading-relaxed"> {loading - ?
加载中...
+ ?
加载中...
: (kw && shownLines.length === 0) - ?
(无匹配行)
+ ?
(无匹配行)
: rows.map(({ raw, obj }, i) => ( (structured && pretty && obj) ? ( ) : ( -
{highlightLine(raw, kw)}
+
{highlightLine(raw, kw)}
) ))}
From cb73e3b9647f0dc4b885908761fefe56e36961ea Mon Sep 17 00:00:00 2001 From: l429609201 <429609201@qq.com> Date: Sat, 29 Aug 2026 09:02:40 +0800 Subject: [PATCH 03/12] =?UTF-8?q?```=20fix(ContainerListRow):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=B0=8F=E5=B1=8F=E4=B8=8B=E6=8B=89=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E8=A2=AB=E8=A3=81=E5=89=AA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除行级 overflow-hidden 样式以避免裁剪绝对定位的下拉菜单, 添加 menuOpen 时的 z-index 提升确保菜单浮于相邻列表行之上, 调整遮罩和菜单的层级顺序保证正确的显示优先级。 ``` --- frontend-react/src/components/ContainerListRow.jsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) 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 浮于遮罩之上,为最上层 */} +
{ onOpen(container); setMenuOpen(false) }} icon={Info} text="详情" />
{running ? ( From 9f1485a730d841529fb27ed8f9b6cfd834686090 Mon Sep 17 00:00:00 2001 From: l429609201 <429609201@qq.com> Date: Sat, 29 Aug 2026 09:45:21 +0800 Subject: [PATCH 04/12] =?UTF-8?q?```=20feat(checkupdate):=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E9=95=9C=E5=83=8F=E6=B8=85=E5=8D=95=E6=8B=89=E5=8F=96?= =?UTF-8?q?=E8=B6=85=E6=97=B6=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 直连 Docker Hub 网络较慢、多架构镜像 manifest 拉取偏慢时,20s 易误超时, 故将超时时间从 20s 放宽至 60s,减少 context deadline exceeded 类误报。 ``` --- internal/module/checkupdate.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/module/checkupdate.go b/internal/module/checkupdate.go index a819bf0a..490fbe09 100644 --- a/internal/module/checkupdate.go +++ b/internal/module/checkupdate.go @@ -82,7 +82,9 @@ const ( // 上限避免大量镜像时打爆 registry 速率限制或本地连接数。 checkConcurrency = 8 // digestHTTPTimeout 单次 manifest HEAD 请求超时。 - digestHTTPTimeout = 20 * time.Second + // 直连 Docker Hub 网络较慢、多架构镜像 manifest 拉取偏慢时,20s 易误超时, + // 故放宽至 60s,减少 context deadline exceeded 类误报。 + digestHTTPTimeout = 60 * time.Second ) func NewImageCheck() *ImageUpdateData { From 30de9e09068ddaf689a972623a7df30f4f2a9210 Mon Sep 17 00:00:00 2001 From: l429609201 <429609201@qq.com> Date: Sat, 29 Aug 2026 10:03:02 +0800 Subject: [PATCH 05/12] =?UTF-8?q?```=20feat(ContainerOps):=20=E5=AE=BD?= =?UTF-8?q?=E5=AE=B9=E5=99=A8=E6=97=A5=E5=BF=97=E5=92=8C=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=8F=B0=E7=AA=97=E5=8F=A3=E4=BB=A5=E6=94=B9=E5=96=84=E7=AD=89?= =?UTF-8?q?=E5=AE=BD=E5=86=85=E5=AE=B9=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 日志和终端输出包含等宽内容(如HTTP行、长UA字符串、三列布局), 原max-w-3xl(768px)宽度过窄导致大量内容截断贴边。 调整为max-w-6xl(1152px)后: - 宽屏下提供充足横向空间展示完整内容 - 窄屏下仍通过w-full实现自适应布局 - 保持了最大高度限制以确保可用性 ``` --- frontend-react/src/components/ContainerOps.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend-react/src/components/ContainerOps.jsx b/frontend-react/src/components/ContainerOps.jsx index 22ef1c06..8e1e5d9d 100644 --- a/frontend-react/src/components/ContainerOps.jsx +++ b/frontend-react/src/components/ContainerOps.jsx @@ -59,7 +59,9 @@ export function ContainerLogs({ container, onClose }) {
+ // 日志是等宽宽内容(HTTP 行含长 UA + 三列布局),max-w-3xl(768px) 过窄导致大量截断贴边, + // 放宽到 max-w-6xl(1152px),宽屏下有充足横向空间,窄屏仍由 w-full 自适应。 + : 'w-full max-w-6xl max-h-[90vh] rounded-xl')}>

@@ -91,7 +93,8 @@ export function ContainerConsole({ container, onClose }) {
+ // 终端输出同为等宽宽内容,与日志弹窗保持一致放宽到 max-w-6xl(1152px)。 + : 'w-full max-w-6xl max-h-[90vh] rounded-xl')}>

From ea61df17758c82aa85dd8c3d002ae4407b231e4c Mon Sep 17 00:00:00 2001 From: l429609201 <429609201@qq.com> Date: Sat, 29 Aug 2026 10:18:05 +0800 Subject: [PATCH 06/12] =?UTF-8?q?```=20fix(ContainerOps):=20=E4=B8=BA?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E9=9D=A2=E6=9D=BF=E5=92=8C=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF=E6=B7=BB=E5=8A=A0=E5=86=85=E8=BE=B9=E8=B7=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为 LogsPanel 添加 px-3 sm:px-5 pb-3 sm:pb-5 内边距, 使其与 header 的 p-3 sm:p-5 样式对齐,避免工具栏和深色日志区 直接贴弹窗外框边缘 - 为 ExecPanel 添加 px-3 sm:px-5 pb-3 sm:pb-5 内边距, 使其与 header 的 p-3 sm:p-5 样式对齐,避免配置栏和终端区 贴弹窗外框边缘 ``` --- frontend-react/src/components/ContainerOps.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend-react/src/components/ContainerOps.jsx b/frontend-react/src/components/ContainerOps.jsx index 8e1e5d9d..8cc31876 100644 --- a/frontend-react/src/components/ContainerOps.jsx +++ b/frontend-react/src/components/ContainerOps.jsx @@ -338,7 +338,8 @@ function LogsPanel({ id, name, hostId }) { const kw = search.trim() return ( -
+ // 补横向 + 底部内边距,与 header 的 p-3 sm:p-5 对齐,避免工具栏和深色日志区直接贴弹窗外框边缘。 +