diff --git a/webclient-vue/src/api/__tests__/modules.spec.js b/webclient-vue/src/api/__tests__/modules.spec.js index 18aa6a1..b0aee8d 100644 --- a/webclient-vue/src/api/__tests__/modules.spec.js +++ b/webclient-vue/src/api/__tests__/modules.spec.js @@ -50,6 +50,18 @@ await devicesApi.setupNewDevice({ alias: "test" }); expect(apiPost).toHaveBeenCalledWith("/api/v1/devices/setup/new-device", { alias: "test" }); }); + + it("resetup posts device_id", async () => { + const { apiPost } = await import("../client.js"); + await devicesApi.resetup(99); + expect(apiPost).toHaveBeenCalledWith("/api/v1/devices/resetup", { device_id: 99 }); + }); + + it("reset posts device_id", async () => { + const { apiPost } = await import("../client.js"); + await devicesApi.reset(42); + expect(apiPost).toHaveBeenCalledWith("/api/v1/devices/reset", { device_id: 42 }); + }); }); describe("areasApi", () => { diff --git a/webclient-vue/src/api/modules/devices.js b/webclient-vue/src/api/modules/devices.js index f184168..37ab2eb 100644 --- a/webclient-vue/src/api/modules/devices.js +++ b/webclient-vue/src/api/modules/devices.js @@ -73,6 +73,14 @@ return apiGet(`/api/v1/devices/id/${safeId(id)}/remove`); }, + resetup(id) { + return apiPost("/api/v1/devices/resetup", { device_id: id }); + }, + + reset(id) { + return apiPost("/api/v1/devices/reset", { device_id: id }); + }, + unassign(id) { return apiGet(`/api/v1/devices/id/${safeId(id)}/unassign-from-area`); }, diff --git a/webclient-vue/src/features/devices/pages/DeviceDetailPage.vue b/webclient-vue/src/features/devices/pages/DeviceDetailPage.vue index e4af3ad..ec3b8fc 100644 --- a/webclient-vue/src/features/devices/pages/DeviceDetailPage.vue +++ b/webclient-vue/src/features/devices/pages/DeviceDetailPage.vue @@ -165,6 +165,40 @@ + + +

{{ resetupDialogMessage }}

+
+ {{ resetupError }} +
+ +
+ + +

{{ resetDialogMessage }}

+
+ {{ resetError }} +
+ +
@@ -238,6 +272,16 @@ const unassignLoading = ref(false); const unassignError = ref(""); +const showResetupDialog = ref(false); +const resetupDialogMessage = ref(""); +const resetupLoading = ref(false); +const resetupError = ref(""); + +const showResetDialog = ref(false); +const resetDialogMessage = ref(""); +const resetLoading = ref(false); +const resetError = ref(""); + const deviceActions = computed(() => { const actions = [{ label: "Edit", icon: "ph-pencil", onSelect: openEdit }]; @@ -252,12 +296,14 @@ } actions.push( + { label: "ReSetup", icon: "ph-gear", onSelect: openResetup }, { label: "Reboot", icon: "ph-arrow-clockwise", disabled: devicesStore.isRebooting(device.value?.id), onSelect: reboot, }, + { label: "Reset", icon: "ph-x", danger: true, onSelect: openReset }, { label: "Remove", icon: "ph-trash", danger: true, onSelect: remove } ); @@ -368,6 +414,56 @@ } } +function openResetup() { + if (!device.value) return; + resetupDialogMessage.value = `Are you sure you want to repeat setup for device "${device.value.name || device.value.alias}"?`; + resetupError.value = ""; + showResetupDialog.value = true; +} + +async function submitResetup() { + const id = deviceId.value; + resetupLoading.value = true; + resetupError.value = ""; + + const result = await devicesStore.resetupDevice(id); + resetupLoading.value = false; + + if (!result.ok) { + resetupError.value = result.error?.message || "Failed to repeat device setup"; + return; + } + + showResetupDialog.value = false; + toast.success({ title: "Success", text: `Device ${device.value?.name || device.value?.alias || "#" + id} setup repeated` }); + router.push({ name: "devices" }); +} + +function openReset() { + if (!device.value) return; + resetDialogMessage.value = `Are you sure you want to reset device "${device.value.name || device.value.alias}"?`; + resetError.value = ""; + showResetDialog.value = true; +} + +async function submitReset() { + const id = deviceId.value; + resetLoading.value = true; + resetError.value = ""; + + const result = await devicesStore.resetDevice(id); + resetLoading.value = false; + + if (!result.ok) { + resetError.value = result.error?.message || "Failed to reset device"; + return; + } + + showResetDialog.value = false; + toast.success({ title: "Success", text: `Device ${device.value?.name || device.value?.alias || "#" + id} reset` }); + router.push({ name: "devices" }); +} + async function loadStatus() { const id = deviceId.value; if (!id || !device.value) return; diff --git a/webclient-vue/src/stores/__tests__/devices.spec.js b/webclient-vue/src/stores/__tests__/devices.spec.js index 2eb56c3..7d1e6d1 100644 --- a/webclient-vue/src/stores/__tests__/devices.spec.js +++ b/webclient-vue/src/stores/__tests__/devices.spec.js @@ -7,6 +7,8 @@ list: vi.fn(), status: vi.fn(), reboot: vi.fn(), + resetup: vi.fn(), + reset: vi.fn(), }, })); diff --git a/webclient-vue/src/stores/devices.js b/webclient-vue/src/stores/devices.js index 4aba72b..c533790 100644 --- a/webclient-vue/src/stores/devices.js +++ b/webclient-vue/src/stores/devices.js @@ -237,6 +237,14 @@ return devicesApi.remove(id); } + async function resetupDevice(id) { + return devicesApi.resetup(id); + } + + async function resetDevice(id) { + return devicesApi.reset(id); + } + async function unassignDevice(id) { const result = await devicesApi.unassign(id); if (result.ok && currentDevice.value) { @@ -288,6 +296,8 @@ removeDevice, unassignDevice, assignToArea, + resetupDevice, + resetDevice, clearDeviceDetail, }; });