package wizard import ( "fmt" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" ) // PageType 页面类型 type PageType int // 总页码 const TotalPages = 6 // Config 系统配置结构 type Config struct { // 协议 License string `json:"license"` AgreementAccepted bool `json:"agreement_accepted"` // 数据接收 ClusterName string `json:"cluster_name"` Country string `json:"country"` Region string `json:"region"` Timezone string `json:"timezone"` HomePage string `json:"homepage"` DBAddress string `json:"db_address"` Software string `json:"software"` // 公网设置 PublicInterface string `json:"public_interface"` PublicIPAddress string `json:"ip_address"` PublicNetmask string `json:"netmask"` PublicGateway string `json:"gateway"` PublicMTU string `json:"public_mtu"` // 内网配置 PrivateInterface string `json:"private_interface"` PrivateIPAddress string `json:"private_ip"` PrivateNetmask string `json:"private_mask"` PrivateMTU string `json:"private_mtu"` // DNS 配置 DNSPrimary string `json:"dns_primary"` DNSSecondary string `json:"dns_secondary"` } const ( PageAgreement PageType = iota PageData PagePublicNetwork PageInternalNetwork PageDNS PageSummary ) // model TUI 主模型 type model struct { config Config // 全局配置 currentPage PageType // 当前页面 totalPages int textInputs []textinput.Model // 当前页面的输入框 networkInterfaces []string // 所有系统网络接口 width int height int err error quitting bool done bool force bool // 核心1: 按页面分组存储所有组件(6个页面 + 6个map) pageComponents map[PageType]map[string]Focusable // 核心2:焦点管理器(每次切换页面时重置) focusManager *FocusManager } // defaultConfig 返回默认配置 func defaultConfig() Config { var ( defaultPublicInterface string defaultInternalInterface string ) interfaces := getNetworkInterfaces() switch len(interfaces) { case 0: defaultPublicInterface = "" defaultInternalInterface = "" case 1: defaultPublicInterface = interfaces[0] defaultInternalInterface = fmt.Sprintf("%s:0", interfaces[0]) case 2: defaultPublicInterface = interfaces[0] defaultInternalInterface = interfaces[1] default: defaultPublicInterface = interfaces[0] defaultInternalInterface = interfaces[1] } return Config{ License: "This test license is for testing purposes only. Do not use it in production.", ClusterName: "cluster.hpc.org", Country: "China", Region: "Beijing", Timezone: "Asia/Shanghai", HomePage: "www.sunhpc.com", DBAddress: "/var/lib/sunhpc/sunhpc.db", Software: "/export/sunhpc", PublicInterface: defaultPublicInterface, PublicIPAddress: "", PublicNetmask: "", PublicGateway: "", PrivateInterface: defaultInternalInterface, PrivateIPAddress: "172.16.9.254", PrivateNetmask: "255.255.255.0", PrivateMTU: "1500", DNSPrimary: "8.8.8.8", DNSSecondary: "8.8.4.4", } } // initialModel 初始化模型 func initialModel() model { cfg := defaultConfig() // 1. 初始化所有页面组件(6个页面) pageComponents := make(map[PageType]map[string]Focusable) // ------------------ 页面1:协议页面 -------------------- page1Comps := make(map[string]Focusable) page1Comps["accept_btn"] = NewButton("接受协议") page1Comps["reject_btn"] = NewButton("拒绝协议") pageComponents[PageAgreement] = page1Comps // ------------------ 页面2:基础信息页面 -------------------- page2Comps := make(map[string]Focusable) page2Comps["Homepage_input"] = NewTextInput("Homepage", cfg.HomePage) page2Comps["ClusterName_input"] = NewTextInput("ClusterName", cfg.ClusterName) page2Comps["Country_input"] = NewTextInput("Country", cfg.Country) page2Comps["Region_input"] = NewTextInput("Region", cfg.Region) page2Comps["Timezone_input"] = NewTextInput("Timezone", cfg.Timezone) page2Comps["DBPath_input"] = NewTextInput("DBPath", cfg.DBAddress) page2Comps["Software_input"] = NewTextInput("Software", cfg.Software) page2Comps["next_btn"] = NewButton("下一步") page2Comps["prev_btn"] = NewButton("上一步") pageComponents[PageData] = page2Comps // ------------------ 页面3:公网网络页面 -------------------- page3Comps := make(map[string]Focusable) page3Comps["PublicInterface_input"] = NewTextInput("PublicInterface", cfg.PublicInterface) page3Comps["PublicIPAddress_input"] = NewTextInput("PublicIPAddress", cfg.PublicIPAddress) page3Comps["PublicNetmask_input"] = NewTextInput("PublicNetmask", cfg.PublicNetmask) page3Comps["PublicGateway_input"] = NewTextInput("PublicGateway", cfg.PublicGateway) page3Comps["PublicMTU_input"] = NewTextInput("PublicMTU", cfg.PublicMTU) page3Comps["next_btn"] = NewButton("下一步") page3Comps["prev_btn"] = NewButton("上一步") pageComponents[PagePublicNetwork] = page3Comps // ------------------ 页面4:内网网络页面 -------------------- page4Comps := make(map[string]Focusable) page4Comps["PrivateInterface_input"] = NewTextInput("PrivateInterface", cfg.PrivateInterface) page4Comps["PrivateIPAddress_input"] = NewTextInput("PrivateIPAddress", cfg.PrivateIPAddress) page4Comps["PrivateNetmask_input"] = NewTextInput("PrivateNetmask", cfg.PrivateNetmask) page4Comps["PrivateMTU_input"] = NewTextInput("PrivateMTU", cfg.PrivateMTU) page4Comps["next_btn"] = NewButton("下一步") page4Comps["prev_btn"] = NewButton("上一步") pageComponents[PageInternalNetwork] = page4Comps // ------------------ 页面5:DNS页面 -------------------- page5Comps := make(map[string]Focusable) page5Comps["Pri_DNS_input"] = NewTextInput("Pri DNS", cfg.DNSPrimary) page5Comps["Sec_DNS_input"] = NewTextInput("Sec DNS", cfg.DNSSecondary) page5Comps["next_btn"] = NewButton("下一步") page5Comps["prev_btn"] = NewButton("上一步") pageComponents[PageDNS] = page5Comps // ------------------ 页面6:Summary页面 -------------------- page6Comps := make(map[string]Focusable) page6Comps["confirm_btn"] = NewButton("Confirm") page6Comps["cancel_btn"] = NewButton("Cancel") pageComponents[PageSummary] = page6Comps // 创建焦点管理器(初始化聚焦页) fm := NewFocusManager(true) // 初始化模型 m := model{ config: cfg, totalPages: 6, currentPage: PageAgreement, // 初始化聚焦在协议页面 pageComponents: pageComponents, focusManager: fm, } // 初始化当前页 (页1) 的焦点 m.initPageFocus(m.currentPage) return m } // Init 初始化命令 func (m model) Init() tea.Cmd { return textinput.Blink } // Update 处理消息更新 func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { case "ctrl+c": m.saveConfig() //m.quitting = true return m, tea.Quit // 1. 焦点切换(Tab/Shift+Tab)交给管理器处理 case "tab", "shift+tab", "left", "right": cmd := m.focusManager.HandleInput(msg) return m, cmd // 2. 回车键:处理当前焦点组件的点击/确认 case "enter": currentCompID := m.focusManager.currentFocusID switch currentCompID { // 页1:accept → 进入页2 case "accept_btn": return m, m.switchPage(PageData) // 页1:reject → 退出程序 case "reject_btn": m.quitting = true return m, tea.Quit // 通用上一页/下一页逻辑 case "prev_btn": return m, m.switchPage(m.currentPage - 1) case "next_btn": return m, m.switchPage(m.currentPage + 1) // 页6:确认配置 → 退出并保存 case "confirm_btn": m.done = true //m.quitting = true return m, tea.Quit case "cancel_btn": m.quitting = true return m, tea.Quit } } // 其他消息(窗口大小、输入框输入等)... } // 处理当前焦点组件的内部更新(比如输入框打字、列表选值) currentComp, exists := m.focusManager.GetCurrent() if exists { // 不同组件的内部更新逻辑(示例) switch comp := currentComp.(type) { case *TextInput: // 输入框更新 newTI, cmd := comp.Model.Update(msg) comp.Model = newTI // 保存输入值到全局配置(示例:主机名) switch m.focusManager.currentFocusID { // 页2:基础信息 case "Homepage_input": m.config.HomePage = comp.Value() case "ClusterName_input": m.config.ClusterName = comp.Value() case "Country_input": m.config.Country = comp.Value() case "Region_input": m.config.Region = comp.Value() case "Timezone_input": m.config.Timezone = comp.Value() case "DBPath_input": m.config.DBAddress = comp.Value() case "Software_input": m.config.Software = comp.Value() // 页3:公网网络 case "PublicInterface_input": m.config.PublicInterface = comp.Value() case "PublicIPAddress_input": m.config.PublicIPAddress = comp.Value() case "PublicNetmask_input": m.config.PublicNetmask = comp.Value() case "PublicGateway_input": m.config.PublicGateway = comp.Value() case "PublicMTU_input": m.config.PublicMTU = comp.Value() // 页4:内网网络 case "PrivateInterface_input": m.config.PrivateInterface = comp.Value() case "PrivateIPAddress_input": m.config.PrivateIPAddress = comp.Value() case "PrivateNetmask_input": m.config.PrivateNetmask = comp.Value() case "PrivateMTU_input": m.config.PrivateMTU = comp.Value() // 页5:DNS case "Pri_DNS_input": m.config.DNSPrimary = comp.Value() case "Sec_DNS_input": m.config.DNSSecondary = comp.Value() } return m, cmd case *List: // 列表更新 newList, cmd := comp.Model.Update(msg) comp.Model = newList return m, cmd } } return m, nil }