Persist agent/secrets settings with masked secrets and runner hot-reload; add maintenance windows, maintpage binary, and zero-downtime listener handoff.
164 lines
4.8 KiB
Go
164 lines
4.8 KiB
Go
// Package sysadmin 系统管理台(一期)薄聚合层:只通过小接口读取其他模块的
|
|
// 公开状态,业务包不得 import 本包。
|
|
package sysadmin
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"chat-agent-system/internal/config"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
// StatusProvider 业务服务的数据库/schema 公开状态(*bizdev.Service 满足)。
|
|
type StatusProvider interface {
|
|
DatabaseReady() bool
|
|
SchemaVersion() int64
|
|
LiveSchemaVersion(ctx context.Context) int64
|
|
}
|
|
|
|
// KBProber 知识库在线探针(*kb.Client 满足)。
|
|
type KBProber interface {
|
|
Online() bool
|
|
}
|
|
|
|
// ApplyFunc 将 agent/secrets 热更新推送到 PI runner;返回状态文案与错误。
|
|
type ApplyFunc func(ctx context.Context) (status string, err error)
|
|
|
|
// Options 装配根注入的依赖;全部为只读探针,聚合层不持有业务写路径。
|
|
type Options struct {
|
|
Pool *pgxpool.Pool
|
|
Store SettingsStore // 测试可直接注入;为空且 Pool 非空时使用 PG store
|
|
Status StatusProvider
|
|
KB KBProber
|
|
TaskStoreReady func(ctx context.Context) bool
|
|
FeatureValues func() map[string]string // 组合根解析后的功能开关生效值(key 为环境变量名)
|
|
ApplyFn ApplyFunc // 可选:保存 HotReload 键后推送 runner
|
|
}
|
|
|
|
// Service 系统管理台聚合服务。
|
|
type Service struct {
|
|
cfg *config.Config
|
|
store SettingsStore
|
|
status StatusProvider
|
|
kb KBProber
|
|
taskStoreReady func(ctx context.Context) bool
|
|
featureValues func() map[string]string
|
|
applyFn ApplyFunc
|
|
specs []SettingSpec
|
|
startedAt time.Time
|
|
}
|
|
|
|
// NewService 构建聚合服务;cfg 允许为 nil(测试)。
|
|
func NewService(cfg *config.Config, opts Options) *Service {
|
|
store := opts.Store
|
|
if store == nil && opts.Pool != nil {
|
|
store = newPGSettingsStore(opts.Pool)
|
|
}
|
|
return &Service{
|
|
cfg: cfg,
|
|
store: store,
|
|
status: opts.Status,
|
|
kb: opts.KB,
|
|
taskStoreReady: opts.TaskStoreReady,
|
|
featureValues: opts.FeatureValues,
|
|
applyFn: opts.ApplyFn,
|
|
specs: defaultSettingSpecs(),
|
|
startedAt: time.Now(),
|
|
}
|
|
}
|
|
|
|
// SetApplyFn 运行期注入推送回调(组合根在 runner URL 就绪后设置)。
|
|
func (s *Service) SetApplyFn(fn ApplyFunc) {
|
|
if s != nil {
|
|
s.applyFn = fn
|
|
}
|
|
}
|
|
|
|
// ApplyToRunner 主动推送当前 DB/env 解析值到 PI runner。
|
|
func (s *Service) ApplyToRunner(ctx context.Context) (string, error) {
|
|
if s == nil || s.applyFn == nil {
|
|
return "n/a", nil
|
|
}
|
|
return s.applyFn(ctx)
|
|
}
|
|
|
|
// Overview 系统总览,字段对齐前端 camelCase 契约。
|
|
type Overview struct {
|
|
Status string `json:"status"`
|
|
Env string `json:"env"`
|
|
AgentBackend string `json:"agentBackend"`
|
|
DatabaseReady bool `json:"databaseReady"`
|
|
SchemaVersion int64 `json:"schemaVersion"`
|
|
TaskStoreReady bool `json:"taskStoreReady"`
|
|
KBOnline bool `json:"kbOnline"`
|
|
UptimeSeconds int64 `json:"uptimeSeconds"`
|
|
}
|
|
|
|
// Overview 聚合 /health 同源信号。
|
|
func (s *Service) Overview(ctx context.Context) Overview {
|
|
dbReady := s.status != nil && s.status.DatabaseReady()
|
|
schemaVersion := int64(0)
|
|
if s.status != nil {
|
|
if dbReady {
|
|
schemaVersion = s.status.LiveSchemaVersion(ctx)
|
|
} else {
|
|
schemaVersion = s.status.SchemaVersion()
|
|
}
|
|
}
|
|
taskStoreReady := false
|
|
if s.taskStoreReady != nil {
|
|
taskStoreReady = s.taskStoreReady(ctx)
|
|
}
|
|
|
|
status := "ok"
|
|
if s.cfg != nil && s.cfg.ContextDbHost != "" && !s.cfg.AllowInMemory && !dbReady {
|
|
status = "degraded"
|
|
}
|
|
|
|
out := Overview{
|
|
Status: status,
|
|
DatabaseReady: dbReady,
|
|
SchemaVersion: schemaVersion,
|
|
TaskStoreReady: taskStoreReady,
|
|
KBOnline: s.kb != nil && s.kb.Online(),
|
|
UptimeSeconds: int64(time.Since(s.startedAt).Seconds()),
|
|
}
|
|
if s.cfg != nil {
|
|
out.Env = s.cfg.Env
|
|
out.AgentBackend = s.cfg.AgentBackend
|
|
}
|
|
return out
|
|
}
|
|
|
|
// KBInfo 知识库连接视图;密码等敏感信息绝不输出。
|
|
type KBInfo struct {
|
|
Online bool `json:"online"`
|
|
Host string `json:"host"`
|
|
Port int `json:"port"`
|
|
Database string `json:"database"`
|
|
User string `json:"user"`
|
|
EmbeddingModel string `json:"embeddingModel"`
|
|
RerankerEnabled bool `json:"rerankerEnabled"`
|
|
SSLMode string `json:"sslMode"`
|
|
}
|
|
|
|
// KBInfo 返回 KB 连接与模型配置(脱敏)。
|
|
func (s *Service) KBInfo() KBInfo {
|
|
out := KBInfo{
|
|
Online: s.kb != nil && s.kb.Online(),
|
|
// KbDSN 固定 sslmode=disable,如实暴露
|
|
SSLMode: "disable",
|
|
}
|
|
if s.cfg != nil {
|
|
out.Host = s.cfg.KbDbHost
|
|
out.Port = s.cfg.KbDbPort
|
|
out.Database = s.cfg.KbDbName
|
|
out.User = s.cfg.KbDbUser
|
|
out.EmbeddingModel = s.cfg.EmbeddingModel
|
|
out.RerankerEnabled = s.cfg.RerankerEndpointURL != ""
|
|
}
|
|
return out
|
|
}
|