Add device telemetry history + per-device graph view
activity_log only ever recorded state transitions/overrides/sync/errors — useful for "what did the app decide" but not "what was the room actually doing" when a guest/staff complaint comes in after the fact. Backend: - New device_readings time-series table (room_temp, target_temp, mode, power_state, fan_speed, valve_position, heating_active), throttled to ~1 row/device/minute via recordReading() in db.js regardless of how often a caller fires — MQTT pushes can be far chattier than that. - Wired into every place zone_devices' live-status cache already gets updated: ac-poller.js, daikin-poller.js, and mqtt.js's TRV (handleStatusBlob/handleInfo) and native-MQTT MHI (writeMhiState) handlers. - 4-day retention by default (config key history_retention_days), pruned every 6h by the new history-pruner.js. - GET /api/devices/:id/history?hours=96 to read it back. Frontend: - DeviceHistoryModal: room temp vs setpoint line chart, plus a valve-position chart (TRVs) or fan-speed step chart (AC), plus a diffed mode/power/fan change log — 24h/4d/7d range picker. Uses recharts, matching the convention already established in utilities/wages/reports. - Graph icon on each device in both ZoneDetailModal and the Devices page opens it. Side effect: frontend/package-lock.json was missing vite-plugin-pwa's whole dependency tree even though package.json already declared it — pre-existing drift, reconciled by this npm install (unrelated to the recharts addition, just came along with it).
This commit is contained in:
parent
59a3e091d8
commit
f0b4ac3c41
15 changed files with 5172 additions and 38 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { Fragment, useEffect, useState } from 'react'
|
||||
import { RadioTower, ChevronDown, ChevronRight, Wrench, Trash2 } from 'lucide-react'
|
||||
import { RadioTower, ChevronDown, ChevronRight, Wrench, Trash2, LineChart } from 'lucide-react'
|
||||
import {
|
||||
fetchDevices, discoverDevices, updateDevice, deleteDevice, fetchZones, fetchDevicePhotos, createMaintenanceAsset,
|
||||
} from '../api'
|
||||
|
|
@ -7,6 +7,7 @@ import type { Device, Zone, DevicePhoto, DeviceType } from '../types'
|
|||
import { DEVICE_TYPE_LABELS, IMPLEMENTED_DEVICE_TYPES } from '../types'
|
||||
import DevicePhotoUpload from '../components/DevicePhotoUpload'
|
||||
import GatewaysPanel from '../components/GatewaysPanel'
|
||||
import DeviceHistoryModal from '../components/DeviceHistoryModal'
|
||||
import { useAuth } from '../components/AuthGate'
|
||||
import { can } from '../types'
|
||||
|
||||
|
|
@ -24,6 +25,7 @@ export default function Devices() {
|
|||
const [discovering, setDiscovering] = useState<DeviceType | null>(null)
|
||||
const [expanded, setExpanded] = useState<number | null>(null)
|
||||
const [photosByDevice, setPhotosByDevice] = useState<Record<number, DevicePhoto[]>>({})
|
||||
const [historyDevice, setHistoryDevice] = useState<Device | null>(null)
|
||||
|
||||
function load() {
|
||||
Promise.all([fetchDevices(), fetchZones()])
|
||||
|
|
@ -194,6 +196,9 @@ export default function Devices() {
|
|||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
<button className="btn btn-sm" onClick={() => setHistoryDevice(d)}>
|
||||
<LineChart size={13} strokeWidth={1.75} /> View history
|
||||
</button>
|
||||
{canManage && d.zone_id && (
|
||||
<button className="btn btn-sm" onClick={() => createAsset(d.id)}>
|
||||
<Wrench size={13} strokeWidth={1.75} /> Create asset in Maintenance
|
||||
|
|
@ -214,6 +219,8 @@ export default function Devices() {
|
|||
</table>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{historyDevice && <DeviceHistoryModal device={historyDevice} onClose={() => setHistoryDevice(null)} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue