Commit Graph
7 Commits
Author SHA1 Message Date
opsx 36724bba94 fix(queries): MySQL 实例误调 Redis 接口 500 + 点表生成 SELECT + 光标多行闪烁
=== 一、/queries 打开 MySQL 实例报 500 ===
现象:GET /api/v1/redis/5/dbs → 500
     后端日志 redis: invalid reply: "J\x00\x00\x00\n"
     ("J\x00\x00\x00" 是 MySQL 握手包,说明用 Redis 协议连了 3306)

根因:SchemaSidebar 是 mysql/redis 共用的组件,其中
        const redisDbs = useRedisDbs(Number(instanceId));
      是无条件调用(React hooks 不能条件调用),而 useRedisDbs 内部
      只看 instanceId 就请求 /redis/{id}/dbs →
      MySQL 实例也发 Redis 请求 → 后端用 Redis 协议连 MySQL → 500。

修复(前后端双保险):
  1. useRedisDbs 增加 enabled 参数(默认 true),false 时不发请求并复位 [0]/false
  2. SchemaSidebar 传 isRedis:useRedisDbs(Number(instanceId), isRedis)
  3. redis.go getRedisClient 前置校验 DBType != "redis" 直接返回
  4. resp 包新增 UserFacingError / UserFacing(),InternalErr 识别后返回
     400 + 可读原因(不再把所有错误笼统包装成 500 "服务器内部错误")

验证:
  GET /redis/5/dbs(MySQL)→ 400
    {"code":"bad_request","detail":"实例「pre-mysql-只读」的类型是 mysql,
     不是 redis。Redis 接口只适用于 redis 实例;MySQL/PostgreSQL 请使用
     /api/v1/instances/5/databases 等对应接口"}
  GET /instances/5/databases → 200
  GET /redis/4/dbs(Redis 集群)→ 200 {"cluster":true,"dbs":[0]}

=== 二、左侧栏点表不生成 SELECT ===
原交互:单击=展开/折叠,双击=执行 SELECT,Alt+点击=插入表名。
需求:单击表 → 在右侧编辑器生成 SELECT * FROM <db>.<table>

修复:
  1. SchemaSidebar 表节点:箭头图标包 data-chevron 单独触发展开/折叠;
     点表名区域 → onInsert(previewSelectSql(...) + ';\n')
     Alt+点击仍为只插表名;双击仍为直接执行
  2. QueriesPage onInsert:生成的文本以 SELECT 开头时「替换」编辑器内容
     (原来一律追加,会和上一条语句拼成非法 SQL);表名/字段名仍追加

=== 三、SQL 编辑器光标同时在多行闪烁 ===
根因:.cm-cursor 强制了 height:1.25em !important + marginTop:0.15em,
     而 .cm-line 行高是 1.55em,光标高度溢出到相邻行,视觉上像两行同时闪。
修复:光标样式只保留颜色与 1.5px 宽度,height/marginTop 交回 CodeMirror 自行计算。

镜像:lghost/opsxsql:latest + v0.1
2026-10-27 00:58:22 +00:00
opsx 0427b87412 fix(instance): access_mode 值 read/readonly 不一致导致「读」实例被过滤
现象:instance-groups 里给 MySQL 实例授予「只读查询」权限后,
     /queries 页面仍然选不到该实例,admin 也无法访问。

根因(枚举值前后端不一致):
  前端 CreateInstanceForm「读」按钮写入 access_mode = "read"
  后端 AccessibleInstances 只匹配 ["readwrite", "readonly"]
  → "read" 永远不命中,选了「读」的实例(MySQL)被过滤掉;
    Redis 实例是 "readwrite" 所以恰好能查,掩盖了该 bug。

修复:后端 readonly 过滤增加 "read",兼容两种历史写法:
  []string{"readwrite", "readonly", "read"}
  写过滤仍为 ["readwrite", "write"]("read" 实例正确地不能写)

验证(admin):
  GET /instances/accessible?access_type=readonly → 5 个(含 MySQL id=5, access_mode=read)
  GET /instances/accessible?access_type=write    → 4 个(MySQL 正确排除)

镜像:lghost/opsxsql:latest + v0.1 = sha256:6b1a7121da71
2026-10-26 18:30:47 +00:00
opsx f19a84f37e fix(redis): 集群拓扑解析错误,跨机集群节点被强制指向种子机
【现象】Redis 集群实例查 keys 报 500:
  Redis cluster connection failed: dial tcp 172.19.16.16:6005: connect: connection refused

【根因】handler/redis.go 解析 CLUSTER SLOTS:
  nodeAddr := slot.Nodes[0].Addr                    // Redis 上报的真实地址 172.19.16.30:6005
  if idx := strings.LastIndex(nodeAddr, ":"); idx >= 0 {
      nodeAddr = instance.Host + nodeAddr[idx:]     // 强制改写成 种子机主机 + 原端口
  }
  该逻辑假设「集群所有节点在同一台机器、只是端口不同」。
  对跨机集群(16/30/59 三台)会把所有节点地址破坏成
  172.19.16.16:6002 / :6003 / :6005 —— 这些端口在 16 上根本不存在 → refused。

【修复】
  仅当 Redis 上报空 IP / 127.0.0.1 / 0.0.0.0 时才用配置 host 兜底(NAT/端口映射场景),
  其余情况一律保留 Redis 上报的真实主机地址。

【附带数据修复(非代码)】
  实例 5 host: pre-mysql.astx.club → 172.19.16.2
  原因:pre-mysql.astx.club 公网 DNS 解析到 43.135.53.180(另一环境 MySQL),
       认证失败 → 前端数据库下拉为空、无法选择数据库。

【验证】
  GET /api/v1/redis/4/keys?db=0&pattern=*  → 200
    {"cluster":true,"keys":[{"key":"24H_rate_limit_10001_...","type":"string","ttl":42901000},
                            {"key":"CEX:FOLLOW:FOLLOWERS:TRADFI:97127836","type":"hash"},...]}
  GET /api/v1/redis/4/dbs                 → 200 {"cluster":true,"dbs":[0]}
  GET /api/v1/instances/5/databases       → 200 返回 25 个库
    ["cex_activity","cex_admin","cex_agent",...,"cex_user","cex_xxl_job"]

镜像 sha256:165992ef79d5
2026-10-26 18:01:44 +00:00
opsx 6b81dbbd4a feat(ldap): LDAP 改为只读接入,彻底移除账号验证与批量导入写入功能
按需求(sql.astx.club/settings):

1. 移除「测试账号验证」按钮
   - LdapConfigForm.tsx 删按钮 + onOpenAccountTest prop
   - 不再渲染 LdapAccountTestDialog

2. 移除批量导入的 LDAP 写操作(BatchImportModal.tsx)
   - TABS 去掉「导入到 LDAP」「从 LDAP 导入」
   - 删除对应两个 JSX 代码块(tab === 'to-ldap' / 'from-ldap')
   - 删除「创建 OU」子弹窗
   - 只保留「本地导入」(CSV 建本地账号,不触碰 LDAP 目录)

3. 移除 LDAP 账号管理面板(含危险写操作)
   - 不再渲染 LdapManagePanel(新建 OU / 添加用户 / 删除用户)
   - 删除 LdapCreateOuDialog / LdapAddUserDialog / ConfirmDialog 引用
   - LdapConfigPage.tsx 421 → 340 行;BatchImportModal.tsx 557 → 329 行

4. 保留(只读用途)
   - LDAP 配置表单(服务器 / 绑定 / 搜索 / 属性映射)
   - 「测试连接」按钮(验证配置可用,不写 LDAP 目录)

设计原则:LDAP 仅用于域账号登录认证(只读 bind + search),
         目录维护请到域控侧操作。

验证(前端 bundle grep,全部归零):
  测试账号验证 0 | 导入到 LDAP 0 | 从 LDAP 导入 0
  新建 OU 0 | 创建 OU 0 | 危险操作 0 | 此操作不可逆 0
保留:
  测试连接 6 | 本地导入 1

镜像 sha256:3970db141d23
/health → {"backend":"go","status":"ok","version":"0.4.0"}
http://sql.astx.club/settings → HTTP 200
2026-10-26 17:49:34 +00:00
opsx e8355e5d5f feat(ldap): LDAP 改为只读接入,移除账号验证测试与批量导入写操作
按需求调整 settings 页 LDAP 区域:

1. 移除「测试账号验证」按钮
   - LdapConfigForm.tsx 删除按钮 + onOpenAccountTest prop
   - LdapConfigPage.tsx 不再渲染 LdapAccountTestDialog

2. 移除批量导入的 LDAP 写操作
   - BatchImportModal.tsx 的 TABS 去掉「导入到 LDAP」「从 LDAP 导入」
   - 只保留「本地导入」(CSV 建本地账号,不触碰 LDAP 目录)

3. 移除 LDAP 账号管理面板(危险写操作)
   - 不再渲染 LdapManagePanel(新建 OU / 添加用户 / 删除用户)
   - 不再渲染 LdapCreateOuDialog / LdapAddUserDialog
   - 面板含「此操作不可逆」删除功能,与只读接入原则冲突

4. 保留(只读用途)
   - LDAP 配置表单(服务器/绑定/搜索/属性映射)
   - 「测试连接」按钮(验证配置是否可用,不写 LDAP)

设计原则:本系统 LDAP 仅用于域账号登录认证(只读 bind + search),
         目录维护请在域控侧操作。

验证(前端 bundle 实测):
  测试账号验证: 0    导入到 LDAP: 0    从 LDAP 导入: 0
  新建 OU: 0        危险操作: 0       此操作不可逆: 0
  测试连接: 6(保留)  本地导入: 1(保留)
  /health → {"backend":"go","status":"ok","version":"0.4.0"}
  http://sql.astx.club/settings → HTTP 200
2026-10-26 17:45:46 +00:00
opsx d584b64813 feat(security): 默认账号随机初始密码(不再硬编码 admin123)
参考 lokistack 的实现:

1. 首次启动生成 16 位随机密码(大小写+数字,排除 0/O/1/l/I 易混淆字符)
   - 4 个默认账号共用:admin / dba / auditor / developer
   - 写入数据目录 initial-admin-password.txt(0600)
   - 打印到启动日志(含账号列表、密码、文件路径、docker/kubectl 获取命令)

2. 支持 ADMIN_INIT_PASSWORD 环境变量覆盖(自动化部署 / 需要固定密码时)

3. 生成失败直接 log.Fatalf 退出,不降级到公开默认口令

4. 批量导入用户 BatchImportUsers:空密码不再默认 admin123,
   改为随机 14 位(新增 genPassword),保证每个导入用户密码不可预测

5. 幂等:已存在的账号不重建、不改密码;只有缺失的账号才创建

6. imagePullPolicy: IfNotPresent → Always
   原因:latest tag 配 IfNotPresent 会导致节点一直复用缓存的旧镜像,
        新 push 的镜像不生效(本次实测踩到:Pod imageID 仍是旧 digest)

7. 文档
   - README 新增「首次部署获取默认账号密码」章节(含 4 账号角色表 + 3 种获取方式)
   - docker-compose.yml 加 ADMIN_INIT_PASSWORD 环境变量
   - kubernetes/deployment.yaml 加 env 注释

验证(清空 DB 后全新 seed):
  镜像 sha256:eaf0affd8d93
  /app/data/initial-admin-password.txt  199B  权限 0600
  内容含 "适用账号: [admin dba auditor developer]" + 随机密码
  启动日志完整打印获取方式
  登录实测:admin / dba / developer 用新密码均返回 access_token;
           admin123 登录失败(已失效)
  二进制 strings:admin123 命中 0
2026-10-26 17:37:21 +00:00
opsx 396df57c9c build+deploy: 编译加 -trimpath,镜像改 lghost/opsxsql,K8s 部署到 sql 命名空间
1. -trimpath(抹除源码绝对路径)
   - Dockerfile: go build 加 -trimpath
   - run.sh: 两处 go build 加 -trimpath
   效果:二进制内 /app/backend/ 绝对路径命中 66 → 0(module 相对路径 sql-platform/ 仍保留,
        那是 Go 的正常行为,-trimpath 只去绝对前缀)

2. 镜像
   - kubernetes/deployment.yaml: opsxsql:latest → lghost/opsxsql:latest
   - kubernetes/install.sh: 默认 IMAGE 改 lghost/opsxsql:latest
   - 已构建并推送:lghost/opsxsql:latest (digest 70562a408cae, 25.2MB)

3. K8s 部署验证(namespace: sql)
   - Deployment opsxsql 1/1 Running,0 重启
   - Pod: opsxsql-695444d6b9-chrzp (node 172.19.0.161)
   - Service opsxsql.sql.svc.cluster.local:80
   - PVC opsxsql-data Bound 10Gi (cbs)
   - Secret opsxsql-secrets 自动生成(SECRET_KEY / JWT_SECRET)
   - /health → {"backend":"go","status":"ok","version":"0.4.0"}
   - 首页正常,seed 完成(26 条审核规则 + LDAP 默认配置)

部署命令:bash kubernetes/install.sh
2026-10-26 17:22:29 +00:00