From c0188cc29db28cc0add722b0ce084c14b5ae932a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 25 Sep 2026 03:38:34 +0000 Subject: [PATCH] Add an "Update Now" board command Boards now hold new board app and agent releases until their nightly install window. update.install_now asks one to check and install at once. It is disruptive (the board shows its update screen, an agent update restarts the agent), asks for confirmation, gets a 10-minute deadline for the download, and its results render as a list. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VUxfdFFUkL7SmAEUvUzspy --- src/components/devices/CommandLauncher.jsx | 25 ++++++++++++++++++---- src/components/devices/CommandOutput.jsx | 22 +++++++++++++++++++ src/utils/permissions.js | 1 + 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/components/devices/CommandLauncher.jsx b/src/components/devices/CommandLauncher.jsx index 8245a69..c835cbe 100644 --- a/src/components/devices/CommandLauncher.jsx +++ b/src/components/devices/CommandLauncher.jsx @@ -1,5 +1,5 @@ import { createElement, useMemo, useState } from 'react'; -import { Camera, FileText, Loader2, MoreHorizontal, Power, RefreshCcw, RotateCw, ServerCog } from 'lucide-react'; +import { Camera, Download, FileText, Loader2, MoreHorizontal, Power, RefreshCcw, RotateCw, ServerCog } from 'lucide-react'; import DeviceService from '../../services/DeviceService'; import { useAuth } from '../../context/AuthContext'; import { COMMAND_PERMISSIONS, COMMAND_RISK, PERMISSIONS } from '../../utils/permissions'; @@ -9,9 +9,25 @@ const QUICK_COMMANDS = [ { kind: 'config.refresh', label: 'Refresh Config', icon: RefreshCcw, confirm: false }, { kind: 'chrome.screenshot', label: 'Screenshot', icon: Camera, confirm: false }, { kind: 'kiosk.restart', label: 'Restart Kiosk', icon: ServerCog, confirm: true }, - { kind: 'system.reboot', label: 'Reboot Device', icon: Power, confirm: true } + { kind: 'system.reboot', label: 'Reboot Device', icon: Power, confirm: true }, + { + kind: 'update.install_now', + label: 'Update Now', + icon: Download, + confirm: 'Check for a new board app or agent and install it now? The board shows its update screen while it installs, instead of waiting for tonight.' + } ]; +/** + * How long the agent may run a command. Most finish in seconds; an update has + * to download a release first (the install itself always finishes once it + * starts, whatever the deadline). + */ +const DEADLINE_MS = { + 'logs.tail': 45000, + 'update.install_now': 600000 +}; + /** * The three command permissions are separately grantable, so the buttons have to * carry their risk class on their face. `inspect` in particular is a surveillance @@ -55,7 +71,8 @@ function CommandLauncher({ deviceId, onIssued, disabled }) { ]); const issue = async (kind, payload = {}, confirmCommand = false) => { - if (confirmCommand && !confirm(`Issue ${kind} to this device?`)) return; + const question = typeof confirmCommand === 'string' ? confirmCommand : `Issue ${kind} to this device?`; + if (confirmCommand && !confirm(question)) return; try { setError(''); @@ -63,7 +80,7 @@ function CommandLauncher({ deviceId, onIssued, disabled }) { const issued = await DeviceService.issueCommand(deviceId, { kind, payload, - deadlineMs: kind === 'logs.tail' ? 45000 : 30000 + deadlineMs: DEADLINE_MS[kind] ?? 30000 }); onIssued?.(issued); setShowLogs(false); diff --git a/src/components/devices/CommandOutput.jsx b/src/components/devices/CommandOutput.jsx index 4b68a90..c731016 100644 --- a/src/components/devices/CommandOutput.jsx +++ b/src/components/devices/CommandOutput.jsx @@ -46,6 +46,28 @@ function CommandOutput({ command }) { ); } + // { checkError?, results: [{ file, type, version, action, message }] } + if (command.kind === 'update.install_now' && Array.isArray(output.results)) { + return ( +
+ {output.checkError && ( +
Could not check for updates: {output.checkError}
+ )} + {output.results.length === 0 ? ( +
Nothing to install: the board app and agent are up to date.
+ ) : ( +
    + {output.results.map((result, index) => ( +
  • + {result.action}: {result.message} +
  • + ))} +
+ )} +
+ ); + } + return (
       {JSON.stringify(output, null, 2)}
diff --git a/src/utils/permissions.js b/src/utils/permissions.js
index 6e944f8..66c9a59 100644
--- a/src/utils/permissions.js
+++ b/src/utils/permissions.js
@@ -74,6 +74,7 @@ export const COMMAND_PERMISSIONS = {
   'config.refresh': PERMISSIONS.BOARD_COMMAND_BENIGN,
   'kiosk.restart': PERMISSIONS.BOARD_COMMAND_DISRUPTIVE,
   'system.reboot': PERMISSIONS.BOARD_COMMAND_DISRUPTIVE,
+  'update.install_now': PERMISSIONS.BOARD_COMMAND_DISRUPTIVE,
   'chrome.screenshot': PERMISSIONS.BOARD_COMMAND_INSPECT,
   'logs.tail': PERMISSIONS.BOARD_COMMAND_INSPECT,
 };