First start: create the config file and open the Config view
Without a config file arr-tui refused to start. It now creates an empty one (private, with its directory) and opens the Config view. A service with neither address nor key is "not configured", which validation allows: its client returns ErrNotConfigured without a request, its indicator stays grey, it is logged once as information, and its lists say so instead of showing errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019reK4DUGYqDUjniWYJvwUW
This commit is contained in:
+30
-2
@@ -3,6 +3,7 @@ package ui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
|
||||
"git.stinnesbeck.com/nils/arr-tui/internal/arr"
|
||||
"git.stinnesbeck.com/nils/arr-tui/internal/config"
|
||||
"git.stinnesbeck.com/nils/arr-tui/internal/radarr"
|
||||
"git.stinnesbeck.com/nils/arr-tui/internal/sabnzbd"
|
||||
@@ -203,6 +205,11 @@ func New(cfg *config.Config) *App {
|
||||
syncFocus = func(p tview.Primitive) { a.app.SetFocus(p) }
|
||||
a.app.SetInputCapture(a.handleKey)
|
||||
a.switchTo(0)
|
||||
if !cfg.AnyConfigured() {
|
||||
// First start (or nothing set up): go where that is done.
|
||||
a.switchTo(a.viewIndex("Config"))
|
||||
a.Notify(levelInfo, "Welcome! Enter the addresses and API keys of your services, then Save")
|
||||
}
|
||||
a.renderServices()
|
||||
return a
|
||||
}
|
||||
@@ -314,7 +321,11 @@ func (a *App) SetService(name, version string, err error) {
|
||||
a.state[name] = serviceState{known: true, version: version, err: err}
|
||||
// Log connection changes only, so background polling does not flood the log.
|
||||
switch {
|
||||
case err != nil && (!prev.known || prev.err == nil):
|
||||
case notConfigured(err):
|
||||
if !prev.known || !notConfigured(prev.err) {
|
||||
a.logs.add(levelInfo, name+" is not configured; set it up in the Config view (F6)")
|
||||
}
|
||||
case err != nil && (!prev.known || prev.err == nil || notConfigured(prev.err)):
|
||||
a.logs.add(levelError, name+" unreachable: "+err.Error())
|
||||
case err == nil && (!prev.known || prev.err != nil):
|
||||
a.logs.add(levelSuccess, strings.TrimSpace(name+" "+version)+" connected")
|
||||
@@ -354,7 +365,7 @@ func (a *App) renderServices() {
|
||||
}
|
||||
st := a.state[n]
|
||||
switch {
|
||||
case !st.known:
|
||||
case !st.known, notConfigured(st.err):
|
||||
s += "[gray]○ " + short + "[-] "
|
||||
case st.err != nil:
|
||||
s += "[red]● " + short + "[-] "
|
||||
@@ -373,6 +384,8 @@ func (a *App) serviceSummary() string {
|
||||
switch {
|
||||
case !st.known:
|
||||
fmt.Fprintf(&b, "[gray]○ %s: not contacted yet[-]\n", n)
|
||||
case notConfigured(st.err):
|
||||
fmt.Fprintf(&b, "[gray]○ %s: not configured (Config view, F6)[-]\n", n)
|
||||
case st.err != nil:
|
||||
fmt.Fprintf(&b, "[red]● %s: %s[-]\n", n, tview.Escape(st.err.Error()))
|
||||
case st.version != "":
|
||||
@@ -384,6 +397,21 @@ func (a *App) serviceSummary() string {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// viewIndex returns the position of the view with the given name.
|
||||
func (a *App) viewIndex(name string) int {
|
||||
for i, v := range a.views {
|
||||
if v.name == name {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// notConfigured reports whether err says a service is not set up.
|
||||
func notConfigured(err error) bool {
|
||||
return errors.Is(err, arr.ErrNotConfigured) || errors.Is(err, sabnzbd.ErrNotConfigured)
|
||||
}
|
||||
|
||||
// post runs f on the UI goroutine and redraws. Safe to call from any goroutine.
|
||||
func (a *App) post(f func()) {
|
||||
a.app.QueueUpdateDraw(f)
|
||||
|
||||
Reference in New Issue
Block a user