Added sensor form
This commit is contained in:
parent
c56a678925
commit
0e202c0850
|
|
@ -15,9 +15,9 @@ export const createSensor = (name: string) =>
|
||||||
body: JSON.stringify({ name }),
|
body: JSON.stringify({ name }),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const updateSensor = (id: number, name: string) =>
|
export const updateSensor = ({ id, ...body }: { id: number; name: string }) =>
|
||||||
request<SensorInfo>(`/api/sensors/${id}`, {
|
request<SensorInfo>(`/api/sensors/${id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'content-type': 'application/json' },
|
headers: { 'content-type': 'application/json' },
|
||||||
body: JSON.stringify({ name }),
|
body: JSON.stringify(body),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { cn } from '@/utils/cn'
|
||||||
|
import { ComponentChild } from 'preact'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
open: boolean
|
||||||
|
onClose: () => void
|
||||||
|
children: ComponentChild
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Modal = ({ open, onClose, children }: Props) => {
|
||||||
|
const preventPropagation = (e: Event) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn('settings-modal', open && 'show')} onMouseDown={onClose}>
|
||||||
|
<div
|
||||||
|
className="inner"
|
||||||
|
onMouseDown={preventPropagation}
|
||||||
|
onMouseUp={preventPropagation}
|
||||||
|
onClick={preventPropagation}
|
||||||
|
>
|
||||||
|
<div className="body">{children}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -16,10 +16,10 @@ export const DashboardHeader = ({ onNewBox, onRefresh }: Props) => {
|
||||||
<div className="dashboard-head">
|
<div className="dashboard-head">
|
||||||
{verticalMode && (
|
{verticalMode && (
|
||||||
<>
|
<>
|
||||||
<button onClick={onNewBox}>
|
<button className="icon" onClick={onNewBox}>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
</button>
|
</button>
|
||||||
<button onClick={onRefresh}>
|
<button className="icon" onClick={onRefresh}>
|
||||||
<RefreshIcon />
|
<RefreshIcon />
|
||||||
</button>
|
</button>
|
||||||
<div className="filter-button" onClick={() => setFiltersShown(true)}>
|
<div className="filter-button" onClick={() => setFiltersShown(true)}>
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,23 @@
|
||||||
import { getSensors } from '@/api/sensors'
|
import { getSensors, SensorInfo } from '@/api/sensors'
|
||||||
import { PlusIcon } from '@/icons'
|
import { PlusIcon } from '@/icons'
|
||||||
import { UserLayout } from '@/layouts/UserLayout/UserLayout'
|
import { UserLayout } from '@/layouts/UserLayout/UserLayout'
|
||||||
|
import { useState } from 'preact/hooks'
|
||||||
import { useQuery } from 'react-query'
|
import { useQuery } from 'react-query'
|
||||||
|
import { SensorFormModal } from './components/SensorFormModal'
|
||||||
import { SensorItem } from './components/SensorItem'
|
import { SensorItem } from './components/SensorItem'
|
||||||
|
|
||||||
export const SensorsPage = () => {
|
export const SensorsPage = () => {
|
||||||
const sensors = useQuery(['/sensors'], getSensors)
|
const sensors = useQuery(['/sensors'], getSensors)
|
||||||
|
|
||||||
|
const [showNew, setShowNew] = useState(false)
|
||||||
|
const [edited, setEdited] = useState<SensorInfo>()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserLayout
|
<UserLayout
|
||||||
header={
|
header={
|
||||||
<div className="sensors-head">
|
<div className="sensors-head">
|
||||||
<div>Sensors</div>
|
<div>Sensors</div>
|
||||||
<button>
|
<button onClick={() => setShowNew(true)}>
|
||||||
<PlusIcon /> Add sensor
|
<PlusIcon /> Add sensor
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -32,6 +37,16 @@ export const SensorsPage = () => {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{(showNew || edited) && (
|
||||||
|
<SensorFormModal
|
||||||
|
open
|
||||||
|
sensor={edited}
|
||||||
|
onClose={() => {
|
||||||
|
setShowNew(false)
|
||||||
|
setEdited(undefined)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</UserLayout>
|
</UserLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { createSensor, SensorInfo, updateSensor } from '@/api/sensors'
|
||||||
|
import { Modal } from '@/components/Modal'
|
||||||
|
import { useState } from 'preact/hooks'
|
||||||
|
import { useMutation } from 'react-query'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
open: boolean
|
||||||
|
onClose: () => void
|
||||||
|
sensor?: SensorInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SensorFormModal = ({ open, onClose, sensor }: Props) => {
|
||||||
|
const [formState, setFormState] = useState(() => ({
|
||||||
|
name: sensor?.name ?? '',
|
||||||
|
}))
|
||||||
|
|
||||||
|
const createMutation = useMutation(createSensor)
|
||||||
|
const updateMutation = useMutation(updateSensor)
|
||||||
|
|
||||||
|
const handleSave = async (e: Event) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
onClose()
|
||||||
|
|
||||||
|
if (sensor) {
|
||||||
|
updateMutation.mutate({ id: sensor.id, name: formState.name })
|
||||||
|
} else {
|
||||||
|
createMutation.mutate(formState.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleChange = (e: Event) => {
|
||||||
|
const target = e.target as HTMLSelectElement | HTMLInputElement
|
||||||
|
|
||||||
|
setFormState({
|
||||||
|
...formState,
|
||||||
|
[target.name]: target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal onClose={onClose} open={open}>
|
||||||
|
<form onSubmit={handleSave}>
|
||||||
|
<div className="input">
|
||||||
|
<label>Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="name"
|
||||||
|
minLength={1}
|
||||||
|
required
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="actions">
|
||||||
|
<button className="cancel" type="button">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button>Save</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
GIN_MODE=debug
|
GIN_MODE=debug
|
||||||
DATABASE_URL=./sensors.sqlite3
|
DATABASE_URL=./sensors.sqlite3?_busy_timeout=500
|
||||||
PORT=8083
|
PORT=8083
|
||||||
BIND_IP=localhost
|
BIND_IP=localhost
|
||||||
AUTH_USERNAME=admin
|
AUTH_USERNAME=admin
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ func main() {
|
||||||
loginProtected := router.Group("/", middleware.LoginAuthMiddleware(server))
|
loginProtected := router.Group("/", middleware.LoginAuthMiddleware(server))
|
||||||
loginProtected.GET("/api/sensors", routes.GetSensors(server))
|
loginProtected.GET("/api/sensors", routes.GetSensors(server))
|
||||||
loginProtected.POST("/api/sensors", routes.PostSensors(server))
|
loginProtected.POST("/api/sensors", routes.PostSensors(server))
|
||||||
|
loginProtected.GET("/api/sensors/:sensor", routes.GetSensor(server))
|
||||||
loginProtected.PUT("/api/sensors/:sensor", routes.PutSensors(server))
|
loginProtected.PUT("/api/sensors/:sensor", routes.PutSensors(server))
|
||||||
loginProtected.GET("/api/sensors/:sensor/values/latest", routes.GetSensorLatestValue(server))
|
loginProtected.GET("/api/sensors/:sensor/values/latest", routes.GetSensorLatestValue(server))
|
||||||
loginProtected.GET("/api/sensors/:sensor/values", routes.GetSensorValues(server))
|
loginProtected.GET("/api/sensors/:sensor/values", routes.GetSensorValues(server))
|
||||||
|
|
|
||||||
|
|
@ -74,3 +74,30 @@ func PutSensors(s *app.Server) gin.HandlerFunc {
|
||||||
c.JSON(http.StatusOK, sensor)
|
c.JSON(http.StatusOK, sensor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetSensor(s *app.Server) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
body := putSensorsBody{}
|
||||||
|
|
||||||
|
sensorId, err := getSensorId(c)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.BindJSON(&body); err != nil {
|
||||||
|
c.AbortWithError(http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sensor, err := s.Services.Sensors.GetById(sensorId)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, sensor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package services
|
package services
|
||||||
|
|
||||||
import "encoding/hex"
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
type SensorsService struct {
|
type SensorsService struct {
|
||||||
ctx *Context
|
ctx *Context
|
||||||
|
|
@ -43,12 +46,18 @@ func (s *SensorsService) GetList() ([]SensorItem, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SensorsService) Create(name string) (*SensorItem, error) {
|
func (s *SensorsService) Create(name string) (*SensorItem, error) {
|
||||||
item := SensorItem{
|
authKey, err := generateRandomString(32)
|
||||||
Name: name,
|
|
||||||
AuthKey: hex.EncodeToString(generateRandomKey(32)),
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := s.ctx.DB.Exec("INSERT INTO sensors (id, name, auth_key) VALUES (?, ?)", item.Name, item.AuthKey)
|
item := SensorItem{
|
||||||
|
Name: name,
|
||||||
|
AuthKey: authKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := s.ctx.DB.Exec("INSERT INTO sensors (name, auth_key) VALUES (?, ?)", item.Name, item.AuthKey)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -93,3 +102,21 @@ func (s *SensorsService) Update(id int64, name string) (*SensorItem, error) {
|
||||||
|
|
||||||
return item, nil
|
return item, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var randomKeySource = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||||
|
|
||||||
|
func generateRandomString(length int) (string, error) {
|
||||||
|
key := make([]byte, length)
|
||||||
|
|
||||||
|
for i := range key {
|
||||||
|
value, err := rand.Int(rand.Reader, big.NewInt(int64(len(randomKeySource))))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
key[i] = randomKeySource[value.Int64()]
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(key), nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"encoding/hex"
|
|
||||||
"io"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -30,13 +27,19 @@ func (s *SessionsService) GetById(id string) (*SessionItem, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SessionsService) Create() (*SessionItem, error) {
|
func (s *SessionsService) Create() (*SessionItem, error) {
|
||||||
|
id, err := generateRandomString(128)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
item := SessionItem{
|
item := SessionItem{
|
||||||
// TODO: The key is not guaranteed to be unique, how do we guarantee that?
|
// TODO: The key is not guaranteed to be unique, how do we guarantee that?
|
||||||
Id: hex.EncodeToString(generateRandomKey(128)),
|
Id: id,
|
||||||
ExpiresAt: generateExpiryDate().Unix(),
|
ExpiresAt: generateExpiryDate().Unix(),
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.ctx.DB.Exec("INSERT INTO sessions (id, expires_at) VALUES (?, ?)", item.Id, item.ExpiresAt)
|
_, err = s.ctx.DB.Exec("INSERT INTO sessions (id, expires_at) VALUES (?, ?)", item.Id, item.ExpiresAt)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -68,11 +71,3 @@ func (s *SessionsService) Cleanup() error {
|
||||||
func generateExpiryDate() time.Time {
|
func generateExpiryDate() time.Time {
|
||||||
return time.Now().Add(time.Hour * 24)
|
return time.Now().Add(time.Hour * 24)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateRandomKey(length int) []byte {
|
|
||||||
k := make([]byte, length)
|
|
||||||
if _, err := io.ReadFull(rand.Reader, k); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return k
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue