113 lines
3.7 KiB
Go
113 lines
3.7 KiB
Go
package ui
|
|
|
|
import (
|
|
"github.com/charmbracelet/bubbles/list"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
)
|
|
|
|
func newMenuList() list.Model {
|
|
items := []list.Item{
|
|
simpleItem{id: "users", title: "Пользователи", desc: "Создать, заблокировать, удалить учётные записи"},
|
|
simpleItem{id: "catalog", title: "Хосты и сервисы", desc: "Общий каталог: адреса и порты, один раз на всех, группы просто выбирают из него"},
|
|
simpleItem{id: "groups", title: "Группы", desc: "Какие хосты из каталога входят в каждую группу"},
|
|
simpleItem{id: "export", title: "Экспорт архивов", desc: "Сгенерировать ключи и .7z архивы для доставки пользователям"},
|
|
simpleItem{id: "audit", title: "Аудит / кто в сети", desc: "Активные сессии и журнал подключений"},
|
|
simpleItem{id: "quit", title: "Выход", desc: ""},
|
|
}
|
|
return newList(items, "ssh-access-tui", 0, 0)
|
|
}
|
|
|
|
func (m Model) updateMenu(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
if key, ok := msg.(tea.KeyMsg); ok {
|
|
switch key.String() {
|
|
case "enter":
|
|
sel, ok := m.menu.SelectedItem().(simpleItem)
|
|
if !ok {
|
|
return m, nil
|
|
}
|
|
m.err, m.status = "", ""
|
|
switch sel.id {
|
|
case "users":
|
|
m.screen = scrUsers
|
|
m.refreshUsersList()
|
|
case "catalog":
|
|
m.screen = scrCatalogHosts
|
|
m.refreshCatalogHostsList()
|
|
case "groups":
|
|
m.screen = scrGroups
|
|
m.refreshGroupsList()
|
|
case "export":
|
|
m.screen = scrExportSelect
|
|
m.initExportSelect()
|
|
case "audit":
|
|
m.screen = scrAudit
|
|
m.refreshAudit()
|
|
case "quit":
|
|
return m.attemptQuit()
|
|
}
|
|
return m, nil
|
|
case "q":
|
|
return m.attemptQuit()
|
|
}
|
|
}
|
|
var cmd tea.Cmd
|
|
m.menu, cmd = m.menu.Update(msg)
|
|
return m, cmd
|
|
}
|
|
|
|
func (m Model) viewMenu() string {
|
|
return borderStyle.Render(m.menu.View())
|
|
}
|
|
|
|
// unexportedUsers returns active users who have never had a key issued
|
|
// (empty PublicKey) — created but forgotten before "Экспорт архивов".
|
|
func unexportedUsers(m *Model) []string {
|
|
st := m.loadState()
|
|
var out []string
|
|
for _, u := range st.Users {
|
|
if u.PublicKey == "" {
|
|
out = append(out, u.Username)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// attemptQuit is the only path that actually quits from the menu ("q" or
|
|
// selecting "Выход"): if there are users created but never exported, it
|
|
// warns instead of quitting straight away, since it's easy to create a
|
|
// batch of users, get interrupted, and forget the export step entirely.
|
|
// ctrl+c bypasses this deliberately, as an always-available hard exit.
|
|
func (m Model) attemptQuit() (tea.Model, tea.Cmd) {
|
|
pending := unexportedUsers(&m)
|
|
if len(pending) == 0 {
|
|
return m, tea.Quit
|
|
}
|
|
m.quitPending = pending
|
|
m.quitReturnTo = scrMenu
|
|
m.screen = scrQuitConfirm
|
|
return m, nil
|
|
}
|
|
|
|
func (m Model) updateQuitConfirm(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
if key, ok := msg.(tea.KeyMsg); ok {
|
|
switch key.String() {
|
|
case "enter", "y":
|
|
return m, tea.Quit
|
|
case "esc", "n":
|
|
m.screen = m.quitReturnTo
|
|
return m, nil
|
|
}
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
func (m Model) viewQuitConfirm() string {
|
|
body := warnStyle.Render("Есть пользователи, для которых ещё не сгенерирован ключ (не были в «Экспорт архивов»):") + "\n\n"
|
|
for _, u := range m.quitPending {
|
|
body += " - " + u + "\n"
|
|
}
|
|
body += "\nБез экспорта у них нет ключа и архива для передачи — доступ не работает.\n"
|
|
body += "\n[enter/y] выйти всё равно [esc/n] вернуться"
|
|
return borderStyle.Render(body)
|
|
}
|