hvac/backend/src/routes/settings.js
jtricerolph 2db7d5f4d0 Add kiosk wall-panel control view
Full-screen /kiosk route, separate from the staff SPA: dedicated long-lived
kiosk login (own password, hashed, mints its own hnf_session cookie locally
via CENTRAL_AUTH_SECRET -- no central auth changes needed), scrollable grid
of small mode/setpoint blocks per live-controllable device (shelly_trv,
mhi_mqtt, daikin), tap-to-edit modal reusing the existing per-type control
panels, and a type-aware bulk multiselect (separate TRV/AC sections, only
applied to matching selected devices, TRV commands staggered).

- backend/src/routes/kiosk.js: public POST /api/kiosk/login
- backend/src/routes/settings.js: admin set/check kiosk password (bcrypt)
- backend/src/routes/config.js: exclude/protect kiosk_password_hash
- frontend/src/pages/Kiosk.tsx + components/Kiosk*: the kiosk UI
- reuses GET /api/status for the combined device list -- no new status
  endpoint needed since the kiosk token now also carries the 'view' cap

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 20:44:50 +00:00

45 lines
2 KiB
JavaScript

import bcrypt from 'bcryptjs'
import { requireAuth, requireCap } from '../auth.js'
import { testConnection } from '../lib/newbook.js'
import { isConnected as mqttConnected } from '../lib/mqtt.js'
import { getConfig, setConfig } from '../db.js'
// Settings-page-backing endpoints that don't fit the generic config.js key/value
// store: connection tests and the MQTT broker's live status. The Home Assistant
// integration itself is configured centrally in the shared `settings` app, not
// here — this just reports whether it's reachable (via the driver's own no-op
// behaviour when unconfigured).
export async function settingsRoutes(app) {
app.addHook('preHandler', requireAuth)
app.post('/api/settings/newbook-test', { preHandler: requireCap('settings') }, async () => {
return testConnection()
})
app.get('/api/settings/mqtt-status', { preHandler: requireCap('settings') }, async () => {
return {
connected: mqttConnected(),
note: mqttConnected()
? 'Connected to the shared MQTT broker.'
: 'Not connected — the shared MQTT broker (LXC 104) may not be provisioned yet, or hvac-backend has no credentials in settings.',
}
})
// Kiosk wall-panel login (see routes/kiosk.js) — the password itself is never
// readable back, only whether one has been set, so the Settings page can show
// a "configured" state without exposing the hash to the browser.
app.get('/api/settings/kiosk-password-status', { preHandler: requireCap('settings') }, async () => {
const hash = await getConfig('kiosk_password_hash', null)
return { configured: !!hash }
})
app.post('/api/settings/kiosk-password', { preHandler: requireCap('settings') }, async (req, reply) => {
const { password } = req.body || {}
if (!password || typeof password !== 'string' || password.length < 6) {
return reply.status(400).send({ error: 'Password must be at least 6 characters' })
}
const hash = await bcrypt.hash(password, 12)
await setConfig('kiosk_password_hash', hash)
return { ok: true }
})
}