feat(ldap): ad/openldap dual compat and config gating

- multi-round user filters (config filter first, then
  objectClass=inetOrgPerson, then AD sAMAccountName/userPrincipalName
  fallback) applied to login search, user listing and batch import;
  previously pure-AD directories found no users
- attribute fallback chains (uid/sAMAccountName/userPrincipalName,
  cn/displayName, telephoneNumber/mobile) via ldapEntryAttr
- import errors explain likely cause instead of empty result
- AES-encrypted admin password kept (aiops stores plaintext)
- frontend gating per aiops pattern: LDAP account management panel
  hidden with hint until a config is actually saved (was gated on
  unsaved form values); batch-import modal hides to/from-LDAP tabs
  when unconfigured, local CSV always available
This commit is contained in:
Logan.Li committed 2026-10-26 20:08:46 +08:00
1 parent 2c96902fbd
commit 13fb3aceb3
6 files changed
+487 -266

No files matched your search

@@ -1,67 +1,21 @@
# OpsxSQL 查询体验与工单流程优化(借鉴 opsxdb / opsx-sec)
# LDAP:借鉴 aiops 的 AD 兼容与配置门控 + 连不上根因说明
**用户已确认**:① 移除工单创建页死的「定时执行」字段 ② 全部四块都做。
**不做**(明确出界):多 tab 页签系统、草稿自动保存、批量审批、Redis Hash 值级 HSCAN 分页——后续迭代。
研究结论:LDAP 参数本身没问题(本机对 8.217.205.92:389 实测连接/导入/登录全通,aiops 种子配置与 opsxsql 同源)。「连不了」的根因在**你正在跑的旧后端实例**:① 其 sqlite 只读(实测报 `attempt to write a readonly database`,LDAP 配置根本存不进去);② 旧二进制还有 CORS 白名单 bug(非 5173 端口的前端所有写请求空 403)。需重启到新构建后端+可写 DB。本次按 aiops 补两块真差距:
---
## 1. 后端借鉴 aiops(controller/ldap.go)——AD/OpenLDAP 双兼容
opsxsql 只支持 OpenLDAP(objectClass=inetOrgPerson + uid),AD 域(sAMAccountName)会搜不到。移植 aiops 的:
- **多轮过滤器**(ldapUserFilter/ldapADUserFilter/ldapUserListFilters):配置过滤器优先 → `(objectClass=inetOrgPerson)` → AD `(&(objectClass=user)(|(sAMAccountName=*)...))` 兜底;登录时 OpenLDAP 过滤器 + AD 过滤器两轮尝试
- **属性回退链**(ldapEntryAttr):uid→sAMAccountName→userPrincipalName、cn→displayName、mail、telephoneNumber→mobile,映射解析按回退取值
- 应用于 tryLDAPAuth(handler/auth.go)、ListLDAPUsers、BatchImportLDAP
- 保留 opsxsql 的 AES 密码加密(aiops 是明文,不倒退)
## Phase 1 — 后端 Redis 扫描服务化(借鉴 opsxdb server/internal/db/redis.go)
## 2. 前端配置门控(aiops `{ldapConfig && <Section/>}` 模式)
- **LdapConfigPage**:GetLDAPConfig 返回 null(未配置)时,隐藏「LDAP 账号管理」(LdapManagePanel/LdapUserTable/建 OU/建用户/账号测试),显示"未配置,先保存基础连接"引导态;保存成功后管理区出现
- **BatchImportModal**(UsersPage):未配置 LDAP 时隐藏「导入到 LDAP / 从 LDAP 导入」两个 tab,仅保留本地 CSV 导入
改造 `backend/internal/handler/redis.go`:
## 3. 实测验证(测试后端,独立 DB)
- 全新库未配置态:LdapConfigPage 管理区隐藏 + 导入弹窗无 LDAP tab
- 写入配置(8.217.205.92:389/dc=opsx,dc=vip/admin):管理区出现 → OU(devops/test)/用户列表(OpenLDAP 过滤器)→ 批量导入 → logan LDAP 登录复测
- 全量门禁(tsc/build/28 测试)后提交
1. **SCAN 增量游标分页**(对标 opsxdb 的不透明游标设计):
- `keyScanPosition{Pattern, DB, Nodes, Index, Cursor}` JSON→base64 编码回传;下次请求带回,服务端校验 pattern/db/节点拓扑一致,不一致报「搜索位置已失效」
- 每页上限 200 键,单页最多 20 次 SCAN 调用,完整批次一起返回
- 响应:`{keys:[{name,type,ttl}], next_cursor, exact?}` — **无通配符输入时先 TYPE 精确探测,命中作为 exact 项置顶**
2. **pattern 规则**:`redisBrowsePattern` — 无 `*?[` 通配符自动包 `*关键词*`;有则按 glob 原样
3. **pipeline 批量 TYPE/TTL**:替换现逐 key 串行 N+1(大库必超时);type 筛选参数真正生效(pipeline 取到 type 后过滤)
4. **集群归一**:`ScanAllKeys` 改为 ForEachMaster 各分片**增量** SCAN 归并排序;cluster 模式只暴露 DB0;MOVED 错误追加「请将部署模式切为集群并填种子节点」提示
5. **CLI 修复**(现为必 403 的死功能):后端 `RedisExecuteCommand` 接受 `{db, command}` 整串——按空白拆 args(引号感知),白名单匹配首词;前端契约同步(api/datasource/redis.ts);危险命令清单二段确认在前端做
6. 更新 key 的 DEL+重写改为按类型的原子命令(Hash 用 HSET/HDEL 行级;保留整 key 重写兜底)
验证:go build/vet/test + curl 实测(单机 redis-server 本地起,若有;否则用实例 fixture 手测游标翻页)
## Phase 2 — 前端 Redis 键浏览重构
新建 `src/features/redis/keyBrowser/` 共享模块(详情页键管理 tab 与 QueriesPage 的 SchemaSidebar Redis 分支两处接入):
1. **hooks/useRedisKeyBrowser.ts**:游标加载状态机(首页/加载更多/重置)、请求序号防竞态(过期响应丢弃)、按键名 Map 增量合并、db/pattern/type 参数
2. **utils/keyTree.ts**(对标 opsxdb src/redis.ts):
- `buildRedisKeyTree` 按 `:` 分隔建 folder 树(文件夹计数徽标、numeric 排序、无分隔符留根层、exact 置顶)
- `formatRedisTtl`(-1 永久/-2 不存在/45s/5m30s/2d5h)、`redisTypeColor` 八类型色点、`removeRedisKeyFromTree` 本地移除(删键不重拉)
3. **搜索框交互**:回车/按钮触发 SCAN(不做防抖——SCAN 有成本,opsxdb 同款取舍);有关键字时显示「清除」;占位提示支持 `*` 通配
4. **树渲染**:叶子只显示末段(hover title 全名)、行尾淡显 `{type} · {ttl}`、底部「加载更多」按钮(title 提示游标扫描语义);DB 切换下拉 cluster 恒 DB0
5. **类型筛选**下拉接真实后端过滤(修死功能)
6. RedisDetailPage 键管理 tab 与 SchemaSidebar Redis 分支替换为新模块(保持各自布局壳)
## Phase 3 — MySQL/PG 查询便捷性(借鉴 opsxdb ObjectTree/SqlEditor)
1. **通用右键菜单组件** `src/components/ContextMenu.tsx`:视口边界翻转、Esc/外点关闭、菜单项分隔线
2. **SchemaSidebar 增强**:
- 表右键菜单:查看数据(生成方言化 SELECT 进编辑器并执行)/ 生成 SQL 子菜单(SELECT/INSERT/UPDATE/DELETE,全列展开+复合主键进 WHERE)/ 复制表名 / 复制建表 DDL(已有 DDL API 则接,无则隐藏)
- 双击表 = 生成 SELECT 预览;列右键:复制列名/复制 table.column
- 搜索框:本地已加载过滤保留
3. **SqlEditor 增强**:「执行选中优先」——Mod-Enter/执行按钮改为选区优先、否则光标所在语句(按分号切分,简单引号感知)
4. **查询历史持久化**(后端 API 已存在闲置):执行成功后自动 saveQueryHistory;历史抽屉改为后端数据源(分页拉取+关键字过滤),点击回填、▶ 回填并执行;收藏 id 集(localStorage)叠加在后端历史上
5. **自定义模板**:QueryTemplatePanel 支持用户自定义片段(localStorage,{name, sql}),与内置并列
## Phase 4 — 工单流程 step 优化(借鉴 opsx-sec)
1. **状态驱动步骤条**:`features/ticket/components/TicketStageSteps.tsx`——工单详情页(及详情弹窗)顶部步骤条:提交→初审→复审(按 workflow 实际 steps 动态生成)→执行→完成;active 由工单 status 推导(数据驱动,非可点击回跳);驳回/失败/回滚显示终态错误色;下方快捷入口按钮(查看 SQL/审批/执行日志)
2. **审批时间线修复**:DetailTimeline 的 ACTION_CONFIG 键改为后端实际 action 值(created/approve/reject/pending_review/executed/...),图标+颜色按事件类型分色(成功绿/警告琥珀/失败红)
3. **审批交互统一**(详情弹窗与详情页行为对齐):
- 通过/驳回:弹出行内评论输入(可选)→确认;列表页行内快捷通过保留但加轻量确认与评论入口;`alert()` 全部换 toast
- 执行:二次确认复述影响面「将对实例 X 库 Y 执行 N 条语句,确认执行?」(opsx-sec 带影响面摘要模式)
- 禁用按钮可解释:不可执行时 disabled+tooltip 原因(等待审批/非执行人/已执行等),`disabledReason()` 纯函数
4. **创建页修正**(TicketCreatePage):
- 移除死的「定时执行」字段;新增「回滚 SQL」采集(后端已支持 rollback_sql);priority 下拉真实可选
- **审批流预览**:选定实例后展示其 workflow 步骤与审批人名单(数据已有,创建前可见谁会审)
- SQL 检测通过才可提交的 gate 保留(已是两段式)
5. **空态引导**:暂无工单空态内嵌「创建变更工单」主按钮(EmptyState 引导动作)
## 实施与验证
- 每 Phase 独立 commit;前端每步过 `tsc --noEmit` + `npm run build` + `audit:pages`(页面不超 700 行),后端过 `go build/vet/test`
- Phase 1/2 完成后:本地起 Go 服务(独立端口+DB),curl 验证游标翻页/pattern/type 过滤/集群归一;浏览器实测 Redis 键浏览(搜索/分组树/加载更多/DB 切换)
- Phase 3/4 完成后:浏览器实测右键菜单/生成 SQL/历史持久化回填、工单步骤条/审批评论/禁用原因/创建页新字段
- 全部完成后跑全量门禁(vitest/tsc/build/go),收尾总结
不改 aiops 项目;测试数据仍在独立临时库,结束清理。