feat: opsx-loki 全面升级 — 前端重构/用户与LDAP/部署向导/日志查询

基于 WEB-UI-Development-Guide 标准的整体升级,并更名 LokiDeploy → opsx-loki。

前端(Gin+React+shadcn/ui):
- 按 02/07 章重构:页面域化拆分(无超 400 行文件)、PageContainer/ListQuery、
  反馈分级(成功静默/失败 toast/危险确认)、401 单飞刷新+队列重放
- 暗色模式(next-themes)+ 语义 Token(--primary 品牌色/success/warning/info)
- 部署向导(状态推导四步:环境→存储→Loki→采集,内嵌表单闭环)
- 日志查询页:LogQL 标签构建器/拖拽直方图/日志上下文/Live Tail/查询历史
- 按用户+标签策略的查询授权(服务端强制合并策略)

后端(Go+Gin+JSON DB):
- 简单用户管理(仅 admin,无 RBAC)+ TokenVersion 会话吊销 + 登录 IP 限流
- LDAP 接入:配置(AES 加密存储/hasPassword 脱敏)/测试连接/批量导入/登录
- 存储配置测试连接(SigV4 零依赖)+ 桶不存在自动创建
- Loki 三模式部署修复:SSH 密钥认证、3.x 配置模板、ruler 对象存储嵌套块、
  凭证解密、孤儿 deploying 状态恢复、Grafana 部署(可选默认关)+面板导入
- DaemonSet 采集纯 YAML(去 helm 依赖)+ DNS 自愈回退 ClusterIP + 集群名标签
- 日志查询代理:cluster-svc 经 kube-apiserver proxy/url 直连 + cursor 翻页
- 统一 log 命名空间;部署就绪探活;Grafana 访问地址自动记录

修复:run.sh 增量编译检测、Sanitized 脱敏补全、LokiDeployment Update 白名单、
健康检查 CreatedAt 零值等 10+ 存量 bug。

数据兼容:DB 沿用旧 lokideploy.json(新名优先);localStorage 一次性迁移;
HKDF 加密前缀保留(已加密凭证不受影响)。
This commit is contained in:
logan committed 2026-10-24 13:20:12 +08:00
1 parent 327e282356
commit f9c6c1d826
204 files changed
+14107 -9103

No files matched your search

@@ -0,0 +1,97 @@
import { memo } from 'react'
import { Monitor, RefreshCw, Trash2, Eye, Terminal } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Card, CardContent } from '@/components/ui/card'
import { StatusBadge } from '@/components/StatusBadge'
import { InfoRow } from '@/components/InfoRow'
import type { Host } from '@/types'
interface HostCardProps {
host: Host
validating: number | null
onViewInfo: (h: Host) => void
onOpenTerminal: (h: Host) => void
onValidate: (id: number) => void
onDelete: (h: Host) => void
}
/** 主机卡片 — 列表项展示与操作入口(删除传整个对象,供确认弹窗展示名称) */
export const HostCard = memo(function HostCard({
host,
validating,
onViewInfo,
onOpenTerminal,
onValidate,
onDelete,
}: HostCardProps) {
return (
<Card className="border-border hover:shadow-md transition-shadow">
<CardContent className="p-5">
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-2 min-w-0">
<Monitor className="w-5 h-5 text-purple-500 flex-shrink-0" />
<h3 className="font-semibold text-foreground truncate">
{host.name}
</h3>
</div>
<StatusBadge status={host.status} />
</div>
<div className="space-y-1.5 text-xs text-muted-foreground mb-4">
<InfoRow label="IP 地址" value={`${host.hostIP}:${host.port}`} />
<InfoRow label="用户名" value={host.user} />
<InfoRow
label="认证方式"
value={host.authType === 'key' ? 'SSH 密钥' : '密码'}
/>
<InfoRow
label="系统"
value={`${host.os || '未知'} (${host.arch || '未知'})`}
/>
</div>
<div className="flex gap-2 pt-3 border-t border-border">
<Button
variant="outline"
size="sm"
className="flex-1 text-xs"
onClick={() => onViewInfo(host)}
>
<Eye className="w-3 h-3 mr-1" />
详情
</Button>
<Button
variant="outline"
size="sm"
className="flex-1 text-xs"
onClick={() => onOpenTerminal(host)}
>
<Terminal className="w-3 h-3 mr-1" />
终端
</Button>
<Button
variant="outline"
size="sm"
aria-label={`验证主机 ${host.name}`}
className="text-xs"
onClick={() => onValidate(host.id)}
disabled={validating === host.id}
>
<RefreshCw
className={`w-3 h-3 ${validating === host.id ? 'animate-spin' : ''}`}
/>
</Button>
<Button
variant="outline"
size="sm"
aria-label={`删除主机 ${host.name}`}
className="text-destructive hover:text-destructive hover:bg-destructive/10"
onClick={() => onDelete(host)}
>
<Trash2 className="w-3 h-3" />
</Button>
</div>
</CardContent>
</Card>
)
})