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 }),
|
||||
})
|
||||
|
||||
export const updateSensor = (id: number, name: string) =>
|
||||
export const updateSensor = ({ id, ...body }: { id: number; name: string }) =>
|
||||
request<SensorInfo>(`/api/sensors/${id}`, {
|
||||
method: 'PUT',
|
||||
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">
|
||||
{verticalMode && (
|
||||
<>
|
||||
<button onClick={onNewBox}>
|
||||
<button className="icon" onClick={onNewBox}>
|
||||
<PlusIcon />
|
||||
</button>
|
||||
<button onClick={onRefresh}>
|
||||
<button className="icon" onClick={onRefresh}>
|
||||
<RefreshIcon />
|
||||
</button>
|
||||
<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 { 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<SensorInfo>()
|
||||
|
||||
return (
|
||||
<UserLayout
|
||||
header={
|
||||
<div className="sensors-head">
|
||||
<div>Sensors</div>
|
||||
<button>
|
||||
<button onClick={() => setShowNew(true)}>
|
||||
<PlusIcon /> Add sensor
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -32,6 +37,16 @@ export const SensorsPage = () => {
|
|||
))}
|
||||
</div>
|
||||
</div>
|
||||
{(showNew || edited) && (
|
||||
<SensorFormModal
|
||||
open
|
||||
sensor={edited}
|
||||
onClose={() => {
|
||||
setShowNew(false)
|
||||
setEdited(undefined)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</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
|
||||
DATABASE_URL=./sensors.sqlite3
|
||||
DATABASE_URL=./sensors.sqlite3?_busy_timeout=500
|
||||
PORT=8083
|
||||
BIND_IP=localhost
|
||||
AUTH_USERNAME=admin
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue