Added support for dial box
This commit is contained in:
parent
39b7d5e73a
commit
009332dccf
|
|
@ -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
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue