342 lines
11 KiB
Go
342 lines
11 KiB
Go
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"`
|
||
State string `json:"state"`
|
||
City string `json:"city"`
|
||
Contact string `json:"contact"`
|
||
Timezone string `json:"timezone"`
|
||
HomePage string `json:"homepage"`
|
||
DBAddress string `json:"db_address"`
|
||
DistroDir string `json:"distro_dir"`
|
||
|
||
// 公网设置
|
||
PublicHostname string `json:"public_hostname"`
|
||
PublicInterface string `json:"public_interface"`
|
||
PublicIPAddress string `json:"ip_address"`
|
||
PublicNetmask string `json:"netmask"`
|
||
PublicGateway string `json:"gateway"`
|
||
PublicDomain string `json:"public_domain"`
|
||
PublicMTU string `json:"public_mtu"`
|
||
|
||
// 内网配置
|
||
PrivateHostname string `json:"private_hostname"`
|
||
PrivateInterface string `json:"private_interface"`
|
||
PrivateIPAddress string `json:"private_ip"`
|
||
PrivateNetmask string `json:"private_mask"`
|
||
PrivateDomain string `json:"private_domain"`
|
||
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",
|
||
State: "Beijing",
|
||
City: "Beijing",
|
||
Contact: "admin@sunhpc.com",
|
||
Timezone: "Asia/Shanghai",
|
||
HomePage: "www.sunhpc.com",
|
||
DBAddress: "/var/lib/sunhpc/sunhpc.db",
|
||
DistroDir: "/export/sunhpc",
|
||
PublicHostname: "cluster.hpc.org",
|
||
PublicInterface: defaultPublicInterface,
|
||
PublicIPAddress: "",
|
||
PublicNetmask: "",
|
||
PublicGateway: "",
|
||
PublicDomain: "hpc.org",
|
||
PublicMTU: "1500",
|
||
PrivateHostname: "cluster",
|
||
PrivateInterface: defaultInternalInterface,
|
||
PrivateIPAddress: "172.16.9.254",
|
||
PrivateNetmask: "255.255.255.0",
|
||
PrivateDomain: "local",
|
||
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["State_input"] = NewTextInput("State", cfg.State)
|
||
page2Comps["City_input"] = NewTextInput("City", cfg.City)
|
||
page2Comps["Contact_input"] = NewTextInput("Contact", cfg.Contact)
|
||
page2Comps["Timezone_input"] = NewTextInput("Timezone", cfg.Timezone)
|
||
page2Comps["DistroDir_input"] = NewTextInput("DistroDir", cfg.DistroDir)
|
||
page2Comps["next_btn"] = NewButton("下一步")
|
||
page2Comps["prev_btn"] = NewButton("上一步")
|
||
pageComponents[PageData] = page2Comps
|
||
|
||
// ------------------ 页面3:公网网络页面 --------------------
|
||
page3Comps := make(map[string]Focusable)
|
||
page3Comps["PublicHostname_input"] = NewTextInput("PublicHostname", cfg.PublicHostname)
|
||
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["PublicDomain_input"] = NewTextInput("PublicDomain", cfg.PublicDomain)
|
||
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["PrivateHostname_input"] = NewTextInput("PrivateHostname", cfg.PrivateHostname)
|
||
page4Comps["PrivateInterface_input"] = NewTextInput("PrivateInterface", cfg.PrivateInterface)
|
||
page4Comps["PrivateIPAddress_input"] = NewTextInput("PrivateIPAddress", cfg.PrivateIPAddress)
|
||
page4Comps["PrivateNetmask_input"] = NewTextInput("PrivateNetmask", cfg.PrivateNetmask)
|
||
page4Comps["PrivateDomain_input"] = NewTextInput("PrivateDomain", cfg.PrivateDomain)
|
||
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 "State_input":
|
||
m.config.State = comp.Value()
|
||
case "City_input":
|
||
m.config.City = comp.Value()
|
||
case "Contact_input":
|
||
m.config.Contact = comp.Value()
|
||
case "Timezone_input":
|
||
m.config.Timezone = comp.Value()
|
||
case "DistroDir_input":
|
||
m.config.DistroDir = 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
|
||
}
|