feat: add Register Update dialog to UpdatesDashboard#70
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a “Register Update” entry point to the Software Updates dashboard so operators can create new updates via POST /updates from the UI, backed by a new Zustand store action.
Changes:
- Added
registerUpdateaction to the Zustand store to POST update registration payloads. - Added
RegisterUpdateDialogcomponent and wired it intoUpdatesDashboardvia a new toolbar button. - Added new
Label/CheckboxUI primitives and unit tests for the dialog.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/lib/store.ts | Adds registerUpdate store action calling client.POST('/updates', …) |
| src/components/UpdatesDashboard.tsx | Adds “Register Update” button and renders the dialog |
| src/components/RegisterUpdateDialog.tsx | Implements the registration dialog UI, validation, and payload construction |
| src/components/RegisterUpdateDialog.test.tsx | Adds unit tests for dialog validation and submission behavior |
| src/components/ui/label.tsx | Introduces a Label primitive used by the dialog |
| src/components/ui/checkbox.tsx | Introduces a Checkbox primitive used by the dialog |
Add RegisterUpdateDialog component with id, name, automated, and JSON metadata fields. Includes client-side validation (required id, JSON parse check) and merges metadata extras into the request body. Add registerUpdate store action and install shadcn label/checkbox primitives.
Wire RegisterUpdateDialog into UpdatesDashboard with a toolbar button. Default update_name to id when the name field is left blank so the gateway field requirement is always satisfied.
…dowing - useEffect resets id/name/automated/metadata/error when dialog closes, so reopening starts clean instead of showing stale inputs or errors. - Strip id/update_name/automated from the JSON metadata object and spread extras before the UI-controlled fields so user metadata cannot override validated top-level values. - Add regression test covering the reserved-key stripping.
86faee6 to
e495d64
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/components/UpdatesDashboard.tsx:65
handleRegisterperforms a mutation but doesn’t use the existingactionAbortRefcancellation pattern used by prepare/execute/automated/delete. If the dashboard unmounts while a register request is in-flight, it can still calltoast.*andrefresh()afterward (the comment on lines 57-59 suggests the intent is to avoid stale toasts/state updates on unmounted components). Consider extendingregisterUpdateto accept an optionalAbortSignaland wiringactionAbortRef.current?.signalthrough, with the samesignal.aborted/AbortErrorguards used inhandleAction.
const handleRegister = async (body: { id: string; [key: string]: unknown }) => {
try {
await registerUpdate(body);
toast.success(`Registered ${body.id}`);
refresh();
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
toast.error(`Register failed: ${msg}`);
throw e;
}
};
// AbortController for mutation actions (prepare/execute/automated/delete).
// Aborted on unmount so in-flight requests don't resolve into setBusyIds
// on an unmounted component or issue stale toast notifications.
const actionAbortRef = useRef<AbortController | null>(null);
useEffect(() => {
const controller = new AbortController();
actionAbortRef.current = controller;
return () => controller.abort();
}, []);
…bmitting
- store.registerUpdate now takes an optional AbortSignal and forwards
it to client.POST('/updates'), matching the prepare/execute/automated/
delete pattern.
- UpdatesDashboard.handleRegister threads actionAbortRef.current?.signal
through and guards toast.success/refresh with signal.aborted so a
late-resolving register cannot fire stale toasts or refresh after
unmount.
- RegisterUpdateDialog now ignores Escape / pointer-down-outside /
onOpenChange(false) and disables Cancel while submitting, so a
slow submit cannot close a dialog the user has since reopened.
mfaferek93
reviewed
Apr 19, 2026
- Drop the redundant toast.error in handleRegister: the dialog already
surfaces the rejection as an inline error, so users were seeing the
same failure twice. The throw is kept so the dialog stays open for
retry (setError runs in the dialog's catch).
- Hide the Register Update button when the gateway reports the updates
API as unavailable (notAvailable/501) so the UI does not expose a
flow that can only fail.
- Hoist RegisterUpdateDialog render out of the five conditional
branches (loading/notAvailable/error/empty/list) into the shared
shell via a local 'body' variable.
- Always send automated=true|false instead of omitting it when false,
so the wire body has a stable shape regardless of UI state.
- Add role=alert, aria-invalid, and aria-describedby so validation
errors are announced by screen readers and associated with the id
and metadata inputs.
- Document why the client.POST('/updates', { body: body as never })
cast is intentional (gateway keeps the schema open for vendor
extras, endpoint not yet in the generated client).
- Extend RegisterUpdateDialog tests: inline error on submit rejection,
non-object JSON rejection parameterised across string/array/null/
number, and automated=false when unchecked.
- RegisterUpdateDialog now renders a DialogDescription (removes the Radix 'Missing Description / aria-describedby' warning that was firing on every render and every test) and shows a Loader2 spinner with 'Registering...' label while the submit is in flight, so the user gets visual feedback during the network call instead of just a silently disabled button. - UpdateCard: introduce displayProgress() which returns 100 whenever status.status === 'completed', regardless of whether the gateway dropped the progress field or left it below 100. Applied to both the main bar and each sub_progress row. Prevents the bar from disappearing or freezing mid-way at end-of-update. - Tests: assert the 100% bar appears when completed has no progress field and that mid-progress values snap to 100% when status flips to completed.
mfaferek93
approved these changes
Apr 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a Register Update button to the Software Updates dashboard toolbar, opening a dialog that POSTs to SOVD
/updates. Closes #69.The dialog collects
id(required),update_name(defaults toidwhen blank),automated(checkbox), and free-form JSON additional metadata. Client-side validates required id and JSON parse; merges metadata extras into the request body before submitting. A newregisterUpdateZustand store action handles the HTTP call.Generic by design: no vendor-specific fields at the top level - all vendor extras go through the JSON metadata field, so the same dialog works for Uptane, Mender, and any other UpdateProvider plugin.
Issue
Type
Testing
src/components/RegisterUpdateDialog.test.tsx(4 cases: render, required id, JSON parse, merged body submit).UpdatesDashboard.test.tsxsuite (11 tests) passes - new toolbar button does not disturb existing behavior.npm run typecheck) clean.Checklist
npm run lint)npm run build)Base branch
This PR targets
fix/uds-data-tab-rendering(PR #68) rather thanmain, so it stacks on top of that Data-tab hardening work. Rebase onto main is trivial once #68 merges.