spinuptui 0.24.4 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -11,6 +11,22 @@ versions; such changes are called out here.
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [0.25.0] - 2026-08-12
15
+
16
+ ### Added
17
+ - **Server actions now surfaces a pending SpinupWP platform upgrade**, not just
18
+ a silent badge. The overlay (`a`) shows a banner explaining the API only
19
+ exposes it as a flag with no actionable detail, and `w` jumps straight to the
20
+ server's page in the SpinupWP web app to run it.
21
+ - **App version on the splash screen**, shown below the tagline while the first
22
+ batch of data loads.
23
+
24
+ ### Fixed
25
+ - **The reboot-pending detail line now shows the real reason when the SSH probe
26
+ fails**, instead of a generic "couldn't read detail over SSH" message — e.g.
27
+ an SSH auth failure now shows the actual error. Both this and the new upgrade
28
+ banner wrap across lines instead of overflowing the panel on longer text.
29
+
14
30
  ## [0.24.4] - 2026-08-06
15
31
 
16
32
  ### Fixed
@@ -1299,7 +1315,8 @@ Initial tagged release.
1299
1315
  ### Notes
1300
1316
  - Read-only release: works with a SpinupWP **Read Only** API token.
1301
1317
 
1302
- [Unreleased]: https://github.com/mwender/spinupwp-tui/compare/v0.24.4...HEAD
1318
+ [Unreleased]: https://github.com/mwender/spinupwp-tui/compare/v0.25.0...HEAD
1319
+ [0.25.0]: https://github.com/mwender/spinupwp-tui/compare/v0.24.4...v0.25.0
1303
1320
  [0.24.4]: https://github.com/mwender/spinupwp-tui/compare/v0.24.3...v0.24.4
1304
1321
  [0.24.3]: https://github.com/mwender/spinupwp-tui/compare/v0.24.2...v0.24.3
1305
1322
  [0.24.2]: https://github.com/mwender/spinupwp-tui/compare/v0.24.1...v0.24.2
package/README.md CHANGED
@@ -320,8 +320,10 @@ Full details: [docs/php-upgrade.md](docs/php-upgrade.md).
320
320
  ## Server actions
321
321
 
322
322
  `a` on a server reboots it or restarts a service (Nginx/PHP-FPM/MySQL/Redis),
323
- and shows *why* a reboot is pending (the actual OS packages, read over SSH).
324
- Needs a Read/Write token. Full details: [docs/server-actions.md](docs/server-actions.md).
323
+ shows *why* a reboot is pending (the actual OS packages, read over SSH), and for
324
+ a pending SpinupWP platform upgrade — which the API can't act on — offers `w` to
325
+ jump straight to it in the SpinupWP web app. Needs a Read/Write token. Full
326
+ details: [docs/server-actions.md](docs/server-actions.md).
325
327
 
326
328
  ## Site monitoring
327
329
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spinuptui",
3
- "version": "0.24.4",
3
+ "version": "0.25.0",
4
4
  "description": "SpinupTUI — a terminal dashboard for browsing, monitoring, and managing your SpinupWP servers and sites.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/ui/Splash.tsx CHANGED
@@ -4,6 +4,7 @@
4
4
  import { useEffect, useState } from "react"
5
5
  import { theme } from "../lib/theme.ts"
6
6
  import { Spinner } from "./components.tsx"
7
+ import { APP_VERSION } from "../version.ts"
7
8
 
8
9
  const TAGLINE = "Terminal control center for your SpinupWP fleet"
9
10
 
@@ -40,6 +41,8 @@ export function Splash({ status }: { status: string }) {
40
41
  <box style={{ height: 1 }} />
41
42
  <text content={TAGLINE} fg={theme.text} />
42
43
  <box style={{ height: 1 }} />
44
+ <text content={`Version ${APP_VERSION}`} fg={theme.brand} />
45
+ <box style={{ height: 1 }} />
43
46
  <Pulse />
44
47
  <box style={{ height: 2 }} />
45
48
  <box style={{ flexDirection: "row" }}>
@@ -15,6 +15,9 @@ import { Panel, Spinner, Centered } from "../components.tsx"
15
15
  import { StatusBar } from "../StatusBar.tsx"
16
16
  import { useStore } from "../store.tsx"
17
17
  import { moveSelection } from "../List.tsx"
18
+ import { openUrl } from "../../lib/open.ts"
19
+ import { serverWebUrl } from "../../lib/spinupweb.ts"
20
+ import { toast } from "../toast.ts"
18
21
  import type { RebootInfo } from "../../lib/ssh.ts"
19
22
  import type { ServerOpKind } from "../store.tsx"
20
23
 
@@ -56,9 +59,11 @@ export function ServerActions() {
56
59
  rebootInfoLoading,
57
60
  rebootInfoErrors,
58
61
  loadRebootInfo,
62
+ accountSlug,
59
63
  } = store
60
64
 
61
65
  const rebootRequired = !!server?.reboot_required
66
+ const upgradeRequired = !!server?.upgrade_required
62
67
  const [phase, setPhase] = useState<Phase>("pick")
63
68
  // Cursor starts on Reboot when one is pending, else on the first restart.
64
69
  const [index, setIndex] = useState(() => (server?.reboot_required ? 0 : 1))
@@ -108,6 +113,12 @@ export function ServerActions() {
108
113
  setTarget(ACTIONS[index])
109
114
  setPhase("confirm")
110
115
  return
116
+ case "w":
117
+ if (upgradeRequired && server) {
118
+ openUrl(serverWebUrl(server.id, accountSlug))
119
+ toast.success(accountSlug ? "Opening in SpinupWP…" : "Set accountSlug for deep links — opening dashboard")
120
+ }
121
+ return
111
122
  }
112
123
  return
113
124
  }
@@ -179,17 +190,17 @@ export function ServerActions() {
179
190
  {/* Reboot "why" context */}
180
191
  {rebootRequired && (
181
192
  <>
182
- <box style={{ flexDirection: "row", height: 1 }}>
183
- <text content="⚠ reboot pending — " fg={theme.warn} style={{ flexShrink: 0 }} />
193
+ <box style={{ flexDirection: "column" }}>
194
+ <text content="⚠ reboot pending" fg={theme.warn} wrapMode="none" />
184
195
  {infoLoading ? (
185
196
  <box style={{ flexDirection: "row" }}>
186
197
  <Spinner interval={120} />
187
198
  <text content=" checking what's pending…" fg={theme.textDim} wrapMode="none" />
188
199
  </box>
189
200
  ) : info ? (
190
- <text content={rebootSummary(info)} fg={theme.textDim} wrapMode="none" style={{ flexShrink: 1 }} />
201
+ <text content={rebootSummary(info)} fg={theme.textDim} />
191
202
  ) : infoError ? (
192
- <text content="couldn't read detail over SSH" fg={theme.textFaint} wrapMode="none" />
203
+ <text content={infoError} fg={theme.textFaint} />
193
204
  ) : (
194
205
  <text content="reboot required" fg={theme.textDim} wrapMode="none" />
195
206
  )}
@@ -197,6 +208,20 @@ export function ServerActions() {
197
208
  <box style={{ height: 1 }} />
198
209
  </>
199
210
  )}
211
+ {/* SpinupWP platform upgrade — the API only exposes this as a flag,
212
+ no detail; the only actionable path is the web app. Two lines
213
+ (not a height:1 row) so the explanation can wrap instead of
214
+ overflowing the panel — see the flexGrow/flexShrink gotcha in
215
+ CLAUDE.md. */}
216
+ {upgradeRequired && (
217
+ <>
218
+ <box style={{ flexDirection: "column" }}>
219
+ <text content="⬆ SpinupWP upgrade pending" fg={theme.warn} wrapMode="none" />
220
+ <text content="Not doable from here — press w to open it in SpinupWP." fg={theme.textDim} />
221
+ </box>
222
+ <box style={{ height: 1 }} />
223
+ </>
224
+ )}
200
225
  {ACTIONS.map((a, i) => {
201
226
  const selected = i === index
202
227
  const recommended = a.kind === "reboot" && rebootRequired
@@ -308,6 +333,7 @@ export function ServerActions() {
308
333
  return [
309
334
  { key: "↑↓/jk", label: "action" },
310
335
  { key: "⏎", label: "choose" },
336
+ ...(upgradeRequired ? [{ key: "w", label: "open in SpinupWP" }] : []),
311
337
  { key: "esc", label: "cancel" },
312
338
  ]
313
339
  case "confirm":