add tui command

This commit is contained in:
2026-02-21 20:22:21 +08:00
parent fbe6aec707
commit ce9af9f7d0
11 changed files with 1194 additions and 239 deletions

366
pkg/wizard/pages.go Normal file
View File

@@ -0,0 +1,366 @@
package wizard
import (
"fmt"
"strings"
"github.com/charmbracelet/lipgloss"
)
// View 渲染视图
func (m model) View() string {
if m.done {
return successView()
}
if m.quitting {
return quitView()
}
if m.err != nil {
return errorView(m.err)
}
var page string
switch m.currentPage {
case PageAgreement:
page = m.agreementView()
case PageData:
page = m.dataView()
case PagePublicNetwork:
page = m.publicNetworkView()
case PageInternalNetwork:
page = m.internalNetworkView()
case PageDNS:
page = m.dnsView()
case PageSummary:
page = m.summaryView()
}
content := strings.Builder{}
content.WriteString(page)
content.WriteString("\n\n")
content.WriteString(progressView(m.currentPage, m.totalPages))
return containerStyle.Render(content.String())
}
// agreementView 协议页面
func (m model) agreementView() string {
title := titleStyle.Render("SunHPC 系统初始化向导")
subtitle := subTitleStyle.Render("请先阅读并同意以下协议")
agreement := agreementBox.Render(`
┌─────────────────────────────────────────────────────────────┐
│ SunHPC 软件许可协议 │
└─────────────────────────────────────────────────────────────┘
1. 许可授予
本软件授予您非独占、不可转让的使用许可。
2. 使用限制
- 不得用于非法目的
- 不得反向工程或反编译
- 不得移除版权标识
3. 免责声明
本软件按"原样"提供,不提供任何明示或暗示的保证。
4. 责任限制
在任何情况下,作者不对因使用本软件造成的任何损失负责。
5. 协议终止
如违反本协议条款,许可将自动终止。
───────────────────────────────────────────────────────────────
请仔细阅读以上条款,点击"接受"表示您同意并遵守本协议。
───────────────────────────────────────────────────────────────
`)
var acceptBtn, rejectBtn string
if m.agreementIdx == 0 {
rejectBtn = selectedButton.Render(">> 拒绝 <<")
acceptBtn = selectedButton.Render(" 同意 ")
} else {
rejectBtn = selectedButton.Render(" 拒绝 ")
acceptBtn = selectedButton.Render(">> 同意 <<")
}
buttonGroup := lipgloss.JoinHorizontal(
lipgloss.Center,
acceptBtn, " ", rejectBtn)
// ✅ 添加调试信息(确认 agreementIdx 的值)
// debugInfo := lipgloss.NewStyle().Foreground(lipgloss.Color("#888888")).
// Render(fmt.Sprintf("[DEBUG: idx=%d]", m.agreementIdx),)
hint := hintStyle.Render("使用 <- -> 或 Tab 选择,Enter 确认")
return lipgloss.JoinVertical(lipgloss.Center,
title, "",
subtitle, "",
agreement, "",
buttonGroup, "",
// debugInfo, "", // ✅ 显示调试信息
hint,
)
}
// dataView 数据接收页面
func (m model) dataView() string {
title := titleStyle.Render("集群基础配置")
subtitle := subTitleStyle.Render("请填写系统基本信息")
var inputs strings.Builder
for i, ti := range m.textInputs {
info := fmt.Sprintf("%-10s|", m.inputLabels[i])
input := inputBox.Render(info + ti.View())
inputs.WriteString(input + "\n")
}
buttons := m.renderNavButtons()
hint := hintStyle.Render("使用 Up/Down 或 Tab 切换、Enter 确认")
return lipgloss.JoinVertical(lipgloss.Center,
title, "",
subtitle, "",
inputs.String(), "",
buttons, "",
hint,
)
}
// publicNetworkView 公网设置页面
func (m model) publicNetworkView() string {
title := titleStyle.Render("公网配置")
subtitle := subTitleStyle.Render("请配置网络接口信息")
autoDetect := infoStyle.Render("[*] 自动检测网络接口: eth0, eth1, ens33")
var inputs strings.Builder
for i, ti := range m.textInputs {
info := fmt.Sprintf("%-10s|", m.inputLabels[i])
input := inputBox.Render(info + ti.View())
inputs.WriteString(input + "\n")
}
buttons := m.renderNavButtons()
hint := hintStyle.Render("使用 Up/Down 或 Tab 切换、Enter 确认")
return lipgloss.JoinVertical(lipgloss.Center,
title, "",
subtitle, "",
autoDetect, "",
inputs.String(), "",
buttons, "",
hint,
)
}
// internalNetworkView 内网配置页面
func (m model) internalNetworkView() string {
title := titleStyle.Render("内网配置")
subtitle := subTitleStyle.Render("请配置内网信息")
var inputs strings.Builder
for i, ti := range m.textInputs {
info := fmt.Sprintf("%-10s|", m.inputLabels[i])
input := inputBox.Render(info + ti.View())
inputs.WriteString(input + "\n")
}
buttons := m.renderNavButtons()
hint := hintStyle.Render("使用 Up/Down 或 Tab 切换、Enter 确认")
return lipgloss.JoinVertical(lipgloss.Center,
title, "",
subtitle, "",
inputs.String(), "",
buttons, "",
hint,
)
}
// dnsView DNS 配置页面
func (m model) dnsView() string {
title := titleStyle.Render("DNS 配置")
subtitle := subTitleStyle.Render("请配置 DNS 服务器")
var inputs strings.Builder
for i, ti := range m.textInputs {
info := fmt.Sprintf("%-10s|", m.inputLabels[i])
input := inputBox.Render(info + ti.View())
inputs.WriteString(input + "\n")
}
buttons := m.renderNavButtons()
hint := hintStyle.Render("使用 Up/Down 或 Tab 切换、Enter 确认")
return lipgloss.JoinVertical(lipgloss.Center,
title, "",
subtitle, "",
inputs.String(), "",
buttons, "",
hint,
)
}
// summaryView 总结页面
func (m model) summaryView() string {
title := titleStyle.Render("配置总结")
subtitle := subTitleStyle.Render("请确认以下配置信息")
summary := summaryBox.Render(fmt.Sprintf(`
+---------------------------------------------+
基本信息
+---------------------------------------------+
主机名:%-35s
国 家: %-31s
地 区:%-31s
时 区:%-38s
主 页:%-38s
+---------------------------------------------+
数据库
+---------------------------------------------+
地 址:%-38s
名 称:%-38s
软 件:%-33s
+---------------------------------------------+
公网配置
+---------------------------------------------+
接 口:%-38s
地 址: %-41s
掩 码:%-38s
网 关:%-38s
+---------------------------------------------+
内网配置
+---------------------------------------------+
接 口:%-38s
地 址: %-41s
掩 码:%-38s
+---------------------------------------------+
DNS
+---------------------------------------------+
主 DNS: %-37s
备 DNS: %-37s
+---------------------------------------------+
`,
m.config.Hostname,
m.config.Country,
m.config.Region,
m.config.Timezone,
m.config.HomePage,
m.config.DBAddress,
m.config.DBName,
m.config.DataAddress,
m.config.PublicInterface,
m.config.IPAddress,
m.config.Netmask,
m.config.Gateway,
m.config.InternalInterface,
m.config.InternalIP,
m.config.InternalMask,
m.config.DNSPrimary,
m.config.DNSSecondary,
))
var buttons string
if m.focusIndex == 0 {
buttons = selectedButton.Render("[>] 执行初始化") + " " + normalButton.Render("[ ] 取消")
} else {
buttons = normalButton.Render("[>] 执行初始化") + " " + selectedButton.Render("[ ] 取消")
}
hint := hintStyle.Render("使用 <- -> 或 Tab 选择Enter 确认")
return lipgloss.JoinVertical(lipgloss.Center,
title, "",
subtitle, "",
summary, "",
buttons, "",
hint,
)
}
// progressView 进度条
func progressView(current PageType, total int) string {
progress := ""
for i := 0; i < total; i++ {
if i < int(current) {
progress += "[+]"
} else if i == int(current) {
progress += "[-]"
} else {
progress += "[ ]"
}
if i < total-1 {
progress += " "
}
}
labels := []string{"协议", "数据", "公网", "内网", "DNS", "总结"}
label := labelStyle.Render(labels[current])
return progressStyle.Render(progress) + " " + label
}
// successView 成功视图
func successView() string {
return containerStyle.Render(lipgloss.JoinVertical(lipgloss.Center,
successTitle.Render("初始化完成!"), "",
successMsg.Render("系统配置已保存,正在初始化..."), "",
hintStyle.Render("按任意键退出"),
))
}
// quitView 退出视图
func quitView() string {
return containerStyle.Render(lipgloss.JoinVertical(lipgloss.Center,
errorTitle.Render("已取消"), "",
errorMsg.Render("初始化已取消,未保存任何配置"),
))
}
// errorView 错误视图
func errorView(err error) string {
return containerStyle.Render(lipgloss.JoinVertical(lipgloss.Center,
errorTitle.Render("错误"), "",
errorMsg.Render(err.Error()), "",
hintStyle.Render("按 Ctrl+C 退出"),
))
}
// navButtons 导航按钮
func navButtons(m model, prev, next string) string {
var btns string
if m.currentPage == 0 {
btns = normalButton.Render(prev) + " " + selectedButton.Render(next)
} else {
btns = selectedButton.Render(prev) + " " + normalButton.Render(next)
}
return btns
}
func (m model) renderNavButtons() string {
var prevBtn, nextBtn string
switch m.focusType {
case FocusTypePrev:
// 焦点在"上一步"
prevBtn = selectedButton.Render("<< 上一步 >>")
nextBtn = normalButton.Render("下一步 >>")
case FocusTypeNext:
// 焦点在"下一步"
prevBtn = normalButton.Render("<< 上一步")
nextBtn = selectedButton.Render("<< 下一步 >>")
default:
// 焦点在输入框
prevBtn = normalButton.Render("<< 上一步")
nextBtn = normalButton.Render("下一步 >>")
}
return lipgloss.JoinHorizontal(
lipgloss.Center,
prevBtn,
" ",
nextBtn,
)
}