package wizard import ( "fmt" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" ) // Config 系统配置结构 type Config struct { // 协议 AgreementAccepted bool `json:"agreement_accepted"` // 数据接收 Hostname string `json:"hostname"` Country string `json:"country"` Region string `json:"region"` Timezone string `json:"timezone"` HomePage string `json:"homepage"` DBAddress string `json:"db_address"` DataAddress string `json:"data_address"` // 公网设置 PublicInterface string `json:"public_interface"` PublicIPAddress string `json:"ip_address"` PublicNetmask string `json:"netmask"` PublicGateway string `json:"gateway"` // 内网配置 InternalInterface string `json:"internal_interface"` InternalIPAddress string `json:"internal_ip"` InternalNetmask string `json:"internal_mask"` // DNS 配置 DNSPrimary string `json:"dns_primary"` DNSSecondary string `json:"dns_secondary"` } // PageType 页面类型 type PageType int const ( PageAgreement PageType = iota PageData PagePublicNetwork PageInternalNetwork PageDNS PageSummary ) const ( FocusTypeInput int = 0 FocusTypePrev int = 1 FocusTypeNext int = 2 ) // model TUI 主模型 type model struct { config Config currentPage PageType totalPages int networkInterfaces []string // 所有系统网络接口 textInputs []textinput.Model inputLabels []string // 存储标签 focusIndex int focusType int // 0=输入框, 1=上一步按钮, 2=下一步按钮 agreementIdx int // 0=拒绝,1=接受 width int height int err error quitting bool done bool force bool } // 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{ Hostname: "cluster.hpc.org", Country: "China", Region: "Beijing", Timezone: "Asia/Shanghai", HomePage: "www.sunhpc.com", DBAddress: "/var/lib/sunhpc/sunhpc.db", DataAddress: "/export/sunhpc", PublicInterface: defaultPublicInterface, PublicIPAddress: "", PublicNetmask: "", PublicGateway: "", InternalInterface: defaultInternalInterface, InternalIPAddress: "172.16.9.254", InternalNetmask: "255.255.255.0", DNSPrimary: "8.8.8.8", DNSSecondary: "8.8.4.4", } } // initialModel 初始化模型 func initialModel() model { cfg := defaultConfig() m := model{ config: cfg, totalPages: 6, textInputs: make([]textinput.Model, 0), inputLabels: make([]string, 0), agreementIdx: 1, focusIndex: 0, focusType: 0, // 0=输入框, 1=上一步按钮, 2=下一步按钮 width: 80, height: 24, } m.initPageInputs() return m } // Init 初始化命令 func (m model) Init() tea.Cmd { return textinput.Blink } // Update 处理消息更新 func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd switch msg := msg.(type) { case tea.KeyMsg: switch msg.String() { case "ctrl+c": m.quitting = true return m, tea.Quit case "esc": if m.currentPage > 0 { return m.prevPage() } case "enter": return m.handleEnter() case "tab", "shift+tab", "up", "down", "left", "right": return m.handleNavigation(msg) } case tea.WindowSizeMsg: m.width = msg.Width m.height = msg.Height // 动态调整容器宽度 /* if msg.Width > 100 { containerStyle = containerStyle.Width(90) } else if msg.Width > 80 { containerStyle = containerStyle.Width(70) } else { containerStyle = containerStyle.Width(msg.Width - 10) } */ // ✅ 动态计算容器宽度(终端宽度的 80%) containerWidth := msg.Width * 80 / 100 // ✅ 重新设置容器样式宽度 containerStyle = containerStyle.Width(containerWidth) // 动态设置协议框宽度(容器宽度的 90%) agreementWidth := containerWidth * 80 / 100 agreementBox = agreementBox.Width(agreementWidth) // 动态设置输入框宽度 inputWidth := containerWidth * 60 / 100 if inputWidth < 40 { inputWidth = 40 } inputBox = inputBox.Width(inputWidth) // 动态设置总结框宽度 summaryWidth := containerWidth * 90 / 100 summaryBox = summaryBox.Width(summaryWidth) return m, nil } // 更新当前焦点的输入框 if len(m.textInputs) > 0 && m.focusIndex < len(m.textInputs) { var cmd tea.Cmd m.textInputs[m.focusIndex], cmd = m.textInputs[m.focusIndex].Update(msg) cmds = append(cmds, cmd) } return m, tea.Batch(cmds...) } // handleEnter 处理回车事件 func (m *model) handleEnter() (tea.Model, tea.Cmd) { switch m.currentPage { case PageAgreement: if m.agreementIdx == 1 { m.config.AgreementAccepted = true return m.nextPage() } else { m.quitting = true return m, tea.Quit } case PageData, PagePublicNetwork, PageInternalNetwork, PageDNS: // 根据焦点类型执行不同操作 switch m.focusType { case FocusTypeInput: // 在输入框上,保存并下一页 m.saveCurrentPage() return m.nextPage() case FocusTypePrev: // 上一步按钮,返回上一页 return m.prevPage() case FocusTypeNext: // 下一步按钮,切换到下一页 m.saveCurrentPage() return m.nextPage() } case PageSummary: switch m.focusIndex { case 0: // 执行 m.done = true if err := m.saveConfig(); err != nil { m.err = err return m, nil } return m, tea.Quit case 1: // 取消 m.quitting = true return m, tea.Quit } } return m, nil } // handleNavigation 处理导航 func (m *model) handleNavigation(msg tea.KeyMsg) (tea.Model, tea.Cmd) { // debug //fmt.Fprintf(os.Stderr, "DEBUG: key=%s page=%d\n", msg.String(), m.currentPage) switch m.currentPage { case PageAgreement: switch msg.String() { case "left", "right", "tab", "shift+tab", "up", "down": m.agreementIdx = 1 - m.agreementIdx } case PageSummary: switch msg.String() { case "left", "right", "tab", "shift+tab": m.focusIndex = 1 - m.focusIndex } default: // 输入框页面: 支持输入框和按钮之间切换 // totalFocusable := len(m.textInputs) + 2 switch msg.String() { case "down", "tab": // 当前在输入框 switch m.focusType { case FocusTypeInput: if m.focusIndex < len(m.textInputs)-1 { // 切换到下一个输入框 m.textInputs[m.focusIndex].Blur() m.focusIndex++ m.textInputs[m.focusIndex].Focus() } else { // 最后一个输入框,切换到“下一步”按钮 m.textInputs[m.focusIndex].Blur() m.focusIndex = 0 m.focusType = FocusTypeNext // 下一步按钮 } case FocusTypePrev: // 当前在“上一步”按钮,切换到第一个输入框 m.focusType = FocusTypeInput m.focusIndex = 0 m.textInputs[0].Focus() case FocusTypeNext: // 当前在“下一步”按钮,切换到“上一步”按钮 m.focusType = FocusTypePrev } case "up", "shift+tab": // 当前在输入框 switch m.focusType { case FocusTypeInput: if m.focusIndex > 0 { // 切换到上一个输入框 m.textInputs[m.focusIndex].Blur() m.focusIndex-- m.textInputs[m.focusIndex].Focus() } else { // 第一个输入框,切换到“上一步”按钮 m.textInputs[m.focusIndex].Blur() m.focusIndex = 0 m.focusType = FocusTypePrev // 上一步按钮 } case FocusTypeNext: // 当前在“下一步”按钮,切换到最后一个输入框 m.focusType = FocusTypeInput m.focusIndex = len(m.textInputs) - 1 m.textInputs[m.focusIndex].Focus() case FocusTypePrev: // 当前在“上一步”按钮,切换到“下一步”按钮 m.focusType = FocusTypeNext } } } return m, nil } // nextPage 下一页 func (m *model) nextPage() (tea.Model, tea.Cmd) { if m.currentPage < PageSummary { m.currentPage++ m.focusIndex = 0 m.initPageInputs() } return m, textinput.Blink } // prevPage 上一页 func (m *model) prevPage() (tea.Model, tea.Cmd) { if m.currentPage > 0 { m.saveCurrentPage() m.currentPage-- m.focusIndex = 0 m.initPageInputs() } return m, textinput.Blink }