mirror of
https://github.com/0x1d/rcond.git
synced 2025-12-14 18:25:21 +01:00
Compare commits
2 Commits
feature/ui
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 359ec6e283 | |||
| 4a7b683b98 |
2
Makefile
2
Makefile
@@ -2,7 +2,7 @@ SHELL := bash
|
||||
ARCH ?= amd64
|
||||
ADDR ?= 0.0.0.0:8080
|
||||
|
||||
default: info
|
||||
default: build
|
||||
|
||||
.PHONY: info
|
||||
info:
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/0x1d/rcond/pkg/config"
|
||||
"github.com/0x1d/rcond/pkg/rcond"
|
||||
"github.com/0x1d/rcond/pkg/ui"
|
||||
)
|
||||
|
||||
func usage() {
|
||||
@@ -25,24 +24,9 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
switch appConfig.RunMode {
|
||||
case config.RunModeNode:
|
||||
fmt.Println("running node")
|
||||
// Validate required fields
|
||||
if err := validateRequiredFields(map[string]*string{
|
||||
"addr": &appConfig.Rcond.Addr,
|
||||
"token": &appConfig.Rcond.ApiToken,
|
||||
}); err != nil {
|
||||
usage()
|
||||
fmt.Printf("\nFailed to validate required fields: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
rcond.NewNode(appConfig).Up()
|
||||
select {}
|
||||
case config.RunModeUI:
|
||||
ui.NewUI(appConfig)
|
||||
}
|
||||
rcond.NewNode(appConfig).Up()
|
||||
|
||||
select {}
|
||||
}
|
||||
|
||||
func loadConfig() (*config.Config, error) {
|
||||
@@ -52,8 +36,6 @@ func loadConfig() (*config.Config, error) {
|
||||
|
||||
flag.StringVar(&configPath, "config", configPath, "Path to the configuration file")
|
||||
flag.BoolVar(&help, "help", false, "Show help")
|
||||
// check for runmode
|
||||
runMode := flag.String("runmode", "node", "Run mode: node or ui")
|
||||
flag.Parse()
|
||||
|
||||
if help {
|
||||
@@ -69,7 +51,15 @@ func loadConfig() (*config.Config, error) {
|
||||
}
|
||||
appConfig = configFile
|
||||
}
|
||||
appConfig.RunMode = config.RunMode(*runMode)
|
||||
|
||||
// Validate required fields
|
||||
if err := validateRequiredFields(map[string]*string{
|
||||
"addr": &appConfig.Rcond.Addr,
|
||||
"token": &appConfig.Rcond.ApiToken,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return appConfig, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -4,18 +4,6 @@ rcond:
|
||||
# API token to use for authentication
|
||||
api_token: 1234567890
|
||||
|
||||
wifi:
|
||||
ap:
|
||||
ssid: "rcond-ap"
|
||||
password: "rcond-ap-password"
|
||||
encryption: "WPA2"
|
||||
interface: "wlan0"
|
||||
sta:
|
||||
ssid: "rcond-sta"
|
||||
password: "rcond-sta-password"
|
||||
encryption: "WPA2"
|
||||
interface: "wlan1"
|
||||
|
||||
cluster:
|
||||
# Enable the cluster agent
|
||||
enabled: true
|
||||
|
||||
26
go.mod
26
go.mod
@@ -7,16 +7,12 @@ replace github.com/0x1d/rcond/cmd => ./cmd
|
||||
replace github.com/0x1d/rcond/pkg => ./pkg
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/bubbletea v1.3.5
|
||||
github.com/charmbracelet/huh v0.7.0
|
||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
|
||||
github.com/godbus/dbus/v5 v5.1.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/hashicorp/logutils v1.0.0
|
||||
github.com/hashicorp/serf v0.10.2
|
||||
github.com/kelseyhightower/envconfig v1.4.0
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/crypto v0.37.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
@@ -24,18 +20,7 @@ require (
|
||||
|
||||
require (
|
||||
github.com/armon/go-metrics v0.4.1 // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/catppuccin/go v0.3.0 // indirect
|
||||
github.com/charmbracelet/bubbles v0.21.0 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||
github.com/charmbracelet/x/ansi v0.8.0 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||
@@ -46,23 +31,12 @@ require (
|
||||
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
||||
github.com/hashicorp/memberlist v0.5.2 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/miekg/dns v1.1.56 // indirect
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sync v0.13.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
)
|
||||
|
||||
73
go.sum
73
go.sum
@@ -1,7 +1,5 @@
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
||||
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
@@ -9,58 +7,16 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
|
||||
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
|
||||
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
|
||||
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
|
||||
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
|
||||
github.com/charmbracelet/bubbletea v1.3.5 h1:JAMNLTbqMOhSwoELIr0qyP4VidFq72/6E9j7HHmRKQc=
|
||||
github.com/charmbracelet/bubbletea v1.3.5/go.mod h1:TkCnmH+aBd4LrXhXcqrKiYwRs7qyQx5rBgH5fVY3v54=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
||||
github.com/charmbracelet/huh v0.7.0 h1:W8S1uyGETgj9Tuda3/JdVkc3x7DBLZYPZc4c+/rnRdc=
|
||||
github.com/charmbracelet/huh v0.7.0/go.mod h1:UGC3DZHlgOKHvHC07a5vHag41zzhpPFj34U92sOmyuk=
|
||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE=
|
||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA=
|
||||
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
|
||||
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||
github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
|
||||
github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=
|
||||
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
|
||||
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
|
||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
|
||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
|
||||
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
|
||||
github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI=
|
||||
github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4=
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
@@ -140,29 +96,13 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
|
||||
github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
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/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||
@@ -190,9 +130,6 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||
@@ -200,8 +137,6 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
@@ -210,15 +145,11 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
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=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
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/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -248,16 +179,12 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
|
||||
@@ -8,46 +8,17 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
RunMode RunMode `yaml:"runmode"`
|
||||
Hostname string `yaml:"hostname" envconfig:"HOSTNAME"`
|
||||
Rcond RcondConfig `yaml:"rcond"`
|
||||
Network NetworkConfig `yaml:"network"`
|
||||
Wifi WifiConfig `yaml:"wifi"`
|
||||
Cluster ClusterConfig `yaml:"cluster"`
|
||||
UI UIConfig `yaml:"ui"`
|
||||
}
|
||||
|
||||
type RunMode string
|
||||
|
||||
const (
|
||||
RunModeNode RunMode = "node"
|
||||
RunModeUI RunMode = "ui"
|
||||
)
|
||||
|
||||
type RcondConfig struct {
|
||||
Addr string `yaml:"addr" envconfig:"RCOND_ADDR"`
|
||||
ApiToken string `yaml:"api_token" envconfig:"RCOND_API_TOKEN"`
|
||||
}
|
||||
|
||||
type WifiConfig struct {
|
||||
AP APConfig `yaml:"ap"`
|
||||
STA STAConfig `yaml:"sta"`
|
||||
}
|
||||
|
||||
type APConfig struct {
|
||||
SSID string `yaml:"ssid" envconfig:"WIFI_AP_SSID"`
|
||||
Password string `yaml:"password" envconfig:"WIFI_AP_PASSWORD"`
|
||||
Encryption string `yaml:"encryption" envconfig:"WIFI_AP_ENCRYPTION"`
|
||||
Interface string `yaml:"interface" envconfig:"WIFI_AP_INTERFACE"`
|
||||
}
|
||||
|
||||
type STAConfig struct {
|
||||
SSID string `yaml:"ssid" envconfig:"WIFI_STA_SSID"`
|
||||
Password string `yaml:"password" envconfig:"WIFI_STA_PASSWORD"`
|
||||
Encryption string `yaml:"encryption" envconfig:"WIFI_STA_ENCRYPTION"`
|
||||
Interface string `yaml:"interface" envconfig:"WIFI_STA_INTERFACE"`
|
||||
}
|
||||
|
||||
type NetworkConfig struct {
|
||||
Connections []ConnectionConfig `yaml:"connections"`
|
||||
}
|
||||
@@ -79,10 +50,6 @@ type ClusterConfig struct {
|
||||
LogLevel string `yaml:"log_level" envconfig:"CLUSTER_LOG_LEVEL"`
|
||||
}
|
||||
|
||||
type UIConfig struct {
|
||||
Enabled bool `yaml:"enabled" envconfig:"UI_ENABLED"`
|
||||
}
|
||||
|
||||
// LoadConfig reads the configuration from a YAML file and environment variables.
|
||||
func LoadConfig(filename string) (*Config, error) {
|
||||
var config Config
|
||||
|
||||
421
pkg/ui/ui.go
421
pkg/ui/ui.go
@@ -1,421 +0,0 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/0x1d/rcond/pkg/config"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/huh"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/skip2/go-qrcode"
|
||||
)
|
||||
|
||||
const maxWidth = 120
|
||||
|
||||
type UI struct {
|
||||
appConfig *config.Config
|
||||
program *tea.Program
|
||||
}
|
||||
|
||||
type UIConfig struct {
|
||||
Enabled bool `yaml:"enabled" envconfig:"UI_ENABLED"`
|
||||
}
|
||||
|
||||
var (
|
||||
red = lipgloss.AdaptiveColor{Light: "#FE5F86", Dark: "#FE5F86"}
|
||||
indigo = lipgloss.AdaptiveColor{Light: "#5A56E0", Dark: "#7571F9"}
|
||||
green = lipgloss.AdaptiveColor{Light: "#02BA84", Dark: "#02BF87"}
|
||||
)
|
||||
|
||||
type Styles struct {
|
||||
Base,
|
||||
HeaderText,
|
||||
Status,
|
||||
StatusHeader,
|
||||
Highlight,
|
||||
ErrorHeaderText,
|
||||
Help lipgloss.Style
|
||||
}
|
||||
|
||||
func NewStyles(lg *lipgloss.Renderer) *Styles {
|
||||
s := Styles{}
|
||||
s.Base = lg.NewStyle().
|
||||
Padding(1, 4, 0, 1)
|
||||
s.HeaderText = lg.NewStyle().
|
||||
Foreground(indigo).
|
||||
Bold(true).
|
||||
Padding(0, 1, 0, 2)
|
||||
s.Status = lg.NewStyle().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(indigo).
|
||||
PaddingLeft(1).
|
||||
MarginTop(1)
|
||||
s.StatusHeader = lg.NewStyle().
|
||||
Foreground(green).
|
||||
Bold(true)
|
||||
s.Highlight = lg.NewStyle().
|
||||
Foreground(lipgloss.Color("212"))
|
||||
s.ErrorHeaderText = s.HeaderText.
|
||||
Foreground(red)
|
||||
s.Help = lg.NewStyle().
|
||||
Foreground(lipgloss.Color("240"))
|
||||
return &s
|
||||
}
|
||||
|
||||
type state int
|
||||
|
||||
const (
|
||||
statusNormal state = iota
|
||||
stateModeSelection
|
||||
stateDone
|
||||
)
|
||||
|
||||
type Model struct {
|
||||
state state
|
||||
lg *lipgloss.Renderer
|
||||
styles *Styles
|
||||
form *huh.Form
|
||||
width int
|
||||
appConfig *config.Config
|
||||
}
|
||||
|
||||
func NewModel(appConfig *config.Config) Model {
|
||||
m := Model{width: maxWidth, state: stateModeSelection, appConfig: appConfig}
|
||||
m.lg = lipgloss.DefaultRenderer()
|
||||
m.styles = NewStyles(m.lg)
|
||||
|
||||
m.form = m.createModeForm()
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (m Model) createModeForm() *huh.Form {
|
||||
return huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewSelect[string]().
|
||||
Key("mode").
|
||||
Options(huh.NewOptions("AP", "STA")...).
|
||||
Title("Parameters").
|
||||
Description("What do you want to configure?"),
|
||||
),
|
||||
).
|
||||
WithWidth(45).
|
||||
WithShowHelp(false).
|
||||
WithShowErrors(false)
|
||||
}
|
||||
|
||||
func min(x, y int) int {
|
||||
if x > y {
|
||||
return y
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func (m Model) Init() tea.Cmd {
|
||||
return m.form.Init()
|
||||
}
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = min(msg.Width, maxWidth) - m.styles.Base.GetHorizontalFrameSize()
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "ctrl+c":
|
||||
return m, tea.Interrupt
|
||||
case "esc", "q":
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
|
||||
var cmds []tea.Cmd
|
||||
|
||||
// Process the form
|
||||
form, cmd := m.form.Update(msg)
|
||||
if f, ok := form.(*huh.Form); ok {
|
||||
m.form = f
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
|
||||
// Handle mode selection
|
||||
if m.state == stateModeSelection && m.form.State == huh.StateCompleted {
|
||||
mode := m.form.GetString("mode")
|
||||
if mode == "AP" {
|
||||
m.form = m.createAPForm()
|
||||
m.state = statusNormal
|
||||
return m, m.form.Init()
|
||||
} else if mode == "STA" {
|
||||
m.form = m.createSTAForm()
|
||||
m.state = statusNormal
|
||||
return m, m.form.Init()
|
||||
}
|
||||
}
|
||||
|
||||
// After form completion, show mode selection again
|
||||
if m.state == statusNormal && m.form.State == huh.StateCompleted {
|
||||
m.appConfig.Wifi.AP.SSID = m.form.GetString("ssid")
|
||||
m.appConfig.Wifi.AP.Password = m.form.GetString("password")
|
||||
m.appConfig.Wifi.AP.Encryption = m.form.GetString("encryption")
|
||||
|
||||
m.form = m.createModeForm()
|
||||
m.state = stateModeSelection
|
||||
return m, m.form.Init()
|
||||
}
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m Model) createAPForm() *huh.Form {
|
||||
if m.appConfig == nil {
|
||||
log.Fatal("appConfig is nil")
|
||||
return nil // or handle the error appropriately
|
||||
}
|
||||
return huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewInput().
|
||||
Key("ssid").
|
||||
Placeholder("Access Point SSID").
|
||||
Title("SSID").
|
||||
Description("The name of your access point").
|
||||
Value(&m.appConfig.Wifi.AP.SSID),
|
||||
|
||||
huh.NewInput().
|
||||
Key("password").
|
||||
Placeholder("Access Point Password").
|
||||
Title("Password").
|
||||
EchoMode(huh.EchoModePassword).
|
||||
Description("The password for your access point").
|
||||
Validate(func(v string) error {
|
||||
if len(v) < 8 {
|
||||
return fmt.Errorf("password must be at least 8 characters long")
|
||||
}
|
||||
return nil
|
||||
}).
|
||||
Value(&m.appConfig.Wifi.AP.Password),
|
||||
|
||||
huh.NewSelect[string]().
|
||||
Key("encryption").
|
||||
Options(huh.NewOptions("WPA2", "WPA3", "WEP")...).
|
||||
Title("Encryption").
|
||||
Description("The encryption method for your access point").
|
||||
Value(&m.appConfig.Wifi.AP.Encryption),
|
||||
|
||||
huh.NewConfirm().
|
||||
Key("save").
|
||||
Title("Apply configuration").
|
||||
Validate(func(v bool) error {
|
||||
if !v {
|
||||
m.state = stateModeSelection
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}).
|
||||
Affirmative("Yep").
|
||||
Negative("Nope"),
|
||||
),
|
||||
).
|
||||
WithWidth(45).
|
||||
WithShowHelp(false).
|
||||
WithShowErrors(false)
|
||||
}
|
||||
|
||||
func (m Model) createSTAForm() *huh.Form {
|
||||
return huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewInput().
|
||||
Key("ssid").
|
||||
Placeholder("Station SSID").
|
||||
Title("SSID").
|
||||
Description("The name of the station").
|
||||
Value(&m.appConfig.Wifi.STA.SSID),
|
||||
|
||||
huh.NewInput().
|
||||
Key("password").
|
||||
Placeholder("Station Password").
|
||||
Title("Password").
|
||||
EchoMode(huh.EchoModePassword).
|
||||
Description("The password for the station").
|
||||
Validate(func(v string) error {
|
||||
if len(v) < 8 {
|
||||
return fmt.Errorf("password must be at least 8 characters long")
|
||||
}
|
||||
return nil
|
||||
}).
|
||||
Value(&m.appConfig.Wifi.STA.Password),
|
||||
huh.NewConfirm().
|
||||
Key("done").
|
||||
Title("All done?").
|
||||
Validate(func(v bool) error {
|
||||
if !v {
|
||||
return fmt.Errorf("Welp, finish up then")
|
||||
}
|
||||
return nil
|
||||
}).
|
||||
Affirmative("Yep").
|
||||
Negative("Wait, no"),
|
||||
),
|
||||
).
|
||||
WithWidth(45).
|
||||
WithShowHelp(false).
|
||||
WithShowErrors(false)
|
||||
}
|
||||
|
||||
func (m Model) View() string {
|
||||
s := m.styles
|
||||
|
||||
switch m.state {
|
||||
case stateModeSelection:
|
||||
v := strings.TrimSuffix(m.form.View(), "\n\n")
|
||||
form := m.lg.NewStyle().Margin(1, 0).Render(v)
|
||||
header := m.appBoundaryView("Configuration")
|
||||
footer := m.appBoundaryView(m.form.Help().ShortHelpView(m.form.KeyBinds()))
|
||||
return s.Base.Render(header + "\n" + form + "\n\n" + footer)
|
||||
case statusNormal:
|
||||
switch m.form.State {
|
||||
case huh.StateCompleted:
|
||||
ssid := m.appConfig.Wifi.STA.SSID
|
||||
password := m.appConfig.Wifi.STA.Password
|
||||
encryption := m.appConfig.Wifi.STA.Encryption
|
||||
qrCode, err := qrcode.New("WIFI:S:"+ssid+";T:"+encryption+";P:"+password+";;", qrcode.Medium)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
qrCode.DisableBorder = true
|
||||
qrCodeImage := qrCode.ToSmallString(false)
|
||||
|
||||
var b strings.Builder
|
||||
fmt.Fprintf(&b, "Congratulations, you've configured your access point!\n\n")
|
||||
fmt.Fprintf(&b, "SSID: %s\nEncryption: %s\n\nPlease proceed to connect to your access point.\n\n", ssid, encryption)
|
||||
|
||||
// Center the QR code
|
||||
centeredQRCode := lipgloss.NewStyle().Render(qrCodeImage)
|
||||
b.WriteString(centeredQRCode)
|
||||
|
||||
return s.Status.Margin(0, 1).Padding(2, 2).Width(48).Render(b.String()) + "\n\n"
|
||||
default:
|
||||
var ssid string
|
||||
var newSsid, newPassword, newEncryption string
|
||||
var encryption string
|
||||
var password string
|
||||
var qrCodeImage string
|
||||
ssid = m.appConfig.Wifi.STA.SSID
|
||||
password = m.appConfig.Wifi.STA.Password
|
||||
encryption = m.appConfig.Wifi.STA.Encryption
|
||||
|
||||
newSsid = m.form.GetString("ssid")
|
||||
if ssid != newSsid && newSsid != "" {
|
||||
ssid = newSsid
|
||||
}
|
||||
newPassword = m.form.GetString("password")
|
||||
if password != newPassword && newPassword != "" {
|
||||
password = newPassword
|
||||
}
|
||||
newEncryption = m.form.GetString("encryption")
|
||||
if encryption != newEncryption && newEncryption != "" {
|
||||
encryption = newEncryption
|
||||
}
|
||||
|
||||
if ssid != "" && password != "" && encryption != "" {
|
||||
qrCode, err := qrcode.New("WIFI:S:"+ssid+";T:"+encryption+";P:"+password+";;", qrcode.Medium)
|
||||
qrCode.BackgroundColor = color.White
|
||||
qrCode.DisableBorder = false
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
qrCodeImage = qrCode.ToSmallString(false)
|
||||
}
|
||||
|
||||
// Form (left side)
|
||||
v := strings.TrimSuffix(m.form.View(), "\n\n")
|
||||
form := m.lg.NewStyle().Margin(1, 0).Render(v)
|
||||
|
||||
// Status (right side)
|
||||
var status string
|
||||
{
|
||||
var (
|
||||
buildInfo = "(None)"
|
||||
)
|
||||
|
||||
if ssid != "" {
|
||||
buildInfo = "SSID: " + ssid
|
||||
}
|
||||
if password != "" {
|
||||
buildInfo += "\n" + "Password: " + strings.Repeat("*", len(password))
|
||||
}
|
||||
if encryption != "" {
|
||||
buildInfo += "\n" + "Encryption: " + encryption
|
||||
}
|
||||
|
||||
if qrCodeImage != "" {
|
||||
buildInfo += "\n\n" + qrCodeImage
|
||||
}
|
||||
|
||||
const statusWidth = 50
|
||||
statusMarginLeft := m.width - statusWidth - lipgloss.Width(form) - s.Status.GetMarginRight()
|
||||
status = s.Status.
|
||||
Height(lipgloss.Height(form)).
|
||||
Width(statusWidth).
|
||||
MarginLeft(statusMarginLeft).
|
||||
Render(s.StatusHeader.Render("Current Configuration") + "\n" +
|
||||
buildInfo)
|
||||
}
|
||||
|
||||
errors := m.form.Errors()
|
||||
header := m.appBoundaryView("Node Configuration")
|
||||
if len(errors) > 0 {
|
||||
header = m.appErrorBoundaryView(m.errorView())
|
||||
}
|
||||
body := lipgloss.JoinHorizontal(lipgloss.Left, form, status)
|
||||
|
||||
footer := m.appBoundaryView(m.form.Help().ShortHelpView(m.form.KeyBinds()))
|
||||
if len(errors) > 0 {
|
||||
footer = m.appErrorBoundaryView("")
|
||||
}
|
||||
|
||||
return s.Base.Render(header + "\n" + body + "\n\n" + footer)
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m Model) errorView() string {
|
||||
var s string
|
||||
for _, err := range m.form.Errors() {
|
||||
s += err.Error()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (m Model) appBoundaryView(text string) string {
|
||||
return lipgloss.PlaceHorizontal(
|
||||
m.width,
|
||||
lipgloss.Left,
|
||||
m.styles.HeaderText.Render(text),
|
||||
lipgloss.WithWhitespaceChars("/"),
|
||||
lipgloss.WithWhitespaceForeground(indigo),
|
||||
)
|
||||
}
|
||||
|
||||
func (m Model) appErrorBoundaryView(text string) string {
|
||||
return lipgloss.PlaceHorizontal(
|
||||
m.width,
|
||||
lipgloss.Left,
|
||||
m.styles.ErrorHeaderText.Render(text),
|
||||
lipgloss.WithWhitespaceChars("/"),
|
||||
lipgloss.WithWhitespaceForeground(red),
|
||||
)
|
||||
}
|
||||
|
||||
func NewUI(appConfig *config.Config) *UI {
|
||||
// Initialize our program
|
||||
ui := &UI{appConfig: appConfig}
|
||||
fmt.Printf("SSID: %s", appConfig.Wifi.AP.SSID)
|
||||
ui.program = tea.NewProgram(NewModel(appConfig), tea.WithAltScreen())
|
||||
if _, err := ui.program.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return ui
|
||||
}
|
||||
179
tea.go
179
tea.go
@@ -1,179 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/huh"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
type model struct {
|
||||
Tabs []string
|
||||
TabContent []string
|
||||
activeTab int
|
||||
form *huh.Form
|
||||
}
|
||||
|
||||
func (m model) Init() tea.Cmd {
|
||||
return m.form.Init()
|
||||
}
|
||||
|
||||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch keypress := msg.String(); keypress {
|
||||
case "ctrl+c", "q":
|
||||
return m, tea.Quit
|
||||
case "right", "l", "n", "tab":
|
||||
m.activeTab = min(m.activeTab+1, len(m.Tabs)-1)
|
||||
return m, nil
|
||||
case "left", "h", "p", "shift+tab":
|
||||
m.activeTab = max(m.activeTab-1, 0)
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
|
||||
var cmds []tea.Cmd
|
||||
|
||||
// Process the form
|
||||
form, cmd := m.form.Update(msg)
|
||||
if f, ok := form.(*huh.Form); ok {
|
||||
m.form = f
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
|
||||
if m.form.State == huh.StateCompleted {
|
||||
// Quit when the form is done.
|
||||
cmds = append(cmds, tea.Quit)
|
||||
}
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func tabBorderWithBottom(left, middle, right string) lipgloss.Border {
|
||||
border := lipgloss.RoundedBorder()
|
||||
border.BottomLeft = left
|
||||
border.Bottom = middle
|
||||
border.BottomRight = right
|
||||
return border
|
||||
}
|
||||
|
||||
var (
|
||||
inactiveTabBorder = tabBorderWithBottom("┴", "─", "┴")
|
||||
activeTabBorder = tabBorderWithBottom("┘", " ", "└")
|
||||
docStyle = lipgloss.NewStyle().Padding(1, 2, 1, 2)
|
||||
highlightColor = lipgloss.AdaptiveColor{Light: "#874BFD", Dark: "#7D56F4"}
|
||||
inactiveTabStyle = lipgloss.NewStyle().Border(inactiveTabBorder, true).BorderForeground(highlightColor).Padding(0, 1)
|
||||
activeTabStyle = inactiveTabStyle.Border(activeTabBorder, true)
|
||||
windowStyle = lipgloss.NewStyle().BorderForeground(highlightColor).Padding(2, 0).Align(lipgloss.Center).Border(lipgloss.NormalBorder()).UnsetBorderTop()
|
||||
)
|
||||
|
||||
func (m model) View() string {
|
||||
doc := strings.Builder{}
|
||||
|
||||
var renderedTabs []string
|
||||
|
||||
for i, t := range m.Tabs {
|
||||
var style lipgloss.Style
|
||||
isFirst, isLast, isActive := i == 0, i == len(m.Tabs)-1, i == m.activeTab
|
||||
if isActive {
|
||||
style = activeTabStyle
|
||||
} else {
|
||||
style = inactiveTabStyle
|
||||
}
|
||||
border, _, _, _, _ := style.GetBorder()
|
||||
if isFirst && isActive {
|
||||
border.BottomLeft = "│"
|
||||
} else if isFirst && !isActive {
|
||||
border.BottomLeft = "├"
|
||||
} else if isLast && isActive {
|
||||
border.BottomRight = "│"
|
||||
} else if isLast && !isActive {
|
||||
border.BottomRight = "┤"
|
||||
}
|
||||
style = style.Border(border)
|
||||
renderedTabs = append(renderedTabs, style.Render(t))
|
||||
}
|
||||
|
||||
row := lipgloss.JoinHorizontal(lipgloss.Top, renderedTabs...)
|
||||
doc.WriteString(row)
|
||||
doc.WriteString("\n")
|
||||
|
||||
if m.activeTab == 0 {
|
||||
doc.WriteString(windowStyle.Width((lipgloss.Width(row) - windowStyle.GetHorizontalFrameSize())).Render(m.form.View()))
|
||||
}
|
||||
|
||||
return docStyle.Render(doc.String())
|
||||
}
|
||||
|
||||
func main() {
|
||||
tabs := []string{"Access Point Configuration"}
|
||||
m := model{
|
||||
Tabs: tabs,
|
||||
form: huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewInput().
|
||||
Key("ssid").
|
||||
Placeholder("Access Point SSID").
|
||||
Title("SSID").
|
||||
Description("The name of your access point"),
|
||||
|
||||
huh.NewInput().
|
||||
Key("password").
|
||||
Placeholder("Access Point Password").
|
||||
Title("Password").
|
||||
EchoMode(huh.EchoModePassword).
|
||||
Description("The password for your access point").
|
||||
Validate(func(v string) error {
|
||||
if len(v) < 8 {
|
||||
return fmt.Errorf("password must be at least 8 characters long")
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
|
||||
huh.NewSelect[string]().
|
||||
Key("encryption").
|
||||
Options(huh.NewOptions("WPA2", "WPA3", "WEP")...).
|
||||
Title("Encryption").
|
||||
Description("The encryption method for your access point"),
|
||||
|
||||
huh.NewConfirm().
|
||||
Key("done").
|
||||
Title("All done?").
|
||||
Validate(func(v bool) error {
|
||||
if !v {
|
||||
return fmt.Errorf("Welp, finish up then")
|
||||
}
|
||||
return nil
|
||||
}).
|
||||
Affirmative("Yep").
|
||||
Negative("Wait, no"),
|
||||
),
|
||||
).
|
||||
WithWidth(45).
|
||||
WithShowHelp(false).
|
||||
WithShowErrors(false),
|
||||
}
|
||||
|
||||
if _, err := tea.NewProgram(m, tea.WithAltScreen()).Run(); err != nil {
|
||||
fmt.Println("Error running program:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
Reference in New Issue
Block a user