From 7caba52e57222be32d12df9971f34f1c7c171a6b Mon Sep 17 00:00:00 2001 From: lda Date: Sat, 10 Oct 2026 17:28:44 +0700 Subject: [PATCH] feat(devices): add rename and forget controls and clarify fleet labels --- ...59850fc9935f40385061b029fab5892a2aded.json | 12 ++ docs/roadmap.md | 6 +- ui/src/api.ts | 21 +++ ui/src/pages/DevicesPage.tsx | 13 +- ui/src/pages/fleet/FleetComponents.tsx | 6 +- .../pages/fleet/FleetDeviceDetailsDialog.tsx | 131 +++++++++++++++++- ...59850fc9935f40385061b029fab5892a2aded.json | 12 ++ wakey-control-plane/src/api/control.rs | 2 +- .../src/api/control/devices.rs | 32 +++++ .../src/api/control/fleet/build.rs | 53 ++++++- .../src/api/control/fleet/tests.rs | 41 +++++- wakey-control-plane/src/api/mod.rs | 3 +- wakey-control-plane/src/runtime/mod.rs | 4 +- wakey-control-plane/src/state/store.rs | 73 ++++++++++ .../src/state/store/devices.rs | 23 +++ 15 files changed, 411 insertions(+), 21 deletions(-) create mode 100644 .sqlx/query-897b794a7f94af42b545d74a5b859850fc9935f40385061b029fab5892a2aded.json create mode 100644 wakey-control-plane/.sqlx/query-897b794a7f94af42b545d74a5b859850fc9935f40385061b029fab5892a2aded.json diff --git a/.sqlx/query-897b794a7f94af42b545d74a5b859850fc9935f40385061b029fab5892a2aded.json b/.sqlx/query-897b794a7f94af42b545d74a5b859850fc9935f40385061b029fab5892a2aded.json new file mode 100644 index 0000000..5fb069e --- /dev/null +++ b/.sqlx/query-897b794a7f94af42b545d74a5b859850fc9935f40385061b029fab5892a2aded.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "UPDATE known_devices SET display_name = ?1, updated_at_unix = ?2 WHERE device_id = ?3", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "897b794a7f94af42b545d74a5b859850fc9935f40385061b029fab5892a2aded" +} diff --git a/docs/roadmap.md b/docs/roadmap.md index 833cea7..5b55f4b 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -22,10 +22,10 @@ live browser recovery and shutdown behavior are not established by those checks. ## Device Management And Presentation -- [ ] Make **forget remembered device** reachable in the frontend. Verify the existing backend before adding routes: remove the known identity and its identifiers, not agent observations. An observed device may reappear as unknown. -- [ ] Add remembered-device rename support, with backend validation and frontend editing. +- [x] Make **forget remembered device** reachable in the frontend, with confirmation. The existing backend removes the known identity and its identifiers, not agent observations. An observed device may reappear as unknown. +- [x] Add remembered-device rename support, with backend validation and frontend editing (`PATCH /api/v1/control/devices/{device_id}`). Pinned state and identifiers are preserved. - [ ] Check and complete editing of remembered-device notes. -- [ ] Replace generic unnamed-row labels with useful fallbacks. Offline IP-only rows: `Offline device: `; unknown-presence IP-only rows: `Device: `. Use MAC fallback when no IP exists; retain a generic label only when neither exists. +- [x] Replace generic unnamed-row labels with useful fallbacks. Offline IP-only rows: `Offline device: `; unknown-presence IP-only rows: `Device: `. Use MAC fallback when no IP exists; retain a generic label only when neither exists. Inactive endpoint addresses can label a row without becoming active IPs or wake routes. Unknown-only fleet groups remain unknown. - [ ] Review frontend visibility defaults. Keep an explicit all-rows option; do not hide evidence just to fix poor labels. The user deliberately enabled all-row querying. Identifier detachment already exists in the current frontend source; it is **not** diff --git a/ui/src/api.ts b/ui/src/api.ts index c9e51d1..7c7fa9f 100644 --- a/ui/src/api.ts +++ b/ui/src/api.ts @@ -409,6 +409,27 @@ export function createKnownDevice(input: { }); } +export function renameKnownDevice( + deviceId: string, + displayName: string, +): Promise { + return request( + `/api/v1/control/devices/${encodeURIComponent(deviceId)}`, + { + method: "PATCH", + body: JSON.stringify({ display_name: displayName }), + }, + ); +} + +export function forgetKnownDevice( + deviceId: string, +): Promise<{ device_id: string; forgotten: boolean }> { + return request(`/api/v1/control/devices/${encodeURIComponent(deviceId)}`, { + method: "DELETE", + }); +} + export function attachDeviceIdentifier( deviceId: string, identifier: { kind: string; value: string }, diff --git a/ui/src/pages/DevicesPage.tsx b/ui/src/pages/DevicesPage.tsx index eaf1d8b..8c0471e 100644 --- a/ui/src/pages/DevicesPage.tsx +++ b/ui/src/pages/DevicesPage.tsx @@ -91,6 +91,14 @@ export function DevicesPage({ agents, onAfterWake, onRefresh }: Props) { if (seq !== loadSeqRef.current) return; setDevices(nextDevices); setKnownDevices(nextKnownDevices); + // Keep the open dialog current after rename/identifier mutations. + setDetails((current) => + current + ? (nextDevices.find( + (device) => device.device_key === current.device_key, + ) ?? null) + : null, + ); } catch (err) { if (seq !== loadSeqRef.current) return; toast.error("Failed to load fleet", { description: String(err) }); @@ -263,6 +271,7 @@ export function DevicesPage({ agents, onAfterWake, onRefresh }: Props) { label="Identity" value={known} values={knownFilters} + labels={{ known: "saved", unknown: "unsaved" }} onChange={(value) => setKnown(value as KnownFilter)} />