Config view: rename Test connections to Test; center Save and Revert

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-21 00:01:17 +02:00
co-authored by Claude Sonnet 5.5
parent 26e1679e4e
commit b40ee8b61c
5 changed files with 8 additions and 7 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ A terminal UI to search for movies and series with [Radarr](https://radarr.video
- **Downloads**: SABnzbd queue and history plus the Radarr/Sonarr queues. Pause/resume, delete, - **Downloads**: SABnzbd queue and history plus the Radarr/Sonarr queues. Pause/resume, delete,
retry failed downloads, remove from the arr queue and blocklist. retry failed downloads, remove from the arr queue and blocklist.
- **Config (F6)**: edit everything in the config file from inside the program, in one list you move - **Config (F6)**: edit everything in the config file from inside the program, in one list you move
through with the arrow keys: service addresses and API keys, the add-form defaults, refresh interval, posters, notifications. *Test connections* through with the arrow keys: service addresses and API keys, the add-form defaults, refresh interval, posters, notifications. *Test*
checks the values you typed before saving; *Save* writes the file (permissions kept, `0600` for a checks the values you typed before saving; *Save* writes the file (permissions kept, `0600` for a
new file) and applies the changes at once, no restart. Values that come from `ARR_TUI_*` new file) and applies the changes at once, no restart. Values that come from `ARR_TUI_*`
environment variables are greyed out and are never written to the file. environment variables are greyed out and are never written to the file.
+1 -1
View File
@@ -69,7 +69,7 @@ Notes:
Posters are JPEG/PNG, decoded and downscaled in the process. Library items fetch `/MediaCover/<id>/poster-250.jpg` from their Radarr/Sonarr instance with the API key; search results fall back to the provider's `remoteUrl` (fetched without credentials). Posters are cached in memory (the terminal's copies are deleted when the cache is dropped and on exit), failed downloads are not retried for two minutes, and the poster shrinks or disappears when the pane is too short. `ui.covers: false` disables it. Posters are JPEG/PNG, decoded and downscaled in the process. Library items fetch `/MediaCover/<id>/poster-250.jpg` from their Radarr/Sonarr instance with the API key; search results fall back to the provider's `remoteUrl` (fetched without credentials). Posters are cached in memory (the terminal's copies are deleted when the cache is dropped and on exit), failed downloads are not retried for two minutes, and the poster shrinks or disappears when the pane is too short. `ui.covers: false` disables it.
- **Calendar view (F5):** a two-week window (starting today, moved a week at a time with `[`/`]`, `t` returns to today) with the library's movie releases and airing episodes, grouped by day. Radarr `GET /calendar` supplies the cinema, digital and physical release dates (taken as written: midnight UTC means that calendar day) and Sonarr `GET /calendar?includeSeries=true` the episodes with their air time (shown in local time). Unmonitored items are included with `u`. A movie appears once per release date in the window. Each entry shows type, title, episode or release kind and status (downloaded / missing / upcoming); the detail pane shows poster and plot; `Enter`/`i` starts the release search and `s` an automatic search. - **Calendar view (F5):** a two-week window (starting today, moved a week at a time with `[`/`]`, `t` returns to today) with the library's movie releases and airing episodes, grouped by day. Radarr `GET /calendar` supplies the cinema, digital and physical release dates (taken as written: midnight UTC means that calendar day) and Sonarr `GET /calendar?includeSeries=true` the episodes with their air time (shown in local time). Unmonitored items are included with `u`. A movie appears once per release date in the window. Each entry shows type, title, episode or release kind and status (downloaded / missing / upcoming); the detail pane shows poster and plot; `Enter`/`i` starts the release search and `s` an automatic search.
- **Download notifications:** a background poller (independent of the visible view) reads SABnzbd's history every 15 s and announces downloads that reach `Completed` or `Failed` as a popup and log entry; the first poll only records the current state. `ui.notify_downloads` (default true) and `ui.bell` (default false, rings the terminal bell) control it. Connection errors are not reported here, the indicators and the log already do that. - **Download notifications:** a background poller (independent of the visible view) reads SABnzbd's history every 15 s and announces downloads that reach `Completed` or `Failed` as a popup and log entry; the first poll only records the current state. `ui.notify_downloads` (default true) and `ui.bell` (default false, rings the terminal bell) control it. Connection errors are not reported here, the indicators and the log already do that.
- **Config view (F6):** one list with a field for every setting of the config file, one below the other under the headings Radarr, Sonarr, SABnzbd and Interface (a single scrolling form; ↑↓ passes over the headings and ↓ after the last field reaches the Save / Revert buttons, so no Tab is needed; Tab jumps between the fields and the buttons). The `Test connections` button is part of the list, right below the SABnzbd settings; `Save` and `Revert` are in a bar below the list. The fields start from the settings in use; API keys are masked (a checkbox shows them). `Test connections` builds temporary clients from the typed values and reports each service. `Save` validates like startup does (URLs, non-empty keys, refresh 1–3600 s), then writes the file from the file's own contents plus the edits (`config.LoadFile`), so values that only come from `ARR_TUI_*` environment variables are left as the file has them and are shown greyed out; the write is atomic (temp file + rename) and keeps the file's permissions (`0600` when new); defaults such as `covers: true` stay out of the file unless it already named them. Everything then applies without a restart: the Radarr/Sonarr/SABnzbd clients are re-pointed (`Reconfigure`, guarded by a mutex), the connection indicators, add-form defaults and options are refreshed, the library and calendar reload, the Downloads interval, notifications, bell and poster style are switched live. `q` is typed text in this view (`F9` opens the help). - **Config view (F6):** one list with a field for every setting of the config file, one below the other under the headings Radarr, Sonarr, SABnzbd and Interface (a single scrolling form; ↑↓ passes over the headings and ↓ after the last field reaches the Save / Revert buttons, so no Tab is needed; Tab jumps between the fields and the buttons). The `Test` button is part of the list, right below the SABnzbd settings; `Save` and `Revert` are centered in a bar below the list. The fields start from the settings in use; API keys are masked (a checkbox shows them). `Test` builds temporary clients from the typed values and reports each service. `Save` validates like startup does (URLs, non-empty keys, refresh 1–3600 s), then writes the file from the file's own contents plus the edits (`config.LoadFile`), so values that only come from `ARR_TUI_*` environment variables are left as the file has them and are shown greyed out; the write is atomic (temp file + rename) and keeps the file's permissions (`0600` when new); defaults such as `covers: true` stay out of the file unless it already named them. Everything then applies without a restart: the Radarr/Sonarr/SABnzbd clients are re-pointed (`Reconfigure`, guarded by a mutex), the connection indicators, add-form defaults and options are refreshed, the library and calendar reload, the Downloads interval, notifications, bell and poster style are switched live. `q` is typed text in this view (`F9` opens the help).
- **Notifications and log (F7):** feedback about actions (added, deleted, grabbed, failures) appears as popups in the bottom right that fade away (errors stay longer). Everything is also kept in the Log view; only connection changes of the background polling are logged, not every failed poll. - **Notifications and log (F7):** feedback about actions (added, deleted, grabbed, failures) appears as popups in the bottom right that fade away (errors stay longer). Everything is also kept in the Log view; only connection changes of the background polling are logged, not every failed poll.
### 4.2 v0.2 (done, M8) ### 4.2 v0.2 (done, M8)
+1 -1
View File
@@ -70,7 +70,7 @@ var helpSections = []helpSection{
{"↑↓", "move through all fields, one below the other, in the sections Radarr, Sonarr, SABnzbd and Interface; ↓ after the last field reaches Save and Revert"}, {"↑↓", "move through all fields, one below the other, in the sections Radarr, Sonarr, SABnzbd and Interface; ↓ after the last field reaches Save and Revert"},
{"Tab", "jump between the fields and Save / Revert"}, {"Tab", "jump between the fields and Save / Revert"},
{"Space", "toggle a checkbox; press a button"}, {"Space", "toggle a checkbox; press a button"},
{"Test connections", "(below the SABnzbd settings) checks the three services with the values typed in"}, {"Test", "(button below the SABnzbd settings) checks the three services with the values typed in"},
{"Save, Revert", "(at the bottom) Save writes the file and applies everything at once; Revert discards edits"}, {"Save, Revert", "(at the bottom) Save writes the file and applies everything at once; Revert discards edits"},
{"q", "is typed here as text (it does not quit); F9 opens this help"}, {"q", "is typed here as text (it does not quit); F9 opens this help"},
}}, }},
+3 -2
View File
@@ -103,7 +103,7 @@ type settingsView struct {
root *tview.Flex root *tview.Flex
form *tview.Form // every field, one below the other, with section headings form *tview.Form // every field, one below the other, with section headings
testItem *formAction // the Test connections button, below the service settings testItem *formAction // the Test button, below the service settings
actions *tview.Form // the buttons below it actions *tview.Form // the buttons below it
groups []*tview.Form // keyboard order: the fields, then the buttons groups []*tview.Form // keyboard order: the fields, then the buttons
status *tview.TextView status *tview.TextView
@@ -160,7 +160,7 @@ func newSettingsView(app *App) *settingsView {
service("Sonarr", "sonarr", true) service("Sonarr", "sonarr", true)
service("SABnzbd", "sabnzbd", false) service("SABnzbd", "sabnzbd", false)
// Right below the connection settings it checks. // Right below the connection settings it checks.
v.testItem = newFormAction("Test connections", v.test) v.testItem = newFormAction("Test", v.test)
f.AddFormItem(v.testItem) f.AddFormItem(v.testItem)
f.AddFormItem(newFormHeading("Interface")) f.AddFormItem(newFormHeading("Interface"))
@@ -181,6 +181,7 @@ func newSettingsView(app *App) *settingsView {
v.actions = tview.NewForm().SetItemPadding(0) v.actions = tview.NewForm().SetItemPadding(0)
v.actions.SetBorder(true).SetTitle(" Actions ") v.actions.SetBorder(true).SetTitle(" Actions ")
v.actions.SetBorderPadding(0, 0, 1, 1) v.actions.SetBorderPadding(0, 0, 1, 1)
v.actions.SetButtonsAlign(tview.AlignCenter)
v.actions.AddButton("Save", v.save) v.actions.AddButton("Save", v.save)
v.actions.AddButton("Revert", v.revert) v.actions.AddButton("Revert", v.revert)
+2 -2
View File
@@ -285,7 +285,7 @@ func TestTestConnectionsButtonSitsBelowTheServices(t *testing.T) {
send(v.form, tcell.KeyDown) send(v.form, tcell.KeyDown)
} }
if focusedItem() != tview.FormItem(v.testItem) { if focusedItem() != tview.FormItem(v.testItem) {
t.Fatalf("↓ after the SABnzbd API key reached %T %q, want the Test connections button", focusedItem(), focusedItem().GetLabel()) t.Fatalf("↓ after the SABnzbd API key reached %T %q, want the Test button", focusedItem(), focusedItem().GetLabel())
} }
send(v.form, tcell.KeyDown) send(v.form, tcell.KeyDown)
if got := focusedItem().GetLabel(); got != "Downloads refresh (seconds)" { if got := focusedItem().GetLabel(); got != "Downloads refresh (seconds)" {
@@ -296,7 +296,7 @@ func TestTestConnectionsButtonSitsBelowTheServices(t *testing.T) {
t.Error("↑ must come back to the button") t.Error("↑ must come back to the button")
} }
// Save and Revert stay below everything; Test connections is not among them. // Save and Revert stay below everything; Test is not among them.
if v.actions.GetButtonCount() != 2 || v.actions.GetButton(0).GetLabel() != "Save" || v.actions.GetButton(1).GetLabel() != "Revert" { if v.actions.GetButtonCount() != 2 || v.actions.GetButton(0).GetLabel() != "Save" || v.actions.GetButton(1).GetLabel() != "Revert" {
t.Errorf("actions bar has %d buttons", v.actions.GetButtonCount()) t.Errorf("actions bar has %d buttons", v.actions.GetButtonCount())
} }