266 lines
5.9 KiB
Go
266 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"felis.lolicon.best/internal/cfsetup"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
)
|
|
|
|
// ---- Messages: sub-model → root ----
|
|
|
|
type pgDoneMsg struct{ err error }
|
|
|
|
type migrationDoneMsg struct {
|
|
n int
|
|
err error
|
|
}
|
|
|
|
type ownerResultMsg struct {
|
|
username string
|
|
displayPassword string
|
|
mode string
|
|
accountable string
|
|
auditWarning string
|
|
err error
|
|
}
|
|
|
|
type edgeResultMsg struct {
|
|
result *cfsetup.Result
|
|
panelHostname string
|
|
adminHostname string
|
|
err error
|
|
}
|
|
|
|
type panelCheckMsg struct{ result panelAccessResult }
|
|
|
|
// switchToDashboard tells the root to show the dashboard.
|
|
type switchToDashboard struct{}
|
|
|
|
// ---- rootModel: top-level session ----
|
|
|
|
type rootModel struct {
|
|
ctx context.Context
|
|
|
|
screen tea.Model // current active screen
|
|
dashboard *dashboardModel // always preserved
|
|
|
|
result breakGlassResult
|
|
err error
|
|
|
|
mode consoleMode
|
|
dbURL string
|
|
store ownerStore
|
|
osUser string
|
|
rootDomain string
|
|
adminHost string
|
|
panelHost string
|
|
adminExists bool
|
|
}
|
|
|
|
func newRootModel(ctx context.Context, store ownerStore, dbURL, rootDomain, adminHostname, panelHostname, osUser string, adminExists bool, mode consoleMode) *rootModel {
|
|
rm := &rootModel{
|
|
ctx: ctx,
|
|
dbURL: dbURL,
|
|
store: store,
|
|
osUser: osUser,
|
|
rootDomain: rootDomain,
|
|
adminHost: adminHostname,
|
|
panelHost: panelHostname,
|
|
adminExists: adminExists,
|
|
mode: mode,
|
|
dashboard: newDashboardModel(ctx, store, dbURL, osUser, rootDomain, adminHostname, panelHostname),
|
|
result: breakGlassResult{
|
|
osUser: osUser,
|
|
rootDomain: rootDomain,
|
|
adminHostname: adminHostname,
|
|
},
|
|
}
|
|
rm.dashboard.adminExists = adminExists
|
|
rm.refreshDashboard()
|
|
if mode == consoleModeBreakGlass {
|
|
rm.screen = newOwnerModel(ctx, store, osUser, adminExists)
|
|
} else {
|
|
rm.screen = rm.dashboard
|
|
}
|
|
return rm
|
|
}
|
|
|
|
func (m *rootModel) Init() tea.Cmd {
|
|
if m.mode == consoleModeSetup {
|
|
return m.checkStatus()
|
|
}
|
|
return m.screen.Init()
|
|
}
|
|
|
|
func (m *rootModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
switch msg := msg.(type) {
|
|
case pgDoneMsg:
|
|
if msg.err != nil {
|
|
m.dashboard.pgStatus = statusFailed
|
|
m.dashboard.pgDetail = msg.err.Error()
|
|
m.showDashboard()
|
|
return m, nil
|
|
}
|
|
m.dashboard.pgStatus = statusDone
|
|
m.dashboard.pgDetail = "ready"
|
|
m.showDashboard()
|
|
return m, m.checkMigrations()
|
|
|
|
case migrationDoneMsg:
|
|
if msg.err == nil {
|
|
m.dashboard.mgStatus = statusDone
|
|
m.dashboard.mgDetail = fmt.Sprintf("%d applied", msg.n)
|
|
} else {
|
|
m.dashboard.mgStatus = statusFailed
|
|
m.dashboard.mgDetail = msg.err.Error()
|
|
}
|
|
m.showDashboard()
|
|
if msg.err != nil {
|
|
return m, nil
|
|
}
|
|
return m, m.checkPanel()
|
|
|
|
case ownerResultMsg:
|
|
if msg.err != nil {
|
|
if m.mode == consoleModeBreakGlass {
|
|
m.err = msg.err
|
|
return m, tea.Quit
|
|
}
|
|
m.dashboard.owStatus = statusFailed
|
|
m.dashboard.owDetail = msg.err.Error()
|
|
m.showDashboard()
|
|
return m, nil
|
|
}
|
|
m.result.provisioned = true
|
|
m.result.username = msg.username
|
|
m.result.displayPassword = msg.displayPassword
|
|
m.result.mode = msg.mode
|
|
m.result.accountable = msg.accountable
|
|
m.result.auditWarning = msg.auditWarning
|
|
if m.mode == consoleModeBreakGlass {
|
|
return m, tea.Quit
|
|
}
|
|
m.adminExists = true
|
|
m.dashboard.adminExists = true
|
|
m.dashboard.owStatus = statusDone
|
|
m.dashboard.owDetail = msg.username
|
|
m.showDashboard()
|
|
return m, m.checkPanel()
|
|
|
|
case panelCheckMsg:
|
|
m.result.panelURL = msg.result.url
|
|
if msg.result.err != nil {
|
|
m.dashboard.paStatus = statusFailed
|
|
m.dashboard.paDetail = msg.result.err.Error()
|
|
} else {
|
|
m.dashboard.paStatus = statusDone
|
|
m.dashboard.paDetail = msg.result.url
|
|
}
|
|
m.showDashboard()
|
|
return m, nil
|
|
|
|
case edgeResultMsg:
|
|
if msg.err != nil {
|
|
if m.mode == consoleModeBreakGlass {
|
|
m.err = msg.err
|
|
return m, tea.Quit
|
|
}
|
|
m.dashboard.egStatus = statusFailed
|
|
m.dashboard.egDetail = msg.err.Error()
|
|
m.showDashboard()
|
|
return m, nil
|
|
}
|
|
m.result.edgeConfigured = true
|
|
m.result.edgeAud = msg.result.AccessAud
|
|
m.result.edgeRoutedHosts = msg.result.RoutedHostnames
|
|
m.result.edgeConfigPath = msg.result.ConfigPath
|
|
m.result.edgePanelHostname = msg.panelHostname
|
|
m.result.edgeAdminHostname = msg.adminHostname
|
|
if m.mode == consoleModeBreakGlass {
|
|
return m, tea.Quit
|
|
}
|
|
m.dashboard.egStatus = statusDone
|
|
m.dashboard.egDetail = "configured"
|
|
m.showDashboard()
|
|
return m, nil
|
|
|
|
case switchToDashboard:
|
|
m.refreshDashboard()
|
|
return m, m.checkStatus()
|
|
|
|
}
|
|
|
|
if m.screen != nil {
|
|
newScreen, cmd := m.screen.Update(msg)
|
|
if newScreen != nil {
|
|
if newScreen != m.screen {
|
|
m.screen = newScreen
|
|
return m, tea.Batch(cmd, m.screen.Init())
|
|
}
|
|
}
|
|
return m, cmd
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
func (m *rootModel) View() string {
|
|
if m.screen != nil {
|
|
return m.screen.View()
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (m *rootModel) showDashboard() {
|
|
m.screen = m.dashboard
|
|
}
|
|
|
|
func (m *rootModel) refreshDashboard() {
|
|
d := m.dashboard
|
|
d.pgStatus = statusPending
|
|
d.mgStatus = statusPending
|
|
d.owStatus = statusOptional
|
|
d.paStatus = statusPending
|
|
d.egStatus = statusOptional
|
|
if m.adminExists {
|
|
d.owStatus = statusDone
|
|
d.owDetail = "already exists (use breakGlass to reset)"
|
|
}
|
|
if m.result.provisioned {
|
|
d.owStatus = statusDone
|
|
d.owDetail = m.result.username
|
|
}
|
|
if m.result.edgeConfigured {
|
|
d.egStatus = statusDone
|
|
d.egDetail = "configured"
|
|
}
|
|
if m.result.panelURL != "" {
|
|
d.paStatus = statusDone
|
|
d.paDetail = m.result.panelURL
|
|
}
|
|
}
|
|
|
|
func (m *rootModel) checkStatus() tea.Cmd {
|
|
return func() tea.Msg {
|
|
if err := checkPostgres(m.dbURL); err != nil {
|
|
return pgDoneMsg{err: err}
|
|
}
|
|
return pgDoneMsg{}
|
|
}
|
|
}
|
|
|
|
func (m *rootModel) checkMigrations() tea.Cmd {
|
|
return func() tea.Msg {
|
|
n, err := countMigrations(m.dbURL)
|
|
return migrationDoneMsg{n: n, err: err}
|
|
}
|
|
}
|
|
|
|
func (m *rootModel) checkPanel() tea.Cmd {
|
|
return func() tea.Msg {
|
|
return panelCheckMsg{result: checkPanelAccess(m.rootDomain)}
|
|
}
|
|
}
|