Tui 重构代码逻辑

This commit is contained in:
2026-02-27 22:52:15 +08:00
parent 3a5f5ddd5d
commit d4e214fe23
8 changed files with 934 additions and 718 deletions

View File

@@ -1,194 +1,162 @@
package wizard
import (
"encoding/json"
"bufio"
"database/sql"
"fmt"
"net"
"os"
"path/filepath"
"strings"
"sunhpc/pkg/database"
"sunhpc/pkg/utils"
"github.com/charmbracelet/bubbles/textinput"
)
// saveConfig 保存配置到文件
// 配置项映射:定义每个配置项对应的表名、键名
var configMappings = []struct {
table string
key string
getVal func(m *model) interface{} // 动态获取配置值的函数
}{
// attributes 表
{"attributes", "license", func(m *model) any { return m.config.License }},
{"attributes", "accepted", func(m *model) any { return m.config.AgreementAccepted }},
{"attributes", "country", func(m *model) any { return m.config.Country }},
{"attributes", "region", func(m *model) any { return m.config.Region }},
{"attributes", "timezone", func(m *model) any { return m.config.Timezone }},
{"attributes", "homepage", func(m *model) any { return m.config.HomePage }},
{"attributes", "dbaddress", func(m *model) any { return m.config.DBAddress }},
{"attributes", "software", func(m *model) any { return m.config.Software }},
// nodes 表
{"nodes", "name", func(m *model) any { return m.config.Hostname }},
// 公网设置表
{"public_network", "public_interface", func(m *model) any { return m.config.PublicInterface }},
{"public_network", "ip_address", func(m *model) any { return m.config.PublicIPAddress }},
{"public_network", "netmask", func(m *model) any { return m.config.PublicNetmask }},
{"public_network", "gateway", func(m *model) any { return m.config.PublicGateway }},
// 内网配置表
{"internal_network", "internal_interface", func(m *model) any { return m.config.InternalInterface }},
{"internal_network", "internal_ip", func(m *model) any { return m.config.InternalIPAddress }},
{"internal_network", "internal_mask", func(m *model) any { return m.config.InternalNetmask }},
// DNS配置表
{"dns_config", "dns_primary", func(m *model) any { return m.config.DNSPrimary }},
{"dns_config", "dns_secondary", func(m *model) any { return m.config.DNSSecondary }},
}
// saveConfig 入口函数:保存所有配置到数据库
func (m *model) saveConfig() error {
configPath := GetConfigPath()
// 确保目录存在
dir := filepath.Dir(configPath)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("创建配置目录失败:%w", err)
}
// 序列化配置
data, err := json.MarshalIndent(m.config, "", " ")
conn, err := database.GetDB() // 假设database包已实现getDB()获取连接
if err != nil {
return fmt.Errorf("序列化配置失败:%w", err)
return fmt.Errorf("获取数据库连接失败: %w", err)
}
defer conn.Close()
// 写入文件
if err := os.WriteFile(configPath, data, 0644); err != nil {
return fmt.Errorf("保存配置文件失败:%w", err)
m.force = false // 初始化全量覆盖标识
// 遍历所有配置项,逐个处理
for _, item := range configMappings {
val := item.getVal(m)
exists, err := m.checkExists(conn, item.table, item.key)
if err != nil {
return fmt.Errorf("检查%s.%s是否存在失败: %w", item.table, item.key, err)
}
// 根据存在性和用户选择处理
if !exists {
// 不存在则直接插入
if err := m.upsertConfig(conn, item.table, item.key, val, false); err != nil {
return fmt.Errorf("插入%s.%s失败: %w", item.table, item.key, err)
}
continue
}
// 已存在:判断是否全量覆盖
if m.force {
if err := m.upsertConfig(conn, item.table, item.key, val, true); err != nil {
return fmt.Errorf("强制更新%s.%s失败: %w", item.table, item.key, err)
}
continue
}
// 询问用户操作
choice, err := m.askUserChoice(item.table, item.key)
if err != nil {
return fmt.Errorf("获取用户选择失败: %w", err)
}
switch strings.ToLower(choice) {
case "y", "yes":
// 单条覆盖
if err := m.upsertConfig(conn, item.table, item.key, val, true); err != nil {
return fmt.Errorf("更新%s.%s失败: %w", item.table, item.key, err)
}
case "a", "all":
// 全量覆盖,后续不再询问
m.force = true
if err := m.upsertConfig(conn, item.table, item.key, val, true); err != nil {
return fmt.Errorf("全量更新%s.%s失败: %w", item.table, item.key, err)
}
case "n", "no":
// 跳过当前项
fmt.Printf("跳过%s.%s的更新\n", item.table, item.key)
default:
fmt.Printf("无效选择%s跳过%s.%s的更新\n", choice, item.table, item.key)
}
}
return nil
}
// loadConfig 从文件加载配置
func loadConfig() (*Config, error) {
configPath := GetConfigPath()
data, err := os.ReadFile(configPath)
// checkExists 集中判断配置项是否存在(核心判断逻辑)
func (m *model) checkExists(conn *sql.DB, table, key string) (bool, error) {
var count int
// 通用存在性检查SQL假设所有表都有key字段作为主键
query := fmt.Sprintf("SELECT COUNT(1) FROM %s WHERE `key` = ?", table)
err := conn.QueryRow(query, key).Scan(&count)
if err != nil {
return nil, fmt.Errorf("读取配置文件失败:%w", err)
// 表不存在也视为"不存在"(可选:根据实际需求调整,比如先建表)
if strings.Contains(err.Error(), "table not found") {
return false, nil
}
return false, err
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("解析配置文件失败:%w", err)
}
return &cfg, nil
return count > 0, nil
}
// 以下是 model.go 中调用的保存方法
func (m *model) saveCurrentPage() {
switch m.currentPage {
case PageData:
m.saveDataPage()
case PagePublicNetwork:
m.savePublicNetworkPage()
case PageInternalNetwork:
m.saveInternalNetworkPage()
case PageDNS:
m.saveDNSPage()
// upsertConfig 统一处理插入/更新逻辑
func (m *model) upsertConfig(conn *sql.DB, table, key string, val interface{}, update bool) error {
var query string
if !update {
// 插入:假设表结构为(key, value)
query = fmt.Sprintf("INSERT INTO %s (`key`, `value`) VALUES (?, ?)", table)
} else {
// 更新
query = fmt.Sprintf("UPDATE %s SET `value` = ? WHERE `key` = ?", table)
}
// 处理参数顺序(更新和插入的参数顺序不同)
var args []interface{}
if !update {
args = []interface{}{key, val}
} else {
args = []interface{}{val, key}
}
_, err := conn.Exec(query, args...)
return err
}
func (m *model) saveDataPage() {
if len(m.textInputs) >= 8 {
m.config.HomePage = m.textInputs[0].Value()
m.config.Hostname = m.textInputs[1].Value()
m.config.Country = m.textInputs[2].Value()
m.config.Region = m.textInputs[3].Value()
m.config.Timezone = m.textInputs[4].Value()
m.config.DBAddress = m.textInputs[5].Value()
m.config.DataAddress = m.textInputs[6].Value()
}
}
func (m *model) savePublicNetworkPage() {
if len(m.textInputs) >= 4 {
m.config.PublicInterface = m.textInputs[0].Value()
m.config.PublicIPAddress = m.textInputs[1].Value()
m.config.PublicNetmask = m.textInputs[2].Value()
m.config.PublicGateway = m.textInputs[3].Value()
}
}
func (m *model) saveInternalNetworkPage() {
if len(m.textInputs) >= 3 {
m.config.InternalInterface = m.textInputs[0].Value()
m.config.InternalIPAddress = m.textInputs[1].Value()
m.config.InternalNetmask = m.textInputs[2].Value()
}
}
func (m *model) saveDNSPage() {
if len(m.textInputs) >= 2 {
m.config.DNSPrimary = m.textInputs[0].Value()
m.config.DNSSecondary = m.textInputs[1].Value()
}
}
// initPageInputs 初始化当前页面的输入框
func (m *model) initPageInputs() {
m.textInputs = make([]textinput.Model, 0)
switch m.currentPage {
case PageData:
fields := []struct{ label, value string }{
{"Homepage", m.config.HomePage},
{"Hostname", m.config.Hostname},
{"Country", m.config.Country},
{"Region", m.config.Region},
{"Timezone", m.config.Timezone},
{"DB Path", m.config.DBAddress},
{"Software", m.config.DataAddress},
}
for _, f := range fields {
ti := textinput.New()
ti.Placeholder = ""
ti.Placeholder = f.label
ti.SetValue(f.value)
ti.Width = 50
m.textInputs = append(m.textInputs, ti)
}
m.focusIndex = 0
if len(m.textInputs) > 0 {
m.textInputs[0].Focus()
}
m.inputLabels = []string{"Homepage", "Hostname", "Country", "Region", "Timezone", "DBPath", "Software"}
case PagePublicNetwork:
fields := []struct{ label, value string }{
{"Public Interface", m.config.PublicInterface},
{"Public IP Address", m.config.PublicIPAddress},
{"Public Netmask", m.config.PublicNetmask},
{"Public Gateway", m.config.PublicGateway},
}
for _, f := range fields {
ti := textinput.New()
ti.Placeholder = ""
ti.Placeholder = f.label
ti.SetValue(f.value)
ti.Width = 50
m.textInputs = append(m.textInputs, ti)
}
m.focusIndex = 0
if len(m.textInputs) > 0 {
m.textInputs[0].Focus()
}
m.inputLabels = []string{"Public Interface", "Public IP Address", "Public Netmask", "Public Gateway"}
case PageInternalNetwork:
fields := []struct{ label, value string }{
{"Internal Interface", m.config.InternalInterface},
{"Internal IP Address", m.config.InternalIPAddress},
{"Internal Netmask", m.config.InternalNetmask},
}
for _, f := range fields {
ti := textinput.New()
ti.Placeholder = f.label
ti.SetValue(f.value)
ti.Width = 50
m.textInputs = append(m.textInputs, ti)
}
m.focusIndex = 0
if len(m.textInputs) > 0 {
m.textInputs[0].Focus()
}
m.inputLabels = []string{"Internal Interface", "Internal IP", "Internal Mask"}
case PageDNS:
fields := []struct{ label, value string }{
{"Primary DNS", m.config.DNSPrimary},
{"Secondary DNS", m.config.DNSSecondary},
}
for _, f := range fields {
ti := textinput.New()
ti.Placeholder = f.label
ti.SetValue(f.value)
ti.Width = 50
m.textInputs = append(m.textInputs, ti)
}
m.focusIndex = 0
if len(m.textInputs) > 0 {
m.textInputs[0].Focus()
}
m.inputLabels = []string{"Pri DNS", "Sec DNS"}
// askUserChoice 询问用户操作选择
func (m *model) askUserChoice(table, key string) (string, error) {
reader := bufio.NewReader(os.Stdin)
fmt.Printf("配置项%s.%s已存在选择操作(y/yes=覆盖, n/no=跳过, a/all=全量覆盖后续所有): ", table, key)
input, err := reader.ReadString('\n')
if err != nil {
return "", err
}
// 去除空格和换行
return strings.TrimSpace(input), nil
}
// 获取系统网络接口