- aitools 共用 runtime:list/describe/schema/query/redis(纯只读,无写确认) - 白名单 ~/.opsxdb/mcp.json;审计 mcp-audit.jsonl;复用 danger.go - 桌面 connections 双写 ~/.opsxdb/connections.json(ox1. 解码) - Cursor 配置示例与任务书验收勾选更新;写操作与 Agent UI 留 P1
168 lines
4.6 KiB
Go
168 lines
4.6 KiB
Go
package aitools
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
|
||
"opsxdb/server/internal/db"
|
||
)
|
||
|
||
// SavedConnection 与桌面 store SavedConnection JSON 对齐(磁盘形态,密码可为 ox1. 混淆)。 // Logan.Li
|
||
type SavedConnection struct {
|
||
ID string `json:"id"`
|
||
Name string `json:"name"`
|
||
Driver string `json:"driver"`
|
||
Host string `json:"host"`
|
||
Port int `json:"port"`
|
||
User string `json:"user"`
|
||
Password string `json:"password"`
|
||
Database string `json:"database"`
|
||
Service string `json:"service,omitempty"`
|
||
SSL bool `json:"ssl,omitempty"`
|
||
SSLMode string `json:"sslMode,omitempty"`
|
||
SSLCA string `json:"sslCA,omitempty"`
|
||
SSLCert string `json:"sslCert,omitempty"`
|
||
SSLKey string `json:"sslKey,omitempty"`
|
||
RedisMode string `json:"redisMode,omitempty"`
|
||
RedisNodes string `json:"redisNodes,omitempty"`
|
||
MasterName string `json:"masterName,omitempty"`
|
||
SentinelPassword string `json:"sentinelPassword,omitempty"`
|
||
SSHEnabled bool `json:"sshEnabled,omitempty"`
|
||
SSHHost string `json:"sshHost,omitempty"`
|
||
SSHPort int `json:"sshPort,omitempty"`
|
||
SSHUser string `json:"sshUser,omitempty"`
|
||
SSHAuth string `json:"sshAuth,omitempty"`
|
||
SSHPassword string `json:"sshPassword,omitempty"`
|
||
SSHPrivateKey string `json:"sshPrivateKey,omitempty"`
|
||
SSHPassphrase string `json:"sshPassphrase,omitempty"`
|
||
SavePassword *bool `json:"savePassword,omitempty"`
|
||
}
|
||
|
||
// ConnectionSummary 列表对外字段,永不含密码。
|
||
type ConnectionSummary struct {
|
||
ID string `json:"id"`
|
||
Name string `json:"name"`
|
||
Driver string `json:"driver"`
|
||
Host string `json:"host"`
|
||
Port int `json:"port"`
|
||
Database string `json:"database"`
|
||
}
|
||
|
||
func ConnectionsPath() (string, error) {
|
||
dir, err := opsxdbDir()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
return filepath.Join(dir, "connections.json"), nil
|
||
}
|
||
|
||
// LoadConnections 读 ~/.opsxdb/connections.json。
|
||
func LoadConnections() ([]SavedConnection, error) {
|
||
path, err := ConnectionsPath()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
b, err := os.ReadFile(path)
|
||
if err != nil {
|
||
if os.IsNotExist(err) {
|
||
return nil, nil
|
||
}
|
||
return nil, err
|
||
}
|
||
var list []SavedConnection
|
||
if err := json.Unmarshal(b, &list); err != nil {
|
||
return nil, fmt.Errorf("connections.json: %w", err)
|
||
}
|
||
return list, nil
|
||
}
|
||
|
||
// SaveConnections 写 ~/.opsxdb/connections.json(0600);供桌面双写 API 调用。
|
||
func SaveConnections(list []SavedConnection) error {
|
||
path, err := ConnectionsPath()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
|
||
return err
|
||
}
|
||
b, err := json.MarshalIndent(list, "", " ")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return os.WriteFile(path, b, 0600)
|
||
}
|
||
|
||
func FindConnection(id string) (*SavedConnection, error) {
|
||
list, err := LoadConnections()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
for i := range list {
|
||
if list[i].ID == id {
|
||
c := list[i]
|
||
return &c, nil
|
||
}
|
||
}
|
||
return nil, fmt.Errorf("connection not found: %s", id)
|
||
}
|
||
|
||
// ToConnReq 映射为 db.ConnReq(解码 ox1. 密码)。
|
||
func (c SavedConnection) ToConnReq() db.ConnReq {
|
||
sslOn := c.SSL || (c.SSLMode != "" && c.SSLMode != "disable")
|
||
mode := c.SSLMode
|
||
if sslOn {
|
||
if mode == "" || mode == "disable" {
|
||
mode = "require"
|
||
}
|
||
} else {
|
||
mode = "disable"
|
||
}
|
||
return db.ConnReq{
|
||
Driver: c.Driver,
|
||
Host: c.Host,
|
||
Port: c.Port,
|
||
User: c.User,
|
||
Password: DecodeSecret(c.Password),
|
||
Database: c.Database,
|
||
Service: c.Service,
|
||
SSL: sslOn,
|
||
SSLMode: mode,
|
||
SSLCA: c.SSLCA,
|
||
SSLCert: c.SSLCert,
|
||
SSLKey: c.SSLKey,
|
||
RedisMode: orDefault(c.RedisMode, "standalone"),
|
||
RedisNodes: c.RedisNodes,
|
||
MasterName: c.MasterName,
|
||
SentinelPassword: DecodeSecret(c.SentinelPassword),
|
||
SSHEnabled: c.SSHEnabled,
|
||
SSHHost: c.SSHHost,
|
||
SSHPort: c.SSHPort,
|
||
SSHUser: c.SSHUser,
|
||
SSHAuth: c.SSHAuth,
|
||
SSHPassword: DecodeSecret(c.SSHPassword),
|
||
SSHPrivateKey: c.SSHPrivateKey,
|
||
SSHPassphrase: DecodeSecret(c.SSHPassphrase),
|
||
}
|
||
}
|
||
|
||
func (c SavedConnection) Summary() ConnectionSummary {
|
||
return ConnectionSummary{
|
||
ID: c.ID,
|
||
Name: c.Name,
|
||
Driver: c.Driver,
|
||
Host: c.Host,
|
||
Port: c.Port,
|
||
Database: c.Database,
|
||
}
|
||
}
|
||
|
||
func orDefault(s, def string) string {
|
||
if strings.TrimSpace(s) == "" {
|
||
return def
|
||
}
|
||
return s
|
||
}
|