From 0e202c0850e2ea94e562a527256d0491c61e0f8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Z=C3=ADpek?= Date: Sun, 28 Aug 2022 12:30:37 +0200 Subject: [PATCH] Added sensor form --- client/src/api/sensors.ts | 4 +- client/src/components/Modal.tsx | 27 ++++++++ .../DashboardHeader/DashboardHeader.tsx | 4 +- client/src/pages/sensors/SensorsPage.tsx | 19 +++++- .../sensors/components/SensorFormModal.tsx | 65 +++++++++++++++++++ server/.env.example | 2 +- server/main.go | 1 + server/routes/sensors.go | 27 ++++++++ server/services/sensors_service.go | 37 +++++++++-- server/services/sessions_service.go | 21 +++--- 10 files changed, 182 insertions(+), 25 deletions(-) create mode 100644 client/src/components/Modal.tsx create mode 100644 client/src/pages/sensors/components/SensorFormModal.tsx diff --git a/client/src/api/sensors.ts b/client/src/api/sensors.ts index 873d335..ce478ba 100644 --- a/client/src/api/sensors.ts +++ b/client/src/api/sensors.ts @@ -15,9 +15,9 @@ export const createSensor = (name: string) => body: JSON.stringify({ name }), }) -export const updateSensor = (id: number, name: string) => +export const updateSensor = ({ id, ...body }: { id: number; name: string }) => request(`/api/sensors/${id}`, { method: 'PUT', headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ name }), + body: JSON.stringify(body), }) diff --git a/client/src/components/Modal.tsx b/client/src/components/Modal.tsx new file mode 100644 index 0000000..b093420 --- /dev/null +++ b/client/src/components/Modal.tsx @@ -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 ( +
+
+
{children}
+
+
+ ) +} diff --git a/client/src/pages/dashboard/components/DashboardHeader/DashboardHeader.tsx b/client/src/pages/dashboard/components/DashboardHeader/DashboardHeader.tsx index 2171c19..80f29fe 100644 --- a/client/src/pages/dashboard/components/DashboardHeader/DashboardHeader.tsx +++ b/client/src/pages/dashboard/components/DashboardHeader/DashboardHeader.tsx @@ -16,10 +16,10 @@ export const DashboardHeader = ({ onNewBox, onRefresh }: Props) => {
{verticalMode && ( <> - -
setFiltersShown(true)}> diff --git a/client/src/pages/sensors/SensorsPage.tsx b/client/src/pages/sensors/SensorsPage.tsx index a241cb4..1119dae 100644 --- a/client/src/pages/sensors/SensorsPage.tsx +++ b/client/src/pages/sensors/SensorsPage.tsx @@ -1,18 +1,23 @@ -import { getSensors } from '@/api/sensors' +import { getSensors, SensorInfo } from '@/api/sensors' import { PlusIcon } from '@/icons' import { UserLayout } from '@/layouts/UserLayout/UserLayout' +import { useState } from 'preact/hooks' import { useQuery } from 'react-query' +import { SensorFormModal } from './components/SensorFormModal' import { SensorItem } from './components/SensorItem' export const SensorsPage = () => { const sensors = useQuery(['/sensors'], getSensors) + const [showNew, setShowNew] = useState(false) + const [edited, setEdited] = useState() + return (
Sensors
-
@@ -32,6 +37,16 @@ export const SensorsPage = () => { ))}
+ {(showNew || edited) && ( + { + setShowNew(false) + setEdited(undefined) + }} + /> + )} ) } diff --git a/client/src/pages/sensors/components/SensorFormModal.tsx b/client/src/pages/sensors/components/SensorFormModal.tsx new file mode 100644 index 0000000..1cc9871 --- /dev/null +++ b/client/src/pages/sensors/components/SensorFormModal.tsx @@ -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 ( + +
+
+ + +
+ +
+ + +
+
+
+ ) +} diff --git a/server/.env.example b/server/.env.example index 3b5e2d1..085b5e8 100644 --- a/server/.env.example +++ b/server/.env.example @@ -1,5 +1,5 @@ GIN_MODE=debug -DATABASE_URL=./sensors.sqlite3 +DATABASE_URL=./sensors.sqlite3?_busy_timeout=500 PORT=8083 BIND_IP=localhost AUTH_USERNAME=admin diff --git a/server/main.go b/server/main.go index d08233a..c472bae 100644 --- a/server/main.go +++ b/server/main.go @@ -40,6 +40,7 @@ func main() { loginProtected := router.Group("/", middleware.LoginAuthMiddleware(server)) loginProtected.GET("/api/sensors", routes.GetSensors(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.GET("/api/sensors/:sensor/values/latest", routes.GetSensorLatestValue(server)) loginProtected.GET("/api/sensors/:sensor/values", routes.GetSensorValues(server)) diff --git a/server/routes/sensors.go b/server/routes/sensors.go index ae9fbd4..5cfb2a1 100644 --- a/server/routes/sensors.go +++ b/server/routes/sensors.go @@ -74,3 +74,30 @@ func PutSensors(s *app.Server) gin.HandlerFunc { 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) + } +} diff --git a/server/services/sensors_service.go b/server/services/sensors_service.go index e70af69..6373b38 100644 --- a/server/services/sensors_service.go +++ b/server/services/sensors_service.go @@ -1,6 +1,9 @@ package services -import "encoding/hex" +import ( + "crypto/rand" + "math/big" +) type SensorsService struct { ctx *Context @@ -43,12 +46,18 @@ func (s *SensorsService) GetList() ([]SensorItem, error) { } func (s *SensorsService) Create(name string) (*SensorItem, error) { - item := SensorItem{ - Name: name, - AuthKey: hex.EncodeToString(generateRandomKey(32)), + authKey, err := generateRandomString(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 { return nil, err @@ -93,3 +102,21 @@ func (s *SensorsService) Update(id int64, name string) (*SensorItem, error) { 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 +} diff --git a/server/services/sessions_service.go b/server/services/sessions_service.go index 7e73841..83f0a46 100644 --- a/server/services/sessions_service.go +++ b/server/services/sessions_service.go @@ -1,9 +1,6 @@ package services import ( - "crypto/rand" - "encoding/hex" - "io" "time" ) @@ -30,13 +27,19 @@ func (s *SessionsService) GetById(id string) (*SessionItem, error) { } func (s *SessionsService) Create() (*SessionItem, error) { + id, err := generateRandomString(128) + + if err != nil { + return nil, err + } + item := SessionItem{ // TODO: The key is not guaranteed to be unique, how do we guarantee that? - Id: hex.EncodeToString(generateRandomKey(128)), + Id: id, 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 { return nil, err @@ -68,11 +71,3 @@ func (s *SessionsService) Cleanup() error { func generateExpiryDate() time.Time { 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 -}