Move UI & make a root model.

Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
This commit is contained in:
Hadi
2026-08-20 20:28:24 +02:00
parent e99f32ded8
commit e77eaf4e2c
12 changed files with 76 additions and 47 deletions
+55
View File
@@ -0,0 +1,55 @@
package main
import (
tea "charm.land/bubbletea/v2"
"github.com/anotherhadi/ilovetui/app"
"github.com/anotherhadi/ilovetui/modal"
"github.com/anotherhadi/ilovetui/notification"
"github.com/anotherhadi/usbguard-tui/ui"
)
type appModel struct {
core ui.Model
modals modal.Model
notif notification.Model
}
func newApp() appModel {
return appModel{
core: ui.New(),
modals: modal.New(),
notif: notification.New(),
}
}
func (a appModel) Init() tea.Cmd {
return tea.Batch(a.core.Init(), a.modals.Init(), a.notif.Init())
}
func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if _, ok := msg.(app.QuitMsg); ok {
return a, tea.Quit
}
a.core.ModalOpen = a.modals.Open()
var coreCmd tea.Cmd
a.core, coreCmd = a.core.Update(msg)
var modalCmd, notifCmd tea.Cmd
a.modals, modalCmd = a.modals.Update(msg)
a.notif, notifCmd = a.notif.Update(msg)
return a, tea.Batch(coreCmd, modalCmd, notifCmd)
}
func (a appModel) View() tea.View {
bg := a.core.View()
bg = a.modals.Render(bg)
bg = a.notif.Render(bg)
return tea.View{
Content: bg,
AltScreen: true,
WindowTitle: "USBGuard TUI",
MouseMode: tea.MouseModeCellMotion,
}
}
+1 -1
View File
@@ -25,7 +25,7 @@
src = ./.; src = ./.;
outputs = ["out"]; outputs = ["out"];
vendorHash = "sha256-tXMeJy9IpXTRhikYedcL+76H9X3In9mb1/KnN1XFPu4="; vendorHash = "sha256-8QP2FbNmvqrJhFe3Ia7tPhkxMeFWBhvr4Zr9kpV9bR4=";
meta = with pkgs.lib; { meta = with pkgs.lib; {
description = "A terminal UI for managing USB devices via usbguard."; description = "A terminal UI for managing USB devices via usbguard.";
+1 -1
View File
@@ -6,7 +6,7 @@ require (
charm.land/bubbles/v2 v2.1.0 charm.land/bubbles/v2 v2.1.0
charm.land/bubbletea/v2 v2.0.6 charm.land/bubbletea/v2 v2.0.6
charm.land/lipgloss/v2 v2.0.3 charm.land/lipgloss/v2 v2.0.3
github.com/anotherhadi/ilovetui v0.2.1 github.com/anotherhadi/ilovetui v0.2.2
github.com/charmbracelet/x/ansi v0.11.7 github.com/charmbracelet/x/ansi v0.11.7
) )
+2 -2
View File
@@ -14,8 +14,8 @@ github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I= github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/anotherhadi/ilovetui v0.2.1 h1:ndU52r0nzlT6LM53HNBvwVyvn3dRPKrbGVvzux9YXbk= github.com/anotherhadi/ilovetui v0.2.2 h1:ZOwyZRxFBZKVJeEAR+lg0xeQfsMZuymtKLwBV0MnBYY=
github.com/anotherhadi/ilovetui v0.2.1/go.mod h1:gAn69amLM3z0x8PV4obyf5bJntL/PTZusEuhu/xdFIY= github.com/anotherhadi/ilovetui v0.2.2/go.mod h1:gAn69amLM3z0x8PV4obyf5bJntL/PTZusEuhu/xdFIY=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o= github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
+1 -2
View File
@@ -6,7 +6,6 @@ import (
tea "charm.land/bubbletea/v2" tea "charm.land/bubbletea/v2"
"github.com/anotherhadi/usbguard-tui/internal/guard" "github.com/anotherhadi/usbguard-tui/internal/guard"
"github.com/anotherhadi/usbguard-tui/internal/ui"
) )
var version = "dev" var version = "dev"
@@ -22,7 +21,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
p := tea.NewProgram(ui.New()) p := tea.NewProgram(newApp())
if _, err := p.Run(); err != nil { if _, err := p.Run(); err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
os.Exit(1) os.Exit(1)
View File
+13 -38
View File
@@ -12,6 +12,7 @@ import (
"charm.land/lipgloss/v2" "charm.land/lipgloss/v2"
"github.com/charmbracelet/x/ansi" "github.com/charmbracelet/x/ansi"
"github.com/anotherhadi/ilovetui/app"
"github.com/anotherhadi/ilovetui/bubbles" "github.com/anotherhadi/ilovetui/bubbles"
"github.com/anotherhadi/ilovetui/helpbar" "github.com/anotherhadi/ilovetui/helpbar"
"github.com/anotherhadi/ilovetui/modal" "github.com/anotherhadi/ilovetui/modal"
@@ -41,8 +42,6 @@ type deviceSummary struct {
type Model struct { type Model struct {
list list.Model list list.Model
help helpbar.Model help helpbar.Model
modals modal.Model
notif notification.Model
daemonStatus string daemonStatus string
defaultPolicy guard.Status defaultPolicy guard.Status
deviceCounts deviceSummary deviceCounts deviceSummary
@@ -52,6 +51,8 @@ type Model struct {
rulesWritable bool rulesWritable bool
pendingRules []pendingRule pendingRules []pendingRule
fatalShown bool fatalShown bool
ModalOpen bool
} }
func New() Model { func New() Model {
@@ -76,8 +77,6 @@ func New() Model {
return Model{ return Model{
list: l, list: l,
help: h, help: h,
modals: modal.New(),
notif: notification.New(),
rulesManaged: rulesManaged, rulesManaged: rulesManaged,
rulesWritable: rulesWritable, rulesWritable: rulesWritable,
} }
@@ -117,21 +116,10 @@ func makeActionList(rulesManaged bool) list.Model {
} }
func (m Model) Init() tea.Cmd { func (m Model) Init() tea.Cmd {
return tea.Batch(fetchDevices, fetchDaemonStatus, fetchDefaultPolicy, tickCmd(), m.modals.Init(), m.notif.Init()) return tea.Batch(fetchDevices, fetchDaemonStatus, fetchDefaultPolicy, tickCmd(), app.SetTitle("USBGuard TUI"))
} }
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
next, cmd := m.updateMain(msg)
nm := next.(Model)
var modalCmd, notifCmd tea.Cmd
nm.modals, modalCmd = nm.modals.Update(msg)
nm.notif, notifCmd = nm.notif.Update(msg)
return nm, tea.Batch(cmd, modalCmd, notifCmd)
}
func (m Model) updateMain(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.WindowSizeMsg: case tea.WindowSizeMsg:
@@ -200,13 +188,13 @@ func (m Model) updateMain(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, fetchDevices return m, fetchDevices
case tea.KeyPressMsg: case tea.KeyPressMsg:
if m.modals.Open() { if m.ModalOpen {
return m, nil return m, nil
} }
return m.updateList(msg) return m.updateList(msg)
case tea.MouseWheelMsg: case tea.MouseWheelMsg:
if m.modals.Open() { if m.ModalOpen {
return m, nil return m, nil
} }
switch msg.Button { switch msg.Button {
@@ -218,7 +206,7 @@ func (m Model) updateMain(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil return m, nil
} }
if m.modals.Open() { if m.ModalOpen {
return m, nil return m, nil
} }
var cmd tea.Cmd var cmd tea.Cmd
@@ -226,15 +214,15 @@ func (m Model) updateMain(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, cmd return m, cmd
} }
func (m Model) updateList(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { func (m Model) updateList(msg tea.KeyPressMsg) (Model, tea.Cmd) {
if msg.String() == "ctrl+c" { if msg.String() == "ctrl+c" {
return m, tea.Quit return m, app.Quit()
} }
if !m.list.SettingFilter() { if !m.list.SettingFilter() {
dev, hasSelection := m.selectedDevice() dev, hasSelection := m.selectedDevice()
switch { switch {
case key.Matches(msg, listKeys.Quit): case key.Matches(msg, listKeys.Quit):
return m, tea.Quit return m, app.Quit()
case key.Matches(msg, listKeys.Refresh): case key.Matches(msg, listKeys.Refresh):
return m, tea.Batch(fetchDevices, fetchDaemonStatus, fetchDefaultPolicy) return m, tea.Batch(fetchDevices, fetchDaemonStatus, fetchDefaultPolicy)
case key.Matches(msg, listKeys.Help): case key.Matches(msg, listKeys.Help):
@@ -274,24 +262,11 @@ func (m Model) updateList(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
return m, cmd return m, cmd
} }
func (m Model) View() tea.View { func (m Model) View() string {
return tea.View{
Content: m.renderContent(),
AltScreen: true,
WindowTitle: "USBGuard TUI",
MouseMode: tea.MouseModeCellMotion,
}
}
func (m Model) renderContent() string {
header := m.renderHeader() header := m.renderHeader()
listView := strings.TrimRight(m.list.View(), "\n") listView := strings.TrimRight(m.list.View(), "\n")
helpView := strings.TrimRight(m.help.View(), "\n") helpView := strings.TrimRight(m.help.View(), "\n")
bg := strings.Join([]string{header, listView, helpView}, "\n") return strings.Join([]string{header, listView, helpView}, "\n")
bg = m.modals.Render(bg)
bg = m.notif.Render(bg)
return bg
} }
func (m Model) renderHeader() string { func (m Model) renderHeader() string {
@@ -5,6 +5,7 @@ import (
tea "charm.land/bubbletea/v2" tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2" "charm.land/lipgloss/v2"
"github.com/anotherhadi/ilovetui/app"
"github.com/anotherhadi/ilovetui/modal" "github.com/anotherhadi/ilovetui/modal"
"github.com/anotherhadi/ilovetui/style" "github.com/anotherhadi/ilovetui/style"
) )
@@ -25,7 +26,7 @@ func (permissionModal) Init() tea.Cmd { return nil }
func (m permissionModal) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m permissionModal) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg.(type) { switch msg.(type) {
case tea.KeyPressMsg, tea.MouseClickMsg: case tea.KeyPressMsg, tea.MouseClickMsg:
return m, tea.Quit return m, app.Quit()
} }
return m, nil return m, nil
} }
@@ -35,8 +36,7 @@ func (permissionModal) View() tea.View {
hintStyle := lipgloss.NewStyle().Foreground(style.S.Muted) hintStyle := lipgloss.NewStyle().Foreground(style.S.Muted)
parts := []string{ parts := []string{
textStyle.Render("Can't reach the usbguard daemon"), textStyle.Render("Can't reach the usbguard daemon (permission denied)."),
textStyle.Render("(permission denied)."),
textStyle.Render("Join the usbguard group, or use sudo."), textStyle.Render("Join the usbguard group, or use sudo."),
hintStyle.Render("press any key to quit"), hintStyle.Render("press any key to quit"),
} }