Add help screen, connection indicators, README and release builds (M7)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-20 19:11:07 +02:00
co-authored by Claude Sonnet 5.5
parent f928427993
commit 3f6e595719
8 changed files with 229 additions and 8 deletions
+72 -3
View File
@@ -34,8 +34,21 @@ type App struct {
message string
overlays []overlay // dialogs and full-screen views stacked above the pages
services *tview.TextView
state map[string]serviceState // keyed by service name
}
// serviceState is the last known connection state of a backend service.
type serviceState struct {
known bool
version string
err error
}
// serviceNames fixes the order of the connection indicators.
var serviceNames = []string{"Radarr", "Sonarr", "SABnzbd"}
// overlay is a page stacked above the main views; focus is restored on close.
type overlay struct {
page string
@@ -49,6 +62,9 @@ func New(cfg *config.Config) *App {
tabs: tview.NewTextView().SetDynamicColors(true),
pages: tview.NewPages(),
status: tview.NewTextView().SetDynamicColors(true),
services: tview.NewTextView().SetDynamicColors(true).SetTextAlign(tview.AlignRight),
state: map[string]serviceState{},
}
radarrClient := radarr.NewClient(cfg.Radarr.URL, cfg.Radarr.APIKey)
@@ -70,7 +86,9 @@ func New(cfg *config.Config) *App {
}
layout := tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(a.tabs, 1, 0, false).
AddItem(tview.NewFlex().
AddItem(a.tabs, 0, 1, false).
AddItem(a.services, 34, 0, false), 1, 0, false).
AddItem(a.pages, 0, 1, true).
AddItem(a.status, 1, 0, false)
@@ -78,9 +96,56 @@ func New(cfg *config.Config) *App {
a.app.SetInputCapture(a.handleKey)
a.switchTo(0)
a.renderStatus()
a.renderServices()
return a
}
// SetService records the outcome of a request to a service and updates the
// connection indicators. UI goroutine only. A nil err means reachable.
func (a *App) SetService(name, version string, err error) {
prev := a.state[name]
if err == nil && version == "" {
version = prev.version
}
a.state[name] = serviceState{known: true, version: version, err: err}
a.renderServices()
}
func (a *App) renderServices() {
var s string
for _, n := range serviceNames {
st := a.state[n]
switch {
case !st.known:
s += "[gray]○ " + n + "[-] "
case st.err != nil:
s += "[red]● " + n + "[-] "
default:
s += "[green]●[-] " + n + " "
}
}
a.services.SetText(s)
}
// serviceSummary describes every service for the help screen.
func (a *App) serviceSummary() string {
var b strings.Builder
for _, n := range serviceNames {
st := a.state[n]
switch {
case !st.known:
fmt.Fprintf(&b, "[gray]○ %s: not contacted yet[-]\n", n)
case st.err != nil:
fmt.Fprintf(&b, "[red]● %s: %s[-]\n", n, tview.Escape(st.err.Error()))
case st.version != "":
fmt.Fprintf(&b, "[green]●[-] %s %s: connected\n", n, tview.Escape(st.version))
default:
fmt.Fprintf(&b, "[green]●[-] %s: connected\n", n)
}
}
return b.String()
}
// SetMessage shows a message in the status bar. UI goroutine only; use
// post from other goroutines.
func (a *App) SetMessage(text string) {
@@ -99,7 +164,7 @@ func (a *App) Run() error {
}
func (a *App) renderStatus() {
hints := "[gray]q quit · Tab/1-4/F1-F4 views · / search"
hints := "[gray]?/F9 help · q quit · Tab/F1-F4 views · / search"
if len(a.overlays) > 0 {
hints = "[gray]Esc back · Ctrl+C quit"
}
@@ -180,8 +245,12 @@ func (a *App) handleKey(ev *tcell.EventKey) *tcell.EventKey {
a.app.Stop()
return nil
}
// View switching that also works while typing in a search box. Not while a dialog is open.
// Shortcuts that also work while typing in a search box. Not while a dialog is open.
if len(a.overlays) == 0 {
if ev.Key() == tcell.KeyF9 {
a.showHelp()
return nil
}
if i := a.viewShortcut(ev); i >= 0 && i < len(a.views) {
a.switchTo(i)
return nil