@switchbot/homebridge-switchbot 5.0.0-beta.98 → 5.0.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/.changeset/config.json +14 -0
- package/.github/copilot-instructions.md +39 -0
- package/.github/workflows/ci.yml +4 -1
- package/.github/workflows/manual-e2e.yml +6 -3
- package/.github/workflows/release.yml +64 -15
- package/.github/workflows/stale.yml +2 -4
- package/.husky/pre-push +15 -0
- package/CHANGELOG.md +126 -134
- package/MIGRATION.md +16 -6
- package/README.md +84 -3
- package/TODO.md +263 -0
- package/config.schema.json +229 -36
- package/dist/SwitchBotHAPPlatform.d.ts +133 -0
- package/dist/SwitchBotHAPPlatform.d.ts.map +1 -0
- package/dist/SwitchBotHAPPlatform.js +555 -0
- package/dist/SwitchBotHAPPlatform.js.map +1 -0
- package/dist/SwitchBotMatterPlatform.d.ts +141 -0
- package/dist/SwitchBotMatterPlatform.d.ts.map +1 -0
- package/dist/SwitchBotMatterPlatform.js +536 -0
- package/dist/SwitchBotMatterPlatform.js.map +1 -0
- package/dist/device-types.d.ts +31 -0
- package/dist/device-types.d.ts.map +1 -0
- package/dist/device-types.js +246 -0
- package/dist/device-types.js.map +1 -0
- package/dist/deviceCommandMapper.d.ts +10 -0
- package/dist/deviceCommandMapper.d.ts.map +1 -0
- package/dist/deviceCommandMapper.js +319 -0
- package/dist/deviceCommandMapper.js.map +1 -0
- package/dist/deviceFactory.d.ts +3 -2
- package/dist/deviceFactory.d.ts.map +1 -1
- package/dist/deviceFactory.js +107 -29
- package/dist/deviceFactory.js.map +1 -1
- package/dist/devices/genericDevice.d.ts +59 -37
- package/dist/devices/genericDevice.d.ts.map +1 -1
- package/dist/devices/genericDevice.js +376 -78
- package/dist/devices/genericDevice.js.map +1 -1
- package/dist/errors.d.ts +38 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +32 -0
- package/dist/errors.js.map +1 -0
- package/dist/homebridge-ui/device-types.js +246 -0
- package/dist/homebridge-ui/device-types.js.map +1 -0
- package/dist/homebridge-ui/deviceCommandMapper.js +319 -0
- package/dist/homebridge-ui/deviceCommandMapper.js.map +1 -0
- package/dist/homebridge-ui/endpoints/config.d.ts +3 -0
- package/dist/homebridge-ui/endpoints/config.d.ts.map +1 -0
- package/dist/homebridge-ui/endpoints/config.js +90 -0
- package/dist/homebridge-ui/endpoints/config.js.map +1 -0
- package/dist/homebridge-ui/endpoints/devices.d.ts +6 -0
- package/dist/homebridge-ui/endpoints/devices.d.ts.map +1 -0
- package/dist/homebridge-ui/endpoints/devices.js +144 -0
- package/dist/homebridge-ui/endpoints/devices.js.map +1 -0
- package/dist/homebridge-ui/endpoints/discovery.d.ts +7 -0
- package/dist/homebridge-ui/endpoints/discovery.d.ts.map +1 -0
- package/dist/homebridge-ui/endpoints/discovery.js +219 -0
- package/dist/homebridge-ui/endpoints/discovery.js.map +1 -0
- package/dist/homebridge-ui/errors.js +32 -0
- package/dist/homebridge-ui/errors.js.map +1 -0
- package/dist/homebridge-ui/homebridge-ui/endpoints/config.js +90 -0
- package/dist/homebridge-ui/homebridge-ui/endpoints/config.js.map +1 -0
- package/dist/homebridge-ui/homebridge-ui/endpoints/devices.js +144 -0
- package/dist/homebridge-ui/homebridge-ui/endpoints/devices.js.map +1 -0
- package/dist/homebridge-ui/homebridge-ui/endpoints/discovery.js +219 -0
- package/dist/homebridge-ui/homebridge-ui/endpoints/discovery.js.map +1 -0
- package/dist/homebridge-ui/homebridge-ui/server.js +11 -0
- package/dist/homebridge-ui/homebridge-ui/server.js.map +1 -0
- package/dist/homebridge-ui/homebridge-ui/utils/config-parser.js +108 -0
- package/dist/homebridge-ui/homebridge-ui/utils/config-parser.js.map +1 -0
- package/dist/homebridge-ui/homebridge-ui/utils/device-migration.js +111 -0
- package/dist/homebridge-ui/homebridge-ui/utils/device-migration.js.map +1 -0
- package/dist/homebridge-ui/homebridge-ui/utils/logger.js +17 -0
- package/dist/homebridge-ui/homebridge-ui/utils/logger.js.map +1 -0
- package/dist/homebridge-ui/public/css/styles.css +483 -0
- package/dist/homebridge-ui/public/index.html +197 -621
- package/dist/homebridge-ui/public/js/advanced-settings.d.ts +3 -0
- package/dist/homebridge-ui/public/js/advanced-settings.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/advanced-settings.js +95 -0
- package/dist/homebridge-ui/public/js/advanced-settings.js.map +1 -0
- package/dist/homebridge-ui/public/js/advanced-settings.ts +94 -0
- package/dist/homebridge-ui/public/js/api.d.ts +66 -0
- package/dist/homebridge-ui/public/js/api.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/api.js +295 -0
- package/dist/homebridge-ui/public/js/api.js.map +1 -0
- package/dist/homebridge-ui/public/js/api.ts +355 -0
- package/dist/homebridge-ui/public/js/app.d.ts +2 -0
- package/dist/homebridge-ui/public/js/app.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/app.js +3722 -0
- package/dist/homebridge-ui/public/js/app.js.map +7 -0
- package/dist/homebridge-ui/public/js/app.ts +22 -0
- package/dist/homebridge-ui/public/js/constants.d.ts +2 -0
- package/dist/homebridge-ui/public/js/constants.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/constants.js +2 -0
- package/dist/homebridge-ui/public/js/constants.js.map +1 -0
- package/dist/homebridge-ui/public/js/constants.ts +1 -0
- package/dist/homebridge-ui/public/js/credentials.d.ts +3 -0
- package/dist/homebridge-ui/public/js/credentials.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/credentials.js +99 -0
- package/dist/homebridge-ui/public/js/credentials.js.map +1 -0
- package/dist/homebridge-ui/public/js/credentials.ts +105 -0
- package/dist/homebridge-ui/public/js/devices-delete.d.ts +3 -0
- package/dist/homebridge-ui/public/js/devices-delete.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/devices-delete.js +199 -0
- package/dist/homebridge-ui/public/js/devices-delete.js.map +1 -0
- package/dist/homebridge-ui/public/js/devices-delete.ts +227 -0
- package/dist/homebridge-ui/public/js/devices.d.ts +9 -0
- package/dist/homebridge-ui/public/js/devices.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/devices.js +98 -0
- package/dist/homebridge-ui/public/js/devices.js.map +1 -0
- package/dist/homebridge-ui/public/js/devices.ts +106 -0
- package/dist/homebridge-ui/public/js/discovery.d.ts +9 -0
- package/dist/homebridge-ui/public/js/discovery.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/discovery.js +1201 -0
- package/dist/homebridge-ui/public/js/discovery.js.map +1 -0
- package/dist/homebridge-ui/public/js/discovery.ts +1335 -0
- package/dist/homebridge-ui/public/js/logger.d.ts +7 -0
- package/dist/homebridge-ui/public/js/logger.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/logger.js +17 -0
- package/dist/homebridge-ui/public/js/logger.js.map +1 -0
- package/dist/homebridge-ui/public/js/logger.ts +17 -0
- package/dist/homebridge-ui/public/js/modal.d.ts +5 -0
- package/dist/homebridge-ui/public/js/modal.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/modal.js +35 -0
- package/dist/homebridge-ui/public/js/modal.js.map +1 -0
- package/dist/homebridge-ui/public/js/modal.ts +35 -0
- package/dist/homebridge-ui/public/js/modals.d.ts +15 -0
- package/dist/homebridge-ui/public/js/modals.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/modals.js +675 -0
- package/dist/homebridge-ui/public/js/modals.js.map +1 -0
- package/dist/homebridge-ui/public/js/modals.ts +765 -0
- package/dist/homebridge-ui/public/js/render.d.ts +71 -0
- package/dist/homebridge-ui/public/js/render.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/render.js +960 -0
- package/dist/homebridge-ui/public/js/render.js.map +1 -0
- package/dist/homebridge-ui/public/js/render.ts +1084 -0
- package/dist/homebridge-ui/public/js/toast.d.ts +6 -0
- package/dist/homebridge-ui/public/js/toast.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/toast.js +38 -0
- package/dist/homebridge-ui/public/js/toast.js.map +1 -0
- package/dist/homebridge-ui/public/js/toast.ts +44 -0
- package/dist/homebridge-ui/public/js/types.d.ts +23 -0
- package/dist/homebridge-ui/public/js/types.d.ts.map +1 -0
- package/dist/homebridge-ui/public/js/types.js +2 -0
- package/dist/homebridge-ui/public/js/types.js.map +1 -0
- package/dist/homebridge-ui/public/js/types.ts +26 -0
- package/dist/homebridge-ui/server.d.ts +1 -3
- package/dist/homebridge-ui/server.d.ts.map +1 -1
- package/dist/homebridge-ui/server.js +8 -450
- package/dist/homebridge-ui/server.js.map +1 -1
- package/dist/homebridge-ui/settings.js +8 -0
- package/dist/homebridge-ui/settings.js.map +1 -0
- package/dist/homebridge-ui/switchbotClient.js +247 -0
- package/dist/homebridge-ui/switchbotClient.js.map +1 -0
- package/dist/homebridge-ui/utils/config-parser.d.ts +39 -0
- package/dist/homebridge-ui/utils/config-parser.d.ts.map +1 -0
- package/dist/homebridge-ui/utils/config-parser.js +108 -0
- package/dist/homebridge-ui/utils/config-parser.js.map +1 -0
- package/dist/homebridge-ui/utils/device-migration.d.ts +35 -0
- package/dist/homebridge-ui/utils/device-migration.d.ts.map +1 -0
- package/dist/homebridge-ui/utils/device-migration.js +111 -0
- package/dist/homebridge-ui/utils/device-migration.js.map +1 -0
- package/dist/homebridge-ui/utils/logger.d.ts +7 -0
- package/dist/homebridge-ui/utils/logger.d.ts.map +1 -0
- package/dist/homebridge-ui/utils/logger.js +17 -0
- package/dist/homebridge-ui/utils/logger.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/settings.d.ts +1 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +1 -0
- package/dist/settings.js.map +1 -1
- package/dist/switchbotClient.d.ts +12 -10
- package/dist/switchbotClient.d.ts.map +1 -1
- package/dist/switchbotClient.js +156 -103
- package/dist/switchbotClient.js.map +1 -1
- package/dist/utils.d.ts +76 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +1121 -4
- package/dist/utils.js.map +1 -1
- package/docs/assets/highlight.css +16 -2
- package/docs/assets/main.js +1 -1
- package/docs/index.html +82 -5
- package/docs/variables/default.html +3 -1
- package/eslint.config.js +9 -5
- package/nodemon.json +2 -2
- package/package.json +34 -21
- package/scripts/build-ui.js +37 -0
- package/scripts/free-dev-ports.mjs +105 -0
- package/scripts/generate-matter-maps.js +34 -17
- package/scripts/sync-device-types.mjs +31 -0
- package/src/SwitchBotHAPPlatform.ts +558 -0
- package/src/SwitchBotMatterPlatform.ts +538 -0
- package/src/device-types.js +246 -0
- package/src/device-types.js.map +1 -0
- package/src/device-types.ts +261 -0
- package/src/deviceCommandMapper.js +319 -0
- package/src/deviceCommandMapper.js.map +1 -0
- package/src/deviceCommandMapper.ts +333 -0
- package/src/deviceFactory.ts +125 -45
- package/src/devices/genericDevice.ts +411 -69
- package/src/errors.js +32 -0
- package/src/errors.js.map +1 -0
- package/src/errors.ts +35 -0
- package/src/homebridge-ui/endpoints/config.ts +110 -0
- package/src/homebridge-ui/endpoints/devices.ts +153 -0
- package/src/homebridge-ui/endpoints/discovery.ts +240 -0
- package/src/homebridge-ui/public/css/styles.css +483 -0
- package/src/homebridge-ui/public/index.html +197 -621
- package/src/homebridge-ui/public/js/advanced-settings.ts +94 -0
- package/src/homebridge-ui/public/js/api.ts +355 -0
- package/src/homebridge-ui/public/js/app.ts +22 -0
- package/src/homebridge-ui/public/js/constants.ts +1 -0
- package/src/homebridge-ui/public/js/credentials.ts +105 -0
- package/src/homebridge-ui/public/js/devices-delete.ts +227 -0
- package/src/homebridge-ui/public/js/devices.ts +106 -0
- package/src/homebridge-ui/public/js/discovery.ts +1335 -0
- package/src/homebridge-ui/public/js/logger.ts +17 -0
- package/src/homebridge-ui/public/js/modal.ts +35 -0
- package/src/homebridge-ui/public/js/modals.ts +765 -0
- package/src/homebridge-ui/public/js/render.ts +1084 -0
- package/src/homebridge-ui/public/js/toast.ts +44 -0
- package/src/homebridge-ui/public/js/types.ts +26 -0
- package/src/homebridge-ui/server.ts +9 -526
- package/src/homebridge-ui/utils/config-parser.ts +125 -0
- package/src/homebridge-ui/utils/device-migration.ts +144 -0
- package/src/homebridge-ui/utils/logger.ts +17 -0
- package/src/index.ts +12 -2
- package/src/settings.js +8 -0
- package/src/settings.js.map +1 -0
- package/src/settings.ts +2 -0
- package/src/switchbotClient.js +247 -0
- package/src/switchbotClient.js.map +1 -0
- package/src/switchbotClient.ts +177 -114
- package/src/utils.ts +1133 -5
- package/test/client/switchbot-client-debounce.spec.ts +35 -0
- package/test/client/switchbot-client-openapi.spec.ts +19 -0
- package/test/client/switchbotClient.spec.ts +64 -0
- package/test/device/device-mapping.spec.ts +23 -0
- package/test/device/deviceBase.spec.ts +26 -0
- package/test/device/deviceFactory-edge.spec.ts +15 -0
- package/test/device/deviceFactory.spec.ts +33 -0
- package/test/device/fan-swing.spec.ts +34 -0
- package/test/device/genericDevice-blepoll.spec.ts +47 -0
- package/test/device/irdevice.spec.ts +9 -0
- package/test/device/lock-users.spec.ts +35 -0
- package/test/device/matter-descriptors.spec.ts +22 -0
- package/test/device/matter-device-state.spec.ts +37 -0
- package/test/e2e/run-e2e.spec.ts +18 -19
- package/test/errors/errors.spec.ts +10 -0
- package/test/helpers/matter-harness.ts +20 -9
- package/test/homebridge-ui/server.spec.ts +9 -0
- package/test/platform/accessory-restore.spec.ts +37 -0
- package/test/platform/matter-childbridge.spec.ts +34 -0
- package/test/platform/matter-integration.spec.ts +33 -0
- package/test/platform/platform-edge.spec.ts +73 -0
- package/test/platform/platform.integration.spec.ts +34 -0
- package/test/utils/utils-extra.spec.ts +10 -0
- package/test/utils/utils.spec.ts +53 -0
- package/todo/TODO.md +80 -0
- package/tsconfig.ui.json +11 -0
- package/.github/npm-version-script-esm.js +0 -97
- package/.github/workflows/beta-release.yml +0 -52
- package/dist/platform.d.ts +0 -35
- package/dist/platform.d.ts.map +0 -1
- package/dist/platform.js +0 -850
- package/dist/platform.js.map +0 -1
- package/src/platform.ts +0 -867
- package/test/accessory-restore.spec.ts +0 -73
- package/test/device-mapping.spec.ts +0 -37
- package/test/deviceFactory.spec.ts +0 -18
- package/test/fan-swing.spec.ts +0 -29
- package/test/lock-users.spec.ts +0 -44
- package/test/matter-childbridge.spec.ts +0 -55
- package/test/matter-descriptors.spec.ts +0 -97
- package/test/matter-device-state.spec.ts +0 -101
- package/test/matter-integration.spec.ts +0 -70
- package/test/platform.integration.spec.ts +0 -55
- package/test/switchbot-client-debounce.spec.ts +0 -131
- package/test/switchbot-client-openapi.spec.ts +0 -56
- package/test/switchbotClient.spec.ts +0 -10
- package/test/utils.spec.ts +0 -20
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { deleteAllDevices as apiDeleteAllDevices, deleteDevice as apiDeleteDevice, fetchDevices, syncParentPluginConfigFromDisk } from './api.js'
|
|
2
|
+
import { uiLog } from './logger.js'
|
|
3
|
+
import { hideBusyUi, showBusyUi } from './modal.js'
|
|
4
|
+
import { renderDeviceList } from './render.js'
|
|
5
|
+
import { toastError, toastSuccess, toastWarning } from './toast.js'
|
|
6
|
+
|
|
7
|
+
async function confirmDeleteDialog(deviceNameOrId: string): Promise<boolean> {
|
|
8
|
+
return new Promise((resolve) => {
|
|
9
|
+
const overlay = document.createElement('div')
|
|
10
|
+
overlay.style.position = 'fixed'
|
|
11
|
+
overlay.style.top = '0'
|
|
12
|
+
overlay.style.left = '0'
|
|
13
|
+
overlay.style.width = '100%'
|
|
14
|
+
overlay.style.height = '100%'
|
|
15
|
+
overlay.style.background = 'rgba(0,0,0,0.6)'
|
|
16
|
+
overlay.style.display = 'flex'
|
|
17
|
+
overlay.style.alignItems = 'center'
|
|
18
|
+
overlay.style.justifyContent = 'center'
|
|
19
|
+
overlay.style.zIndex = '10000'
|
|
20
|
+
|
|
21
|
+
const modal = document.createElement('div')
|
|
22
|
+
modal.style.background = getComputedStyle(document.body).backgroundColor
|
|
23
|
+
modal.style.color = getComputedStyle(document.body).color
|
|
24
|
+
modal.style.padding = '20px'
|
|
25
|
+
modal.style.borderRadius = '10px'
|
|
26
|
+
modal.style.minWidth = '340px'
|
|
27
|
+
modal.style.maxWidth = '90vw'
|
|
28
|
+
modal.style.boxShadow = '0 12px 40px rgba(0,0,0,0.35)'
|
|
29
|
+
|
|
30
|
+
const title = document.createElement('h3')
|
|
31
|
+
title.textContent = 'Delete Device'
|
|
32
|
+
title.style.margin = '0 0 10px 0'
|
|
33
|
+
|
|
34
|
+
const message = document.createElement('p')
|
|
35
|
+
message.textContent = `Remove "${deviceNameOrId}" from configuration?`
|
|
36
|
+
message.style.margin = '0 0 16px 0'
|
|
37
|
+
|
|
38
|
+
const actions = document.createElement('div')
|
|
39
|
+
actions.style.display = 'flex'
|
|
40
|
+
actions.style.justifyContent = 'flex-end'
|
|
41
|
+
actions.style.gap = '8px'
|
|
42
|
+
|
|
43
|
+
const cancelBtn = document.createElement('button')
|
|
44
|
+
cancelBtn.textContent = 'Cancel'
|
|
45
|
+
cancelBtn.style.background = '#6b7280'
|
|
46
|
+
|
|
47
|
+
const deleteBtn = document.createElement('button')
|
|
48
|
+
deleteBtn.textContent = 'Delete'
|
|
49
|
+
deleteBtn.style.background = '#ef4444'
|
|
50
|
+
|
|
51
|
+
const cleanup = (result: boolean) => {
|
|
52
|
+
overlay.remove()
|
|
53
|
+
resolve(result)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
cancelBtn.onclick = () => cleanup(false)
|
|
57
|
+
deleteBtn.onclick = () => cleanup(true)
|
|
58
|
+
overlay.addEventListener('click', (event) => {
|
|
59
|
+
if (event.target === overlay) {
|
|
60
|
+
cleanup(false)
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
actions.appendChild(cancelBtn)
|
|
65
|
+
actions.appendChild(deleteBtn)
|
|
66
|
+
modal.appendChild(title)
|
|
67
|
+
modal.appendChild(message)
|
|
68
|
+
modal.appendChild(actions)
|
|
69
|
+
overlay.appendChild(modal)
|
|
70
|
+
document.body.appendChild(overlay)
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function deleteDeviceFromConfig(deviceId: string, deviceName: string): Promise<void> {
|
|
75
|
+
uiLog.info('Delete button clicked for device:', deviceId, deviceName)
|
|
76
|
+
|
|
77
|
+
const confirmed = await confirmDeleteDialog(deviceName || deviceId)
|
|
78
|
+
if (!confirmed) {
|
|
79
|
+
uiLog.info('Delete cancelled by user')
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
showBusyUi()
|
|
85
|
+
uiLog.info('Deleting device from config:', deviceId)
|
|
86
|
+
const resp = await apiDeleteDevice(deviceId)
|
|
87
|
+
uiLog.info('Delete response:', resp)
|
|
88
|
+
|
|
89
|
+
uiLog.info('Syncing parent config from disk...')
|
|
90
|
+
const synced = await syncParentPluginConfigFromDisk(true)
|
|
91
|
+
if (!synced) {
|
|
92
|
+
toastWarning('Device deleted, but configuration sync failed')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
uiLog.info('Refreshing device list...')
|
|
96
|
+
const list = await fetchDevices()
|
|
97
|
+
uiLog.info('Rendering devices:', list.length)
|
|
98
|
+
renderDeviceList(list)
|
|
99
|
+
|
|
100
|
+
uiLog.info('✓ Device deleted successfully')
|
|
101
|
+
toastSuccess(`Device "${deviceName || deviceId}" deleted successfully`)
|
|
102
|
+
} catch (e) {
|
|
103
|
+
uiLog.error('Delete error:', e)
|
|
104
|
+
toastError(e instanceof Error ? e.message : 'Failed to delete device')
|
|
105
|
+
} finally {
|
|
106
|
+
hideBusyUi()
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function confirmDeleteAllDialog(deviceCount: number): Promise<boolean> {
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
const overlay = document.createElement('div')
|
|
113
|
+
overlay.style.position = 'fixed'
|
|
114
|
+
overlay.style.top = '0'
|
|
115
|
+
overlay.style.left = '0'
|
|
116
|
+
overlay.style.width = '100%'
|
|
117
|
+
overlay.style.height = '100%'
|
|
118
|
+
overlay.style.background = 'rgba(0,0,0,0.7)'
|
|
119
|
+
overlay.style.display = 'flex'
|
|
120
|
+
overlay.style.alignItems = 'center'
|
|
121
|
+
overlay.style.justifyContent = 'center'
|
|
122
|
+
overlay.style.zIndex = '10000'
|
|
123
|
+
|
|
124
|
+
const modal = document.createElement('div')
|
|
125
|
+
modal.style.background = getComputedStyle(document.body).backgroundColor
|
|
126
|
+
modal.style.color = getComputedStyle(document.body).color
|
|
127
|
+
modal.style.padding = '20px'
|
|
128
|
+
modal.style.borderRadius = '10px'
|
|
129
|
+
modal.style.minWidth = '380px'
|
|
130
|
+
modal.style.maxWidth = '90vw'
|
|
131
|
+
modal.style.boxShadow = '0 12px 40px rgba(0,0,0,0.35)'
|
|
132
|
+
modal.style.borderTop = '3px solid #ef4444'
|
|
133
|
+
|
|
134
|
+
const title = document.createElement('h3')
|
|
135
|
+
title.textContent = '⚠️ Remove All Devices'
|
|
136
|
+
title.style.margin = '0 0 12px 0'
|
|
137
|
+
title.style.color = '#ef4444'
|
|
138
|
+
|
|
139
|
+
const message = document.createElement('p')
|
|
140
|
+
message.innerHTML = `Are you sure you want to remove <strong>all ${deviceCount} device(s)</strong> from your configuration?<br><br>This action cannot be undone.`
|
|
141
|
+
message.style.margin = '0 0 18px 0'
|
|
142
|
+
message.style.lineHeight = '1.5'
|
|
143
|
+
|
|
144
|
+
const actions = document.createElement('div')
|
|
145
|
+
actions.style.display = 'flex'
|
|
146
|
+
actions.style.justifyContent = 'flex-end'
|
|
147
|
+
actions.style.gap = '10px'
|
|
148
|
+
|
|
149
|
+
const cancelBtn = document.createElement('button')
|
|
150
|
+
cancelBtn.textContent = 'Cancel'
|
|
151
|
+
cancelBtn.style.background = '#6b7280'
|
|
152
|
+
cancelBtn.style.padding = '8px 16px'
|
|
153
|
+
cancelBtn.style.fontSize = '13px'
|
|
154
|
+
cancelBtn.className = 'secondary'
|
|
155
|
+
|
|
156
|
+
const deleteBtn = document.createElement('button')
|
|
157
|
+
deleteBtn.textContent = 'Remove All'
|
|
158
|
+
deleteBtn.style.background = '#ef4444'
|
|
159
|
+
deleteBtn.style.padding = '8px 20px'
|
|
160
|
+
deleteBtn.style.fontSize = '13px'
|
|
161
|
+
|
|
162
|
+
const cleanup = (result: boolean) => {
|
|
163
|
+
overlay.remove()
|
|
164
|
+
resolve(result)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
cancelBtn.onclick = () => cleanup(false)
|
|
168
|
+
deleteBtn.onclick = () => cleanup(true)
|
|
169
|
+
overlay.addEventListener('click', (event) => {
|
|
170
|
+
if (event.target === overlay) {
|
|
171
|
+
cleanup(false)
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
actions.appendChild(cancelBtn)
|
|
176
|
+
actions.appendChild(deleteBtn)
|
|
177
|
+
modal.appendChild(title)
|
|
178
|
+
modal.appendChild(message)
|
|
179
|
+
modal.appendChild(actions)
|
|
180
|
+
overlay.appendChild(modal)
|
|
181
|
+
document.body.appendChild(overlay)
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export async function deleteAllDevicesFromConfig(): Promise<void> {
|
|
186
|
+
uiLog.info('Remove All Devices button clicked')
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
// Get current device count
|
|
190
|
+
const list = await fetchDevices()
|
|
191
|
+
if (!list || list.length === 0) {
|
|
192
|
+
toastWarning('No devices to remove')
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const confirmed = await confirmDeleteAllDialog(list.length)
|
|
197
|
+
if (!confirmed) {
|
|
198
|
+
uiLog.info('Remove all cancelled by user')
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
showBusyUi()
|
|
203
|
+
uiLog.info('Deleting all devices from config')
|
|
204
|
+
const resp = await apiDeleteAllDevices()
|
|
205
|
+
uiLog.info('Delete all response:', resp)
|
|
206
|
+
|
|
207
|
+
uiLog.info('Syncing parent config from disk...')
|
|
208
|
+
const synced = await syncParentPluginConfigFromDisk(true)
|
|
209
|
+
if (!synced) {
|
|
210
|
+
toastWarning('Devices deleted, but configuration sync failed')
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
uiLog.info('Refreshing device list...')
|
|
214
|
+
const updatedList = await fetchDevices()
|
|
215
|
+
uiLog.info('Rendering devices:', updatedList.length)
|
|
216
|
+
renderDeviceList(updatedList)
|
|
217
|
+
|
|
218
|
+
const deletedCount = resp?.deletedCount || 0
|
|
219
|
+
uiLog.info(`✓ Removed ${deletedCount} device(s) successfully`)
|
|
220
|
+
toastSuccess(`Removed ${deletedCount} device(s) successfully`)
|
|
221
|
+
} catch (e) {
|
|
222
|
+
uiLog.error('Delete all error:', e)
|
|
223
|
+
toastError(e instanceof Error ? e.message : 'Failed to delete all devices')
|
|
224
|
+
} finally {
|
|
225
|
+
hideBusyUi()
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { addDevice, fetchDevices, syncParentPluginConfigFromDisk } from './api.js'
|
|
2
|
+
import { uiLog } from './logger.js'
|
|
3
|
+
import { hideBusyUi, showBusyUi } from './modal.js'
|
|
4
|
+
import { renderDeviceList } from './render.js'
|
|
5
|
+
import { toastError, toastInfo, toastSuccess, toastWarning } from './toast.js'
|
|
6
|
+
|
|
7
|
+
export async function addDeviceToConfig(device: any, options: { refresh?: boolean, showStatus?: boolean } = {}): Promise<{ added: boolean }> {
|
|
8
|
+
const { refresh = true, showStatus = true } = options
|
|
9
|
+
try {
|
|
10
|
+
const { importDiscoveredDevice } = await import('./modals.js')
|
|
11
|
+
|
|
12
|
+
const importValues: any = await importDiscoveredDevice(device)
|
|
13
|
+
if (!importValues || typeof importValues !== 'object' || !importValues.configDeviceName || !importValues.configDeviceType) {
|
|
14
|
+
return { added: false }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
showBusyUi()
|
|
18
|
+
uiLog.info('Adding device to config:', device)
|
|
19
|
+
// Never allow 'undefined' as a device name
|
|
20
|
+
let safeName = importValues.configDeviceName
|
|
21
|
+
if (!safeName || safeName === 'undefined') {
|
|
22
|
+
safeName = device.name || device.id
|
|
23
|
+
uiLog.warn(`Device name was invalid ("${importValues.configDeviceName}"), using fallback: "${safeName}"`)
|
|
24
|
+
}
|
|
25
|
+
const resp = await addDevice(device.id, safeName, importValues.configDeviceType, {
|
|
26
|
+
address: importValues.address,
|
|
27
|
+
model: device.model,
|
|
28
|
+
rssi: device.rssi,
|
|
29
|
+
encryptionKey: importValues.encryptionKey,
|
|
30
|
+
keyId: importValues.keyId,
|
|
31
|
+
})
|
|
32
|
+
uiLog.info('Add device response:', resp)
|
|
33
|
+
|
|
34
|
+
const alreadyExists = !!resp?.alreadyExists
|
|
35
|
+
const message = resp?.message
|
|
36
|
+
|| (alreadyExists
|
|
37
|
+
? `Device "${importValues.configDeviceName}" already in config`
|
|
38
|
+
: `Device "${importValues.configDeviceName}" added successfully!`)
|
|
39
|
+
|
|
40
|
+
if (alreadyExists) {
|
|
41
|
+
toastInfo(message)
|
|
42
|
+
} else {
|
|
43
|
+
toastSuccess(message)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (showStatus) {
|
|
47
|
+
const status = document.getElementById('discoverStatus')
|
|
48
|
+
if (status) {
|
|
49
|
+
status.textContent = (alreadyExists ? '• ' : '✓ ') + message
|
|
50
|
+
status.classList.remove('error')
|
|
51
|
+
status.classList.add('success-msg')
|
|
52
|
+
|
|
53
|
+
// Sync parent Homebridge form cache and auto-save to prevent cache overwrite
|
|
54
|
+
if (!alreadyExists) {
|
|
55
|
+
const synced = await syncParentPluginConfigFromDisk(true)
|
|
56
|
+
status.textContent += synced
|
|
57
|
+
? ' - Config saved automatically.'
|
|
58
|
+
: ' - Warning: config may not persist until you close/reopen settings.'
|
|
59
|
+
|
|
60
|
+
if (synced) {
|
|
61
|
+
toastSuccess('Configuration synced and saved automatically')
|
|
62
|
+
} else {
|
|
63
|
+
toastWarning('Configuration sync failed; close and reopen settings before Save')
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (refresh) {
|
|
70
|
+
// Force reload of config from disk after add
|
|
71
|
+
await syncParentPluginConfigFromDisk(true)
|
|
72
|
+
await loadConfiguredDevices()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { added: !alreadyExists }
|
|
76
|
+
} catch (e) {
|
|
77
|
+
const msg = e instanceof Error ? e.message : String(e)
|
|
78
|
+
uiLog.error('Add device error:', msg)
|
|
79
|
+
const status = document.getElementById('discoverStatus')
|
|
80
|
+
if (status) {
|
|
81
|
+
status.textContent = `✗ Error: ${msg}`
|
|
82
|
+
status.classList.add('error')
|
|
83
|
+
}
|
|
84
|
+
toastError(msg)
|
|
85
|
+
return { added: false }
|
|
86
|
+
} finally {
|
|
87
|
+
hideBusyUi()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function initRemoveAllButton(): Promise<void> {
|
|
92
|
+
const removeAllBtn = document.getElementById('removeAllBtn')
|
|
93
|
+
if (!removeAllBtn) {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
removeAllBtn.addEventListener('click', async () => {
|
|
98
|
+
const { deleteAllDevicesFromConfig } = await import('./devices-delete.js')
|
|
99
|
+
await deleteAllDevicesFromConfig()
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function loadConfiguredDevices(): Promise<void> {
|
|
104
|
+
const list = await fetchDevices()
|
|
105
|
+
renderDeviceList(list)
|
|
106
|
+
}
|