6 Commits

Author SHA1 Message Date
Hadi 031ff48696 v1.1.0
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-05-26 20:14:22 +02:00
Hadi 95d7f368e1 Add ilovetui integration
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-05-26 20:14:08 +02:00
Hadi 64b36e716c Merge branch 'main' of github.com:anotherhadi/usbguard-tui 2026-05-11 20:10:07 +02:00
Hadi 85184dafca Add FUNDING.yml
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-05-11 20:10:01 +02:00
Hadi 6db3a32758 Merge branch 'main' of github.com:anotherhadi/usbguard-tui 2026-05-06 14:42:02 +02:00
Hadi 1ac92a5ace Print nixos rules on exit
Signed-off-by: Hadi <hadi@example.com>
2026-05-06 14:41:50 +02:00
10 changed files with 136 additions and 55 deletions
+1
View File
@@ -0,0 +1 @@
ko_fi: anotherhadi
+2
View File
@@ -19,6 +19,8 @@ USBGuard is a software framework for implementing a USB device authorization pol
Built with [bubbletea](https://github.com/charmbracelet/bubbletea) & Golang! Built with [bubbletea](https://github.com/charmbracelet/bubbletea) & Golang!
Colors and styles can be customized using [ilovetui](https://github.com/anotherhadi/ilovetui), which applies theme changes across all compatible TUI applications at once.
## Features ## Features
- List all connected USB devices with their current status (allowed, blocked, rejected) - List all connected USB devices with their current status (allowed, blocked, rejected)
+2 -2
View File
@@ -14,7 +14,7 @@
(system: f system (import nixpkgs {inherit system;})); (system: f system (import nixpkgs {inherit system;}));
pname = "usbguard-tui"; pname = "usbguard-tui";
version = "1.0.1"; version = "1.1.0";
ldflags = ["-s" "-w" "-X main.version=${version}"]; ldflags = ["-s" "-w" "-X main.version=${version}"];
in { in {
@@ -25,7 +25,7 @@
src = ./.; src = ./.;
outputs = ["out"]; outputs = ["out"];
vendorHash = "sha256-NfmNdQaISob3ZguQnwgfXHUDUFION988ucp18q996pM="; vendorHash = "sha256-tXMeJy9IpXTRhikYedcL+76H9X3In9mb1/KnN1XFPu4=";
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.";
+13
View File
@@ -10,20 +10,33 @@ require (
) )
require ( require (
charm.land/glamour/v2 v2.0.0 // indirect
github.com/alecthomas/chroma/v2 v2.14.0 // indirect
github.com/anotherhadi/ilovetui v0.1.6 // indirect
github.com/atotto/clipboard v0.1.4 // indirect github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/charmbracelet/colorprofile v0.4.3 // indirect github.com/charmbracelet/colorprofile v0.4.3 // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468 // indirect github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468 // indirect
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/charmbracelet/x/termios v0.1.1 // indirect github.com/charmbracelet/x/termios v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.2.2 // indirect github.com/charmbracelet/x/windows v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/dlclark/regexp2 v1.11.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
github.com/mattn/go-runewidth v0.0.23 // indirect github.com/mattn/go-runewidth v0.0.23 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/sahilm/fuzzy v0.1.1 // indirect github.com/sahilm/fuzzy v0.1.1 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yuin/goldmark v1.7.8 // indirect
github.com/yuin/goldmark-emoji v1.0.5 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.43.0 // indirect golang.org/x/sys v0.43.0 // indirect
golang.org/x/text v0.24.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )
+28
View File
@@ -2,12 +2,20 @@ charm.land/bubbles/v2 v2.1.0 h1:YSnNh5cPYlYjPxRrzs5VEn3vwhtEn3jVGRBT3M7/I0g=
charm.land/bubbles/v2 v2.1.0/go.mod h1:l97h4hym2hvWBVfmJDtrEHHCtkIKeTEb3TTJ4ZOB3wY= charm.land/bubbles/v2 v2.1.0/go.mod h1:l97h4hym2hvWBVfmJDtrEHHCtkIKeTEb3TTJ4ZOB3wY=
charm.land/bubbletea/v2 v2.0.6 h1:UHN/91OyuhaOFGSrBXQ/hMZD8IO1Uc4BvHlgHXL2WJo= charm.land/bubbletea/v2 v2.0.6 h1:UHN/91OyuhaOFGSrBXQ/hMZD8IO1Uc4BvHlgHXL2WJo=
charm.land/bubbletea/v2 v2.0.6/go.mod h1:MH/D8ZLlN3op37vQvijKuU29g3rqTp+aQapURFonF9g= charm.land/bubbletea/v2 v2.0.6/go.mod h1:MH/D8ZLlN3op37vQvijKuU29g3rqTp+aQapURFonF9g=
charm.land/glamour/v2 v2.0.0 h1:IDBoqLEy7Hdpb9VOXN+khLP/XSxtJy1VsHuW/yF87+U=
charm.land/glamour/v2 v2.0.0/go.mod h1:kjq9WB0s8vuUYZNYey2jp4Lgd9f4cKdzAw88FZtpj/w=
charm.land/lipgloss/v2 v2.0.3 h1:yM2zJ4Cf5Y51b7RHIwioil4ApI/aypFXXVHSwlM6RzU= charm.land/lipgloss/v2 v2.0.3 h1:yM2zJ4Cf5Y51b7RHIwioil4ApI/aypFXXVHSwlM6RzU=
charm.land/lipgloss/v2 v2.0.3/go.mod h1:7myLU9iG/3xluAWzpY/fSxYYHCgoKTie7laxk6ATwXA= charm.land/lipgloss/v2 v2.0.3/go.mod h1:7myLU9iG/3xluAWzpY/fSxYYHCgoKTie7laxk6ATwXA=
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
github.com/anotherhadi/ilovetui v0.1.6 h1:NKg+T1DpV08Q4r+iowFrXF+0bTd6Y2f4OFpFwhsfsyY=
github.com/anotherhadi/ilovetui v0.1.6/go.mod h1:HVai6u5NGKSMOpmioYpwrN0lSxQjc7HtISUc5hTwvOw=
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=
github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w= github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q= github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q= github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468 h1:Q9fO0y1Zo5KB/5Vu8JZoLGm1N3RzF9bNj3Ao3xoR+Ac= github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468 h1:Q9fO0y1Zo5KB/5Vu8JZoLGm1N3RzF9bNj3Ao3xoR+Ac=
@@ -16,6 +24,8 @@ github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dA
github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ= github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA= github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I= github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI=
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
@@ -26,12 +36,18 @@ github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSE
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4= github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
@@ -40,9 +56,21 @@ github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk=
github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+4
View File
@@ -76,6 +76,10 @@ func extractField(rule, field string) string {
return rest[:end] return rest[:end]
} }
func NixOSRule(dev Device, status Status) string {
return fmt.Sprintf("%s id %s name \"%s\"", status, dev.VidPid, dev.Name)
}
func extractUnquoted(rule, field string) string { func extractUnquoted(rule, field string) string {
prefix := field + " " prefix := field + " "
idx := strings.Index(rule, prefix) idx := strings.Index(rule, prefix)
+8 -6
View File
@@ -7,6 +7,7 @@ import (
"charm.land/bubbles/v2/list" "charm.land/bubbles/v2/list"
tea "charm.land/bubbletea/v2" tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2" "charm.land/lipgloss/v2"
"github.com/anotherhadi/ilovetui"
"github.com/anotherhadi/usbguard-tui/internal/guard" "github.com/anotherhadi/usbguard-tui/internal/guard"
) )
@@ -30,25 +31,25 @@ func (d deviceDelegate) Render(w io.Writer, m list.Model, index int, item list.I
} }
clr, ok := colorMap[dev.Status] clr, ok := colorMap[dev.Status]
if !ok { if !ok {
clr = colorMuted clr = ilovetui.S.Muted
} }
var nameStyle, descStyle lipgloss.Style var nameStyle, descStyle lipgloss.Style
if selected { if selected {
nameStyle = lipgloss.NewStyle(). nameStyle = lipgloss.NewStyle().
Border(lipgloss.NormalBorder(), false, false, false, true). Border(lipgloss.NormalBorder(), false, false, false, true).
BorderForeground(colorAccent). BorderForeground(ilovetui.S.Primary).
Foreground(clr). Foreground(clr).
Bold(true). Bold(true).
PaddingLeft(1) PaddingLeft(1)
descStyle = lipgloss.NewStyle(). descStyle = lipgloss.NewStyle().
Border(lipgloss.NormalBorder(), false, false, false, true). Border(lipgloss.NormalBorder(), false, false, false, true).
BorderForeground(colorAccent). BorderForeground(ilovetui.S.Primary).
Foreground(colorMuted). Foreground(ilovetui.S.Muted).
PaddingLeft(1) PaddingLeft(1)
} else { } else {
nameStyle = lipgloss.NewStyle().Foreground(clr).PaddingLeft(2) nameStyle = lipgloss.NewStyle().Foreground(clr).PaddingLeft(2)
descStyle = lipgloss.NewStyle().Foreground(colorMuted).PaddingLeft(2) descStyle = lipgloss.NewStyle().Foreground(ilovetui.S.Muted).PaddingLeft(2)
} }
permIndicator := "○ tmp" permIndicator := "○ tmp"
@@ -66,6 +67,7 @@ type actionItem struct {
fn func(int, bool) error fn func(int, bool) error
permanent bool permanent bool
status guard.Status status guard.Status
nixos bool
} }
func (a actionItem) Title() string { return a.label } func (a actionItem) Title() string { return a.label }
@@ -86,7 +88,7 @@ func (d actionDelegate) Render(w io.Writer, m list.Model, index int, item list.I
if index == m.Index() { if index == m.Index() {
clr, ok := statusColorsSelected[a.status] clr, ok := statusColorsSelected[a.status]
if !ok { if !ok {
clr = colorAccent clr = ilovetui.S.Primary
} }
fmt.Fprintf(w, " %s", lipgloss.NewStyle().Bold(true).Foreground(clr).Render("> "+a.label)) fmt.Fprintf(w, " %s", lipgloss.NewStyle().Bold(true).Foreground(clr).Render("> "+a.label))
} else { } else {
+53 -23
View File
@@ -1,6 +1,7 @@
package ui package ui
import ( import (
"fmt"
"strings" "strings"
"time" "time"
@@ -10,6 +11,7 @@ import (
"charm.land/bubbles/v2/textinput" "charm.land/bubbles/v2/textinput"
tea "charm.land/bubbletea/v2" tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2" "charm.land/lipgloss/v2"
"github.com/anotherhadi/ilovetui"
"github.com/anotherhadi/usbguard-tui/internal/guard" "github.com/anotherhadi/usbguard-tui/internal/guard"
) )
@@ -25,6 +27,7 @@ type (
devicesMsg []guard.Device devicesMsg []guard.Device
daemonStatusMsg string daemonStatusMsg string
actionMsg struct{ err error } actionMsg struct{ err error }
nixRuleMsg struct{ rule string }
) )
type Model struct { type Model struct {
@@ -38,8 +41,11 @@ type Model struct {
notice string notice string
selectedDev *guard.Device selectedDev *guard.Device
rulesManaged bool rulesManaged bool
pendingRules []string
} }
func (m Model) PendingRules() []string { return m.pendingRules }
func New() Model { func New() Model {
l := list.New(nil, deviceDelegate{}, 0, 0) l := list.New(nil, deviceDelegate{}, 0, 0)
l.SetShowHelp(false) l.SetShowHelp(false)
@@ -52,17 +58,16 @@ func New() Model {
l.Styles = list.DefaultStyles(true) l.Styles = list.DefaultStyles(true)
filterStyles := textinput.DefaultStyles(true) filterStyles := textinput.DefaultStyles(true)
filterStyles.Focused.Prompt = filterStyles.Focused.Prompt.Foreground(colorAccent) filterStyles.Focused.Prompt = filterStyles.Focused.Prompt.Foreground(ilovetui.S.Primary)
filterStyles.Blurred.Prompt = filterStyles.Blurred.Prompt.Foreground(colorAccent) filterStyles.Blurred.Prompt = filterStyles.Blurred.Prompt.Foreground(ilovetui.S.Primary)
l.Styles.Filter = filterStyles l.Styles.Filter = filterStyles
h := help.New() h := ilovetui.NewHelp()
h.Styles = help.DefaultStyles(true)
rulesManaged := guard.IsRulesManaged() rulesManaged := guard.IsRulesManaged()
notice := "" notice := ""
if rulesManaged { if rulesManaged {
notice = "Rules managed by NixOS config: permanent actions not available." notice = "Rules managed by NixOS config: permanent actions will print NixOS rules on exit."
listKeys.AllowPerm.SetEnabled(false) listKeys.AllowPerm.SetEnabled(false)
listKeys.BlockPerm.SetEnabled(false) listKeys.BlockPerm.SetEnabled(false)
listKeys.RejectPerm.SetEnabled(false) listKeys.RejectPerm.SetEnabled(false)
@@ -82,18 +87,21 @@ func makeActionList(rulesManaged bool) list.Model {
var items []list.Item var items []list.Item
if rulesManaged { if rulesManaged {
items = []list.Item{ items = []list.Item{
actionItem{"allow", guard.AllowDevice, false, guard.Allowed}, actionItem{"allow", guard.AllowDevice, false, guard.Allowed, false},
actionItem{"block", guard.BlockDevice, false, guard.Blocked}, actionItem{"allow (perm)", nil, true, guard.Allowed, true},
actionItem{"reject", guard.RejectDevice, false, guard.Rejected}, actionItem{"block", guard.BlockDevice, false, guard.Blocked, false},
actionItem{"block (perm)", nil, true, guard.Blocked, true},
actionItem{"reject", guard.RejectDevice, false, guard.Rejected, false},
actionItem{"reject (perm)", nil, true, guard.Rejected, true},
} }
} else { } else {
items = []list.Item{ items = []list.Item{
actionItem{"allow", guard.AllowDevice, false, guard.Allowed}, actionItem{"allow", guard.AllowDevice, false, guard.Allowed, false},
actionItem{"allow (permanent)", guard.AllowDevice, true, guard.Allowed}, actionItem{"allow (permanent)", guard.AllowDevice, true, guard.Allowed, false},
actionItem{"block", guard.BlockDevice, false, guard.Blocked}, actionItem{"block", guard.BlockDevice, false, guard.Blocked, false},
actionItem{"block (permanent)", guard.BlockDevice, true, guard.Blocked}, actionItem{"block (permanent)", guard.BlockDevice, true, guard.Blocked, false},
actionItem{"reject", guard.RejectDevice, false, guard.Rejected}, actionItem{"reject", guard.RejectDevice, false, guard.Rejected, false},
actionItem{"reject (permanent)", guard.RejectDevice, true, guard.Rejected}, actionItem{"reject (permanent)", guard.RejectDevice, true, guard.Rejected, false},
} }
} }
l := list.New(items, actionDelegate{}, 24, len(items)) l := list.New(items, actionDelegate{}, 24, len(items))
@@ -135,6 +143,18 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.daemonStatus = string(msg) m.daemonStatus = string(msg)
return m, nil return m, nil
case nixRuleMsg:
m.state = stateList
m.selectedDev = nil
m.pendingRules = append(m.pendingRules, msg.rule)
count := len(m.pendingRules)
if count == 1 {
m.notice = "1 NixOS rule queued (printed on exit)"
} else {
m.notice = fmt.Sprintf("%d NixOS rules queued (printed on exit)", count)
}
return m, nil
case actionMsg: case actionMsg:
m.state = stateList m.state = stateList
m.selectedDev = nil m.selectedDev = nil
@@ -224,6 +244,10 @@ func (m Model) updatePopup(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
case key.Matches(msg, listKeys.Open): case key.Matches(msg, listKeys.Open):
if item := m.actionList.SelectedItem(); item != nil { if item := m.actionList.SelectedItem(); item != nil {
a := item.(actionItem) a := item.(actionItem)
if a.nixos && m.selectedDev != nil {
rule := guard.NixOSRule(*m.selectedDev, a.status)
return m, func() tea.Msg { return nixRuleMsg{rule: rule} }
}
return m, doAction(m.selectedDev.ID, a.fn, a.permanent) return m, doAction(m.selectedDev.ID, a.fn, a.permanent)
} }
} }
@@ -279,10 +303,15 @@ func (m Model) renderActionSelect() string {
innerW := m.actionListInnerWidth() innerW := m.actionListInnerWidth()
title := popupTitleStyle.Foreground(color).Width(innerW).Render(dev.Name) title := popupTitleStyle.Foreground(color).Width(innerW).Render(dev.Name)
hint := lipgloss.NewStyle().Foreground(colorMuted).Width(innerW).Render("↑↓ navigate enter confirm esc cancel") hint := lipgloss.NewStyle().Foreground(ilovetui.S.Muted).Width(innerW).Render("↑↓ navigate enter confirm esc cancel")
content := strings.Join([]string{title, m.actionList.View(), "", hint}, "\n") parts := []string{title, m.actionList.View(), ""}
return popupStyle.Width(innerW).Render(content) if m.rulesManaged {
nixosHint := lipgloss.NewStyle().Foreground(ilovetui.S.Muted).Width(innerW).Render("[NixOS: perm rules printed on exit]")
parts = append(parts, nixosHint)
}
parts = append(parts, hint)
return popupStyle.Width(innerW).Render(strings.Join(parts, "\n"))
} }
func (m Model) popupOuterWidth() int { func (m Model) popupOuterWidth() int {
@@ -302,15 +331,12 @@ func (m Model) actionListInnerWidth() int {
func (m Model) defaultNotice() string { func (m Model) defaultNotice() string {
if m.rulesManaged { if m.rulesManaged {
return "Rules managed by NixOS config: permanent actions not available." return "Rules managed by NixOS config: permanent actions will print NixOS rules on exit."
} }
return "" return ""
} }
func (m Model) actionItemCount() int { func (m Model) actionItemCount() int {
if m.rulesManaged {
return 3
}
return 6 return 6
} }
@@ -321,8 +347,12 @@ func (m Model) actionItemCount() int {
func (m *Model) updateActionListSize() { func (m *Model) updateActionListSize() {
items := m.actionItemCount() items := m.actionItemCount()
innerW := m.actionListInnerWidth() innerW := m.actionListInnerWidth()
// popup overhead: border(2) + padding_v(2) + title(1) + blank(1) + hint(1) = 7 // popup overhead: border(2) + padding_v(2) + title(1) + blank(1) + hint(1) = 7; +1 for NixOS footer
available := m.height - 7 - 2 // 2 lines margin overhead := 7
if m.rulesManaged {
overhead = 8
}
available := m.height - overhead - 2 // 2 lines margin
if available >= items { if available >= items {
m.actionList.SetShowPagination(false) m.actionList.SetShowPagination(false)
m.actionList.SetSize(innerW, items) m.actionList.SetSize(innerW, items)
+13 -23
View File
@@ -4,49 +4,39 @@ import (
"image/color" "image/color"
"charm.land/lipgloss/v2" "charm.land/lipgloss/v2"
"github.com/anotherhadi/ilovetui"
"github.com/anotherhadi/usbguard-tui/internal/guard" "github.com/anotherhadi/usbguard-tui/internal/guard"
) )
var (
colorAllowed color.Color = lipgloss.Color("28")
colorAllowedSelected color.Color = lipgloss.Color("42")
colorBlocked color.Color = lipgloss.Color("124")
colorBlockedSelected color.Color = lipgloss.Color("196")
colorRejected color.Color = lipgloss.Color("130")
colorRejectedSelected color.Color = lipgloss.Color("214")
colorMuted color.Color = lipgloss.Color("240")
colorAccent color.Color = lipgloss.Color("99")
)
var statusColors = map[guard.Status]color.Color{ var statusColors = map[guard.Status]color.Color{
guard.Allowed: colorAllowed, guard.Allowed: ilovetui.S.Success,
guard.Blocked: colorBlocked, guard.Blocked: ilovetui.S.Error,
guard.Rejected: colorRejected, guard.Rejected: ilovetui.S.Warning,
} }
var statusColorsSelected = map[guard.Status]color.Color{ var statusColorsSelected = map[guard.Status]color.Color{
guard.Allowed: colorAllowedSelected, guard.Allowed: ilovetui.S.Success,
guard.Blocked: colorBlockedSelected, guard.Blocked: ilovetui.S.Error,
guard.Rejected: colorRejectedSelected, guard.Rejected: ilovetui.S.Warning,
} }
var ( var (
headerStyle = lipgloss.NewStyle(). headerStyle = lipgloss.NewStyle().
Bold(true). Bold(true).
Foreground(colorAccent). Foreground(ilovetui.S.Primary).
PaddingLeft(1) PaddingLeft(1)
daemonActiveStyle = lipgloss.NewStyle().Foreground(colorAllowedSelected) daemonActiveStyle = lipgloss.NewStyle().Foreground(ilovetui.S.Success)
daemonOtherStyle = lipgloss.NewStyle().Foreground(colorMuted) daemonOtherStyle = lipgloss.NewStyle().Foreground(ilovetui.S.Muted)
mutedStyle = lipgloss.NewStyle().Foreground(colorMuted) mutedStyle = lipgloss.NewStyle().Foreground(ilovetui.S.Muted)
popupStyle = lipgloss.NewStyle(). popupStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()). Border(lipgloss.RoundedBorder()).
BorderForeground(colorAccent). BorderForeground(ilovetui.S.Primary).
Padding(1, 3) Padding(1, 3)
popupTitleStyle = lipgloss.NewStyle().Bold(true).MarginBottom(1) popupTitleStyle = lipgloss.NewStyle().Bold(true).MarginBottom(1)
warnStyle = lipgloss.NewStyle().Foreground(colorRejected) warnStyle = lipgloss.NewStyle().Foreground(ilovetui.S.Warning)
) )
+12 -1
View File
@@ -23,8 +23,19 @@ func main() {
} }
p := tea.NewProgram(ui.New()) p := tea.NewProgram(ui.New())
if _, err := p.Run(); err != nil { m, err := p.Run()
if err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
os.Exit(1) os.Exit(1)
} }
if fm, ok := m.(ui.Model); ok {
if rules := fm.PendingRules(); len(rules) > 0 {
fmt.Println("# Add to your NixOS configuration:")
fmt.Println("services.usbguard.rules = lib.mkAfter ''")
for _, rule := range rules {
fmt.Println(" ", rule)
}
fmt.Println("'';")
}
}
} }