Added support for dial box

This commit is contained in:
Jan Zípek 2022-08-24 12:50:00 +02:00
parent 39b7d5e73a
commit 009332dccf
Signed by: kamen
GPG Key ID: A17882625B33AC31
11 changed files with 133 additions and 11 deletions

View File

@ -19,3 +19,16 @@ export const getSensorValues = ({
from.getTime() / 1000 from.getTime() / 1000
)}&to=${Math.round(to.getTime() / 1000)}` )}&to=${Math.round(to.getTime() / 1000)}`
) )
export const getLatestSensorValue = ({
to,
sensor,
}: {
to: Date
sensor: string
}) =>
request<SensorValue>(
`/api/sensors/${encodeURI(sensor)}/values/latest?to=${Math.round(
to.getTime() / 1000
)}`
)

View File

@ -214,6 +214,8 @@ form.horizontal .input label {
.grid-sensors .grid-box .box { .grid-sensors .grid-box .box {
position: relative; position: relative;
display: flex;
flex-direction: column;
} }
.grid-sensors .grid-box .box .resize-h { .grid-sensors .grid-box .box .resize-h {
@ -245,6 +247,8 @@ form.horizontal .input label {
.grid-sensors .grid-box .box .header { .grid-sensors .grid-box .box .header {
display: flex; display: flex;
flex-grow: 0;
flex-shrink: 0;
} }
.grid-sensors .grid-box .box .header .drag-handle { .grid-sensors .grid-box .box .header .drag-handle {
@ -256,6 +260,19 @@ form.horizontal .input label {
margin-left: auto; 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 { .grid-sensors .box-preview {
position: absolute; position: absolute;
background-color: #3988FF; background-color: #3988FF;

View File

@ -59,8 +59,7 @@ export const NewDashboardPage = () => {
return b return b
}) })
setBoxes([box, ...otherBoxes]) handleChange(() => [box, ...otherBoxes])
// TODO: Save
} }
// Terrible code - ensure there's default dashboard // Terrible code - ensure there's default dashboard

View File

@ -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 (
<div className="dial">
{value.data && (
<>
{value.data.value}
{data.unit}
</>
)}
</div>
)
}

View File

@ -1,7 +1,9 @@
import { getSensors } from '@/api/sensors' import { getSensors } from '@/api/sensors'
import { DashboardDialData, DashboardGraphData } from '@/utils/parseDashboard'
import { useState } from 'preact/hooks' import { useState } from 'preact/hooks'
import { useQuery } from 'react-query' import { useQuery } from 'react-query'
import { BoxDefinition } from '../../types' import { BoxDefinition } from '../../types'
import { DialSettings } from './components/DialSettings'
import { GraphSettings } from './components/GraphSettings' import { GraphSettings } from './components/GraphSettings'
type Props = { type Props = {
@ -88,11 +90,22 @@ export const BoxSettings = ({ value, onSave, onClose }: Props) => {
onChange={handleChange} onChange={handleChange}
> >
<option value="graph">Graph</option> <option value="graph">Graph</option>
<option value="dial">Dial</option>
</select> </select>
</div> </div>
{formState.type === 'graph' && ( {formState.type === 'graph' && (
<GraphSettings value={data} onChange={setData} /> <GraphSettings
value={data as DashboardGraphData}
onChange={setData}
/>
)}
{formState.type === 'dial' && (
<DialSettings
value={data as DashboardDialData}
onChange={setData}
/>
)} )}
<div className="actions"> <div className="actions">

View File

@ -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 (
<>
<div className="input">
<label>Unit</label>
<input name="unit" value={formState.unit} onChange={handleChange} />
</div>
</>
)
}

View File

@ -5,6 +5,7 @@ import { useDashboardContext } from '../contexts/DashboardContext'
import { useDragging } from '../hooks/useDragging' import { useDragging } from '../hooks/useDragging'
import { ResizingMode, useResize } from '../hooks/useResize' import { ResizingMode, useResize } from '../hooks/useResize'
import { BoxDefinition } from '../types' import { BoxDefinition } from '../types'
import { BoxDialContent } from './BoxDialContent'
import { BoxGraphContent } from './BoxGraphContent' import { BoxGraphContent } from './BoxGraphContent'
import { BoxSettings } from './BoxSettings/BoxSettings' import { BoxSettings } from './BoxSettings/BoxSettings'
@ -129,6 +130,9 @@ export const EditableBox = ({
{box.sensor && box.data?.type === 'graph' && ( {box.sensor && box.data?.type === 'graph' && (
<BoxGraphContent box={box} data={box.data} /> <BoxGraphContent box={box} data={box.data} />
)} )}
{box.sensor && box.data?.type === 'dial' && (
<BoxDialContent box={box} data={box.data} />
)}
</div> </div>
<div <div

View File

@ -24,7 +24,7 @@ export const useDragging = ({ cellWidth, boxes, box }: Props) => {
Math.min(GRID_WIDTH - box.w, Math.round(state.x / cellWidth)) 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) const gridHeights = Array(GRID_WIDTH)
.fill(null) .fill(null)

View File

@ -11,7 +11,7 @@ export type DashboardContentBox = {
h: number h: number
sensor?: string sensor?: string
title?: string title?: string
data?: DashboardGraphData data?: DashboardGraphData | DashboardDialData
} }
export type DashboardGraphData = { export type DashboardGraphData = {
@ -22,6 +22,11 @@ export type DashboardGraphData = {
graphType?: string graphType?: string
} }
export type DashboardDialData = {
type: 'dial'
unit?: string
}
export const parseDashboard = (input: string) => { export const parseDashboard = (input: string) => {
return JSON.parse(input) as DashboardContent return JSON.parse(input) as DashboardContent
} }

View File

@ -11,11 +11,15 @@ type postSensorValueBody struct {
Value float64 `json:"value"` Value float64 `json:"value"`
} }
type getSensorQuery struct { type getSensorValuesQuery struct {
From int64 `form:"from"` From int64 `form:"from"`
To int64 `form:"to"` To int64 `form:"to"`
} }
type getLatestSensorValueQuery struct {
To int64 `form:"to"`
}
func PostSensorValues(s *app.Server) gin.HandlerFunc { func PostSensorValues(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
var newValue postSensorValueBody var newValue postSensorValueBody
@ -37,7 +41,7 @@ func PostSensorValues(s *app.Server) gin.HandlerFunc {
func GetSensorValues(s *app.Server) gin.HandlerFunc { func GetSensorValues(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
var query getSensorQuery var query getSensorValuesQuery
sensor := c.Param("sensor") sensor := c.Param("sensor")
@ -58,7 +62,7 @@ func GetSensorValues(s *app.Server) gin.HandlerFunc {
func GetSensorLatestValue(s *app.Server) gin.HandlerFunc { func GetSensorLatestValue(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
var query getSensorQuery var query getLatestSensorValueQuery
sensor := c.Param("sensor") sensor := c.Param("sensor")
@ -67,7 +71,7 @@ func GetSensorLatestValue(s *app.Server) gin.HandlerFunc {
return return
} }
value, err := s.Services.SensorValues.GetLatest(sensor, query.From, query.To) value, err := s.Services.SensorValues.GetLatest(sensor, query.To)
if err != nil { if err != nil {
c.AbortWithError(500, err) c.AbortWithError(500, err)

View File

@ -51,10 +51,10 @@ func (s *SensorValuesService) GetList(sensor string, from int64, to int64) ([]se
return values, nil 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{} 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) err := row.Scan(&value.Timestamp, &value.Value)
if err != nil { if err != nil {