diff --git a/client/src/api/sensorValues.ts b/client/src/api/sensorValues.ts index 16642b1..49bef1f 100644 --- a/client/src/api/sensorValues.ts +++ b/client/src/api/sensorValues.ts @@ -19,3 +19,16 @@ export const getSensorValues = ({ from.getTime() / 1000 )}&to=${Math.round(to.getTime() / 1000)}` ) + +export const getLatestSensorValue = ({ + to, + sensor, +}: { + to: Date + sensor: string +}) => + request( + `/api/sensors/${encodeURI(sensor)}/values/latest?to=${Math.round( + to.getTime() / 1000 + )}` + ) diff --git a/client/src/assets/style.css b/client/src/assets/style.css index 1b2ed9a..2a5fab2 100644 --- a/client/src/assets/style.css +++ b/client/src/assets/style.css @@ -214,6 +214,8 @@ form.horizontal .input label { .grid-sensors .grid-box .box { position: relative; + display: flex; + flex-direction: column; } .grid-sensors .grid-box .box .resize-h { @@ -245,6 +247,8 @@ form.horizontal .input label { .grid-sensors .grid-box .box .header { display: flex; + flex-grow: 0; + flex-shrink: 0; } .grid-sensors .grid-box .box .header .drag-handle { @@ -256,6 +260,19 @@ form.horizontal .input label { margin-left: auto; } +.grid-sensors .grid-box .box .body { + flex: 1; +} + +.grid-sensors .grid-box .box .dial { + text-align: center; + font-size: 150%; + display: flex; + align-items: center; + justify-content: center; + height: 100%; +} + .grid-sensors .box-preview { position: absolute; background-color: #3988FF; diff --git a/client/src/pages/dashboard/NewDashboardPage.tsx b/client/src/pages/dashboard/NewDashboardPage.tsx index fb207a9..2f7f707 100644 --- a/client/src/pages/dashboard/NewDashboardPage.tsx +++ b/client/src/pages/dashboard/NewDashboardPage.tsx @@ -59,8 +59,7 @@ export const NewDashboardPage = () => { return b }) - setBoxes([box, ...otherBoxes]) - // TODO: Save + handleChange(() => [box, ...otherBoxes]) } // Terrible code - ensure there's default dashboard diff --git a/client/src/pages/dashboard/components/BoxDialContent.tsx b/client/src/pages/dashboard/components/BoxDialContent.tsx new file mode 100644 index 0000000..5347dce --- /dev/null +++ b/client/src/pages/dashboard/components/BoxDialContent.tsx @@ -0,0 +1,34 @@ +import { getLatestSensorValue } from '@/api/sensorValues' +import { DashboardDialData } from '@/utils/parseDashboard' +import { useQuery } from 'react-query' +import { useDashboardContext } from '../contexts/DashboardContext' +import { BoxDefinition } from '../types' + +type Props = { + box: BoxDefinition + data: DashboardDialData +} + +export const BoxDialContent = ({ box, data }: Props) => { + const { filter } = useDashboardContext() + + const valuesQuery = { + sensor: box.sensor ?? '-1', + to: filter.customTo, + } + + const value = useQuery(['/sensor/values/latest', valuesQuery], () => + getLatestSensorValue(valuesQuery) + ) + + return ( +
+ {value.data && ( + <> + {value.data.value} + {data.unit} + + )} +
+ ) +} diff --git a/client/src/pages/dashboard/components/BoxSettings/BoxSettings.tsx b/client/src/pages/dashboard/components/BoxSettings/BoxSettings.tsx index c770c86..e3b2a4c 100644 --- a/client/src/pages/dashboard/components/BoxSettings/BoxSettings.tsx +++ b/client/src/pages/dashboard/components/BoxSettings/BoxSettings.tsx @@ -1,7 +1,9 @@ import { getSensors } from '@/api/sensors' +import { DashboardDialData, DashboardGraphData } from '@/utils/parseDashboard' import { useState } from 'preact/hooks' import { useQuery } from 'react-query' import { BoxDefinition } from '../../types' +import { DialSettings } from './components/DialSettings' import { GraphSettings } from './components/GraphSettings' type Props = { @@ -88,11 +90,22 @@ export const BoxSettings = ({ value, onSave, onClose }: Props) => { onChange={handleChange} > + {formState.type === 'graph' && ( - + + )} + + {formState.type === 'dial' && ( + )}
diff --git a/client/src/pages/dashboard/components/BoxSettings/components/DialSettings.tsx b/client/src/pages/dashboard/components/BoxSettings/components/DialSettings.tsx new file mode 100644 index 0000000..15e3541 --- /dev/null +++ b/client/src/pages/dashboard/components/BoxSettings/components/DialSettings.tsx @@ -0,0 +1,33 @@ +import { DashboardDialData } from '@/utils/parseDashboard' +import { useEffect, useState } from 'preact/hooks' + +type Props = { + value?: DashboardDialData + onChange: (data: DashboardDialData) => void +} + +export const DialSettings = ({ value, onChange }: Props) => { + const [formState, setFormState] = useState(() => ({ unit: value?.unit })) + + const handleChange = (e: Event) => { + const target = e.target as HTMLSelectElement | HTMLInputElement + + setFormState({ + ...formState, + [target.name]: target.value, + }) + } + + useEffect(() => { + onChange({ ...formState, type: 'dial' }) + }, [formState]) + + return ( + <> +
+ + +
+ + ) +} diff --git a/client/src/pages/dashboard/components/EditableBox.tsx b/client/src/pages/dashboard/components/EditableBox.tsx index 8e50109..783b2b3 100644 --- a/client/src/pages/dashboard/components/EditableBox.tsx +++ b/client/src/pages/dashboard/components/EditableBox.tsx @@ -5,6 +5,7 @@ import { useDashboardContext } from '../contexts/DashboardContext' import { useDragging } from '../hooks/useDragging' import { ResizingMode, useResize } from '../hooks/useResize' import { BoxDefinition } from '../types' +import { BoxDialContent } from './BoxDialContent' import { BoxGraphContent } from './BoxGraphContent' import { BoxSettings } from './BoxSettings/BoxSettings' @@ -129,6 +130,9 @@ export const EditableBox = ({ {box.sensor && box.data?.type === 'graph' && ( )} + {box.sensor && box.data?.type === 'dial' && ( + + )}
{ Math.min(GRID_WIDTH - box.w, Math.round(state.x / cellWidth)) ) - const dragY = Math.round(state.y / GRID_H_SNAP) * GRID_H_SNAP + const dragY = Math.max(0, Math.round(state.y / GRID_H_SNAP) * GRID_H_SNAP) const gridHeights = Array(GRID_WIDTH) .fill(null) diff --git a/client/src/utils/parseDashboard.ts b/client/src/utils/parseDashboard.ts index 5390f19..93b812b 100644 --- a/client/src/utils/parseDashboard.ts +++ b/client/src/utils/parseDashboard.ts @@ -11,7 +11,7 @@ export type DashboardContentBox = { h: number sensor?: string title?: string - data?: DashboardGraphData + data?: DashboardGraphData | DashboardDialData } export type DashboardGraphData = { @@ -22,6 +22,11 @@ export type DashboardGraphData = { graphType?: string } +export type DashboardDialData = { + type: 'dial' + unit?: string +} + export const parseDashboard = (input: string) => { return JSON.parse(input) as DashboardContent } diff --git a/server/routes/sensor_values.go b/server/routes/sensor_values.go index 59b9db0..82480d4 100644 --- a/server/routes/sensor_values.go +++ b/server/routes/sensor_values.go @@ -11,11 +11,15 @@ type postSensorValueBody struct { Value float64 `json:"value"` } -type getSensorQuery struct { +type getSensorValuesQuery struct { From int64 `form:"from"` To int64 `form:"to"` } +type getLatestSensorValueQuery struct { + To int64 `form:"to"` +} + func PostSensorValues(s *app.Server) gin.HandlerFunc { return func(c *gin.Context) { var newValue postSensorValueBody @@ -37,7 +41,7 @@ func PostSensorValues(s *app.Server) gin.HandlerFunc { func GetSensorValues(s *app.Server) gin.HandlerFunc { return func(c *gin.Context) { - var query getSensorQuery + var query getSensorValuesQuery sensor := c.Param("sensor") @@ -58,7 +62,7 @@ func GetSensorValues(s *app.Server) gin.HandlerFunc { func GetSensorLatestValue(s *app.Server) gin.HandlerFunc { return func(c *gin.Context) { - var query getSensorQuery + var query getLatestSensorValueQuery sensor := c.Param("sensor") @@ -67,7 +71,7 @@ func GetSensorLatestValue(s *app.Server) gin.HandlerFunc { return } - value, err := s.Services.SensorValues.GetLatest(sensor, query.From, query.To) + value, err := s.Services.SensorValues.GetLatest(sensor, query.To) if err != nil { c.AbortWithError(500, err) diff --git a/server/services/sensor_values_service.go b/server/services/sensor_values_service.go index dd1819d..9e6bd91 100644 --- a/server/services/sensor_values_service.go +++ b/server/services/sensor_values_service.go @@ -51,10 +51,10 @@ func (s *SensorValuesService) GetList(sensor string, from int64, to int64) ([]se return values, nil } -func (s *SensorValuesService) GetLatest(sensor string, from int64, to int64) (*sensorValue, error) { +func (s *SensorValuesService) GetLatest(sensor string, to int64) (*sensorValue, error) { var value = sensorValue{} - row := s.ctx.DB.QueryRow("SELECT timestamp, value FROM sensor_values WHERE sensor = ? AND timestamp > ? AND timestamp < ? ORDER BY timestamp DESC LIMIT 1", sensor, from, to) + row := s.ctx.DB.QueryRow("SELECT timestamp, value FROM sensor_values WHERE sensor = ? timestamp < ? ORDER BY timestamp DESC LIMIT 1", sensor, to) err := row.Scan(&value.Timestamp, &value.Value) if err != nil {