add edit and all in one menu for hosts and services
This commit is contained in:
1 parent
8c1b93c14e
commit
e39ad275b5
15 files changed
+1136
-675
No files matched your search
+89
-60
@@ -29,13 +29,14 @@ const (
|
||||
scrUsers
|
||||
scrUserNew
|
||||
scrUserNewGroups
|
||||
scrCatalogHosts
|
||||
scrHostForm
|
||||
scrCatalogServices
|
||||
scrServiceForm
|
||||
scrGroups
|
||||
scrGroupHosts
|
||||
scrHostNew
|
||||
scrHostServices
|
||||
scrServiceNew
|
||||
scrGroupForm
|
||||
scrGroupHostsPick
|
||||
scrExportSelect
|
||||
scrExportConfirm
|
||||
scrExportResult
|
||||
scrConfigUpdateResult
|
||||
scrAudit
|
||||
@@ -54,17 +55,37 @@ const (
|
||||
confirmDeleteUser
|
||||
confirmDeleteHost
|
||||
confirmDeleteService
|
||||
confirmDeleteGroup
|
||||
confirmExportRotate
|
||||
)
|
||||
|
||||
// Colors are deliberately ANSI-native (0-15) rather than fixed 256-color
|
||||
// codes: those map to whatever palette the user's own terminal is themed
|
||||
// with, so they stay legible in both light and dark terminals. The bubbles
|
||||
// list widget's default title style paints a fixed 256-color background
|
||||
// ("62", blue-purple) with near-white text, which reads badly in many
|
||||
// terminal themes — newList below overrides it with a plain bold
|
||||
// foreground and no background fill for the same reason.
|
||||
var (
|
||||
titleStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("212")).Padding(0, 1)
|
||||
errStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("196"))
|
||||
okStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("42"))
|
||||
helpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
|
||||
warnStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("214"))
|
||||
borderStyle = lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).BorderForeground(lipgloss.Color("240")).Padding(1, 2)
|
||||
titleStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("5")).Padding(0, 1)
|
||||
errStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("1"))
|
||||
okStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("2"))
|
||||
warnStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("3"))
|
||||
borderStyle = lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).BorderForeground(lipgloss.Color("8")).Padding(1, 2)
|
||||
|
||||
listTitleStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("6")).Padding(0, 1)
|
||||
)
|
||||
|
||||
// newList builds a list.Model with the corrected (readable) title style
|
||||
// applied, so every screen gets it instead of bubbles' low-contrast default.
|
||||
func newList(items []list.Item, title string, w, h int) list.Model {
|
||||
l := list.New(items, list.NewDefaultDelegate(), w, h)
|
||||
l.Title = title
|
||||
l.Styles.Title = listTitleStyle
|
||||
l.SetShowStatusBar(false)
|
||||
return l
|
||||
}
|
||||
|
||||
// Model is the root bubbletea model for the whole application.
|
||||
type Model struct {
|
||||
cfg config.Config
|
||||
@@ -86,23 +107,31 @@ type Model struct {
|
||||
userNewCursor int
|
||||
userGroupsEditTarget string // non-empty: editing this existing user's groups instead of creating a new user
|
||||
|
||||
groupsList list.Model
|
||||
groupNewName textinput.Model
|
||||
awaitingGroup bool
|
||||
selectedGroup string
|
||||
hostsList list.Model
|
||||
hostForm [2]textinput.Model // name, address
|
||||
hostFormFocus int
|
||||
// catalog: hosts and their services, shared across groups
|
||||
catalogHostsList list.Model
|
||||
hostForm [3]textinput.Model // name, address, description
|
||||
hostFormFocus int
|
||||
hostFormEditTarget string // "" = creating a new host
|
||||
selectedHost string // catalog host currently open in scrCatalogServices/scrServiceForm
|
||||
catalogServicesList list.Model
|
||||
serviceForm [3]textinput.Model // name, port, description
|
||||
serviceFormFocus int
|
||||
serviceFormEditTarget string // "" = creating a new service
|
||||
|
||||
selectedHost string
|
||||
servicesList list.Model
|
||||
serviceForm [3]textinput.Model // name, port, description
|
||||
serviceFormFocus int
|
||||
// groups: named policies that reference a subset of catalog hosts
|
||||
groupsList list.Model
|
||||
groupForm [2]textinput.Model // name, description
|
||||
groupFormFocus int
|
||||
groupFormEditTarget string // "" = creating a new group
|
||||
selectedGroup string
|
||||
groupHostsPickSel map[string]bool
|
||||
groupHostsPickOrder []string
|
||||
groupHostsPickCursor int
|
||||
|
||||
exportSel map[string]bool
|
||||
exportOrder []string
|
||||
exportCursor int
|
||||
exportChosen []string // confirmed selection, pending scrExportConfirm -> runExport
|
||||
exportChosen []string // selection pending confirmation (if any rotation) or already run
|
||||
exportRes []keymgr.BuildResult
|
||||
exportPass string
|
||||
exportErr string
|
||||
@@ -114,14 +143,17 @@ type Model struct {
|
||||
quitPending []string // usernames with no key issued yet, shown on scrQuitConfirm
|
||||
|
||||
// generic confirmation for destructive actions (lock/delete user,
|
||||
// delete host/service) — see confirm.go. confirmTarget holds the
|
||||
// username/host/service name being acted on; for host/service deletion
|
||||
// the group (and host, for a service) context is the already-present
|
||||
// selectedGroup/selectedHost fields above.
|
||||
// delete host/service/group, export key rotation) — see confirm.go.
|
||||
// Confirming requires typing back a freshly generated token, not just
|
||||
// pressing enter/y, so a destructive action can never fire from a
|
||||
// reflexive keypress.
|
||||
pendingConfirm confirmKind
|
||||
confirmTarget string
|
||||
confirmMessage string
|
||||
confirmReturnTo screen
|
||||
confirmToken string
|
||||
confirmInput textinput.Model
|
||||
confirmError string
|
||||
|
||||
configUpdateUser string
|
||||
configUpdateArchive string
|
||||
@@ -140,20 +172,13 @@ type Model struct {
|
||||
func New(cfg config.Config, st *store.Store) Model {
|
||||
m := Model{cfg: cfg, st: st}
|
||||
m.menu = newMenuList()
|
||||
m.usersList = emptyList("Пользователи")
|
||||
m.groupsList = emptyList("Группы доступа")
|
||||
m.hostsList = emptyList("Хосты")
|
||||
m.servicesList = emptyList("Сервисы")
|
||||
m.usersList = newList(nil, "Пользователи", 0, 0)
|
||||
m.catalogHostsList = newList(nil, "Хосты и сервисы", 0, 0)
|
||||
m.catalogServicesList = newList(nil, "Сервисы", 0, 0)
|
||||
m.groupsList = newList(nil, "Группы", 0, 0)
|
||||
return m
|
||||
}
|
||||
|
||||
func emptyList(title string) list.Model {
|
||||
l := list.New(nil, list.NewDefaultDelegate(), 0, 0)
|
||||
l.Title = title
|
||||
l.SetShowStatusBar(false)
|
||||
return l
|
||||
}
|
||||
|
||||
func (m Model) Init() tea.Cmd { return nil }
|
||||
|
||||
func (m *Model) loadState() *model.State {
|
||||
@@ -195,9 +220,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
listW, listH := msg.Width-4, msg.Height-8
|
||||
m.menu.SetSize(listW, listH)
|
||||
m.usersList.SetSize(listW, listH)
|
||||
m.catalogHostsList.SetSize(listW, listH)
|
||||
m.catalogServicesList.SetSize(listW, listH)
|
||||
m.groupsList.SetSize(listW, listH)
|
||||
m.hostsList.SetSize(listW, listH)
|
||||
m.servicesList.SetSize(listW, listH)
|
||||
return m, nil
|
||||
case tea.KeyMsg:
|
||||
if msg.String() == "ctrl+c" {
|
||||
@@ -214,20 +239,22 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m.updateUserNew(msg)
|
||||
case scrUserNewGroups:
|
||||
return m.updateUserNewGroups(msg)
|
||||
case scrCatalogHosts:
|
||||
return m.updateCatalogHosts(msg)
|
||||
case scrHostForm:
|
||||
return m.updateHostForm(msg)
|
||||
case scrCatalogServices:
|
||||
return m.updateCatalogServices(msg)
|
||||
case scrServiceForm:
|
||||
return m.updateServiceForm(msg)
|
||||
case scrGroups:
|
||||
return m.updateGroups(msg)
|
||||
case scrGroupHosts:
|
||||
return m.updateGroupHosts(msg)
|
||||
case scrHostNew:
|
||||
return m.updateHostNew(msg)
|
||||
case scrHostServices:
|
||||
return m.updateHostServices(msg)
|
||||
case scrServiceNew:
|
||||
return m.updateServiceNew(msg)
|
||||
case scrGroupForm:
|
||||
return m.updateGroupForm(msg)
|
||||
case scrGroupHostsPick:
|
||||
return m.updateGroupHostsPick(msg)
|
||||
case scrExportSelect:
|
||||
return m.updateExportSelect(msg)
|
||||
case scrExportConfirm:
|
||||
return m.updateExportConfirm(msg)
|
||||
case scrExportResult:
|
||||
return m.updateExportResult(msg)
|
||||
case scrConfigUpdateResult:
|
||||
@@ -253,20 +280,22 @@ func (m Model) View() string {
|
||||
body = m.viewUserNew()
|
||||
case scrUserNewGroups:
|
||||
body = m.viewUserNewGroups()
|
||||
case scrCatalogHosts:
|
||||
body = m.viewCatalogHosts()
|
||||
case scrHostForm:
|
||||
body = m.viewHostForm()
|
||||
case scrCatalogServices:
|
||||
body = m.viewCatalogServices()
|
||||
case scrServiceForm:
|
||||
body = m.viewServiceForm()
|
||||
case scrGroups:
|
||||
body = m.viewGroups()
|
||||
case scrGroupHosts:
|
||||
body = m.viewGroupHosts()
|
||||
case scrHostNew:
|
||||
body = m.viewHostNew()
|
||||
case scrHostServices:
|
||||
body = m.viewHostServices()
|
||||
case scrServiceNew:
|
||||
body = m.viewServiceNew()
|
||||
case scrGroupForm:
|
||||
body = m.viewGroupForm()
|
||||
case scrGroupHostsPick:
|
||||
body = m.viewGroupHostsPick()
|
||||
case scrExportSelect:
|
||||
body = m.viewExportSelect()
|
||||
case scrExportConfirm:
|
||||
body = m.viewExportConfirm()
|
||||
case scrExportResult:
|
||||
body = m.viewExportResult()
|
||||
case scrConfigUpdateResult:
|
||||
|
||||
Reference in new issue
Block a user