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,231 @@
# 02 — 项目架构与目录结构
> 参考:vue-vben-admin(三层包划分)、soybean-admin(文件路由 + store 纯函数分离)、ant-design-pro(页面 co-location)、vue-pure-admin(页面内聚五件套);补充:gin-vue-admin(分域 api/view)
---
## 一、组织目录的三种流派
### 1.1 流派对照
| 流派 | 代表 | 结构 | 优点 | 代价 |
|------|------|------|------|------|
| **按层分**(layer-first) | 早期中台 | `components/ views/ api/ stores/` 每层内平铺全部业务 | 上手简单 | 业务散在 5 个目录,删模块要全局找 |
| **按域分**(domain-first) | **本标准(Vue 单包)/ gva** | 层目录保留,但 `api/user/`、`views/user/`、`stores/` 域内聚合 | 删域=删目录,心智清晰 | 需要约定维持层内域目录一致 |
| **按功能分包**(feature-first) | adp 页面级、vben 包级 | 页面目录自带 service/mock/类型/样式;框架能力独立成包 | 高内聚,可整体搬移 | 需要晋升机制防止复制泛滥 |
**结论:中小工程用「按域分 + 页面 co-location」混合式**——全局按域(api/views 域目录对齐),页面私有代码留在页面目录内。
### 1.2 四标杆的目录精髓
```
vue-vben-admin(依赖方向三层,分层标杆)
apps/* ──▶ packages/effects/* ──▶ packages/@core/*
(app 是唯一允许直接依赖具体 UI 库的层;
@core 零 UI 依赖零业务;effects 通过 adapter 适配 UI 生态)
ant-design-pro(页面五件套 co-location)
src/pages/table-list/
├── index.tsx # 页面(ProTable 声明式列配置)
├── service.ts # 本页接口(手写层)
├── data.d.ts # 本页类型
├── _mock.ts # 本页 mock(config 显式 include)
└── components/
├── CreateForm.tsx # 新建弹窗(trigger 模式)
└── UpdateForm.tsx # 编辑弹窗(StepsForm 进 Modal)
soybean-admin(生成物 + store 纯函数分离,★★★★☆)
src/
├── router/elegant/ # 文件路由生成物(routes/transform/imports + RouteKey 类型 d.ts)
├── service/{api,request}/ # api=纯请求函数、request=实例拦截器,两层分离
├── store/modules/<mod>/ # 每模块 index.ts(状态编排) + shared.ts(可单测纯函数)
├── typings/{api,...} # 全局 namespace 类型(Api/App/Storage/Env)
└── views/_builtin/ # 403/404/500/login 内置页统一收纳,业务页平级
vue-pure-admin(页面内聚五件套,★★★★☆)
src/views/system/user/
├── index.vue # 纯模板(零逻辑,只解构 hook)
├── form/index.vue # 弹窗表单
├── tree.vue # 左树
└── utils/{hook.tsx,rule.ts,types.ts} # 逻辑/校验/类型全在本域
gin-vue-admin(补充:全局分域 + 指令/钩子独立)
src/
├── api/ # 一域一文件:user.js menu.js ...
├── pinia/modules/ # 一域一 store
├── view/superAdmin/ view/example/ # 按业务分组
├── directive/auth.js # v-auth 指令独立目录
├── hooks/responsive.js # 组合式函数独立目录
└── plugin/<name>/ # 业务插件 = 自带 view/api/assets 的小应用
```
---
## 二、标准目录结构(Vue 工程)
### 2.1 目标结构
```
src/
├── main.js # 入口:仅装配(EP按需、pinia、router、指令、全局组件)
├── App.vue
├── api/ # ★ 按功能域分目录,与 views 域目录同名
│ ├── auth/ # captcha.js mfa.js
│ ├── user/ # users.js roles.js permissions.js
│ ├── rbac/ # 域小可单文件,大了再拆
│ ├── system/ # menu.js modules.js logs.js ...
│ └── <domain>/ # 业务域
├── views/ # ★ 域目录与 api/ 一一对应
│ ├── user/ # Users.vue + components/XxxFormDialog.vue ...
│ ├── <domain>/
│ └── _builtin/ # 403/404/500/login 内置页统一收纳(soybean 范式)
├── stores/ # Pinia:一域一文件(auth.js menu.js site.js ...)
├── composables/ # useXxx(依赖业务上下文的钩子)
├── components/ # ★ 全局组件(需晋升,见第四章)
├── layout/ # 布局骨架(业务页只挂内容区,禁止重写布局)
├── router/ # index.js(含守卫) modules/(路由分文件)
├── config/ # theme.js modules.js(纯配置,无副作用导入)
├── typings/ # 全局契约类型(TS 渐进路线,soybean 四区)
├── utils/ # request.js permission.js(无状态工具)
└── styles/ # 全部文件必须有入口引用链
```
React 工程同构映射:`views→pages`、`composables→hooks`、`stores→store`(zustand slices)、`components→components`(含 barrel 出口)。
### 2.2 层内规则(必须)
1. **api 域目录与 views 域目录强制同名**(`api/cron/` ↔ `views/cron/`);**禁止**在 api 根级别放散文件
2. **store 文件名 = 导出名**(`stores/auth.js` → `useAuthStore`);单 store ≤300 行;推荐 soybean 的「index + shared」目录式(状态编排与纯函数分离)
3. **layout/ 是禁改区**:业务页通过页面容器组件的 header/slot 定制,不碰布局骨架(可裁剪底座的前提)
4. **components/ 只放「晋升组件」**(见第四章晋升机制),新页面私有组件一律 `views/<域>/components/`
### 2.3 可裁剪分层与模块开关(底座/脚手架推荐)
```
┌─────────────────────────────────────────────────────┐
│ 壳层 shell 登录/MFA/403/500 —— 不挂主布局 │
├─────────────────────────────────────────────────────┤
│ 系统层 system 用户/RBAC/审计/设置 —— 底座自带 │
├─────────────────────────────────────────────────────┤
│ 示例层 examples 演示模块 —— fork 后可整层删除 │
└─────────────────────────────────────────────────────┘
```
- 路由 `meta.layer: 'shell' | 'system' | 'examples'` 声明归属
- 运行时开关:**源真相在后端**(如 `GET/PUT /api/system/modules`),localStorage 仅作缓存——**禁止**把 localStorage 当源真相
- 构建期裁剪:环境变量(如 `VITE_ENABLE_EXAMPLES=false`)+ 路由中字面量比较,保证打包器 tree-shake 掉示例 chunk
- 示例层代码**不得 import 系统层**内聚单元,保证整层可摘除;示例域附删除指引文档(EXAMPLES.md 惯例)
**分层是目录结构之外的第二根轴**:每个新模块必须先回答「我在哪一层」。
### 2.4 命名规范与类型目录
| 对象 | 规范 | 示例 |
|------|------|------|
| 组件文件 | PascalCase | `UserFormDialog.vue` |
| 工具 / composable | camelCase,composable 带 use 前缀 | `request.js`、`usePagedQuery.js` |
| 目录 | kebab-case(views 域目录用业务域名) | `user-management/`、`cron/` |
| store 文件 | 文件名与导出同名 | `stores/auth.js` → `useAuthStore` |
| API 文件 | 资源名复数 | `api/user/users.js` |
| 常量 | UPPER_SNAKE_CASE | `SYSTEM_ORDER` |
类型目录(TS 渐进路线,soybean 范式):全局契约类型集中 `typings/`(Api / App / Storage / Env 四区 namespace,storage 与 env 均键级类型);页面私有类型随页面(`data.d.ts` / `utils/types.ts`);**禁止**同一类型定义在多个组件内重复出现。
---
## 三、页面 co-location(adp 五件套)
### 3.1 什么时候页面自带 service
```
全局 api/<域>/xxx.js # 多页面复用、域级语义(users.js 的 list/create/delete)
页面内 service.ts # 只服务本页的接口(dashboard 聚合、报表导出)
```
判定:**接口第二个消费者出现时**,从页面 `service.ts` 提升到 `api/<域>/`。反向不成立——不要预防性提升。
### 3.2 页面目录模板
```
views/<domain>/<Page>.vue # 页面骨架:容器 + 列表组件 + 子组件编排(≤400 行)
views/<domain>/components/ # 本页私有组件(表单弹窗、详情抽屉、单元格渲染器)
views/<domain>/composables/ # 本页私有逻辑(可选,超过 2 个页面组件共享时建)
views/<domain>/utils/ # 本页 hook/rule/types(pure 范式,可选)
views/<domain>/EXAMPLES.md # 仅示例层:删除指引
```
---
## 四、公共组件晋升机制
```
第 1 次使用:写在页面 components/ 里
第 2 次使用:复制一份,页面内注明来源 ← 允许的重复
第 3 次使用:晋升 src/components/,删除两份复制 ← 必须执行
```
晋升时同步做三件事:
1. **去业务化**:props 收口(`fetcher`/`permissions` 进来,具体 API 调用出去)
2. **注册出口**:Vue 端全局组件在 `main.js` 注册;React 端进 `components/index.ts` barrel(adp:按「布局组件 / 业务组件」分组导出)
3. **写使用说明**:组件头部注释 3 行(用途/必需 props/已知限制)
必备的两个全局页面组件(详见 05/07 章):**PageContainer**(页面容器:页头/面包屑/slot)与 **ListPage**(分页列表:fetcher + 筛选 slot + 表格透传 + 分页)。
---
## 五、架构守护:让目录约定不被腐蚀
| 守护手段 | 出处 | 可落地做法 |
|----------|------|------------|
| 循环依赖扫描 | vben `check:circular` | `npx madge --circular src/` 进 CI |
| 依赖方向检查 | vben `check:dep`(@core 禁依赖 UI 库) | 示例层禁 import 系统层:ESLint `no-restricted-imports` 按路径分组 |
| 死代码检测 | adp coverage 排除生成物 | CI 跑 `knip`/`depcheck`,styles 无入口文件直接报错 |
| 生成物隔离 | adp `services/ant-design-pro/`(OpenAPI 生成,禁手改) | 生成物独立目录 + lint ignore |
| 路由自动收集 | pure `router/modules/` + `import.meta.glob` | 新增路由文件零注册代码 |
---
## 六、最佳实践清单
✅ **要做**
1. api 域目录与 views 域目录同名;新域从脚手架起步(自动生成五处对照:菜单/路由/页面/API/权限码)
2. 页面私有组件不出页面目录;晋升走「三次法则」
3. 每个新模块先声明 `meta.layer`,示例层保持可整层摘除
4. store 按域拆分、文件名=导出名、单 store ≤300 行
5. 目录结构变化(新增域/晋升组件/删示例)在同一 PR 内更新架构文档
❌ **不要**
1. 不要在 api 根级别放散文件
2. 不要把页面私有组件直接丢进 `src/components/`
3. 不要让业务页 import layout 内部组件(只准用 slot 协议)
4. 不要建「future/」「temp/」「new/」这类无语义目录
5. 不要保留无入口引用的样式文件和空目录
---
## 七、AI 检查清单(对比目标工程逐项核对)
**P0**
1. api 与 views 域目录是否同名对齐?有无 api 根级散文件?
2. store 文件名与导出名是否一致?有无超 300 行的 store?
3. 有无空目录、无引用样式、描述已不存在目录的文档残留?
**P1**
4. 页面骨架是否 ≤400 行?超 400 行的视图列出拆分建议(参照 07 章)
5. 私有组件是否越界进全局 components/?全局组件是否都有注册出口与头注释?
6. 若为可裁剪底座:meta.layer 是否全覆盖?模块开关源真相是否在后端?
7. CI 是否有循环依赖与死代码检查?
**P2**
8. 是否值得引入路由文件自动收集(`router/modules/` + glob)或文件路由(soybean @elegant-router)?
9. `typings/` 全局类型目录是否建立(Api/App/Storage/Env 四区)?
---
> 下一篇:[03-请求层与数据流](03-请求层与数据流.md) —— 前端工程的血管系统。