Proper validation, always show menu in management pages

This commit is contained in:
Jan Zípek 2024-04-01 12:41:04 +02:00
parent 74a4019dc5
commit 71b1b3ad0b
Signed by: kamen
GPG Key ID: A17882625B33AC31
16 changed files with 199 additions and 289 deletions

View File

@ -1,18 +1,27 @@
main.layout {
height: 100%;
overflow: auto;
display: flex;
flex-direction: column;
> .right-content {
overflow: auto;
display: flex;
flex-direction: column;
height: 100%;
flex: 1;
}
}
.menu {
position: fixed;
left: 0;
top: 0;
bottom: 0;
&.as-popup {
position: fixed;
left: 0;
top: 0;
bottom: 0;
}
color: var(--box-fg-color);
width: 15rem;
transition: left 0.1s;
transition: left 0.1s, margin-left 0.1s;
z-index: 2;
display: flex;
@ -62,6 +71,11 @@ main.layout {
text-decoration: none;
padding: 0.4rem 1rem;
display: flex;
color: var(--box-fg-color);
&.current {
background-color: var(--box-border-color);
}
&:hover {
background-color: var(--box-border-color);
@ -88,7 +102,9 @@ header.header {
> .inner {
display: flex;
padding: 0.5rem 0.5rem 0.5rem 0;
height: 2.2rem;
background-color: var(--header-bg-color);
align-items: center;
> .menu-button {
display: flex;

View File

@ -1,7 +1,6 @@
.alerts-page .content {
width: 50rem;
padding: 1rem;
margin: 0 auto;
padding: 1rem 2rem;
.contact-points {
margin-bottom: 2rem;

View File

@ -1,9 +1,11 @@
.dashboard {
/*
.dashboard-page {
height: 100%;
overflow: auto;
display: flex;
flex-direction: column;
}
*/
.dashboard-head {
display: flex;

View File

@ -11,8 +11,7 @@
section.content {
width: 50rem;
margin: 0 auto;
padding: 1rem;
padding: 1rem 2rem;
}
}

View File

@ -8,24 +8,39 @@ type Props = {
children: ComponentChild
header?: ComponentChild
className?: string
popupMenu?: boolean
}
export const UserLayout = ({ children, header, className }: Props) => {
const [menuShown, setMenuShown] = useState(false)
export const UserLayout = ({
children,
header,
className,
popupMenu = false,
}: Props) => {
const [menuShown, setMenuShown] = useState(popupMenu ? false : true)
return (
<main className={cn('layout', className)}>
<header className={'header'}>
<div className="inner">
<div className="menu-button" onClick={() => setMenuShown(true)}>
<MenuIcon />
<UserMenu
popup={popupMenu}
shown={menuShown}
onHide={() => setMenuShown(false)}
/>
<div className="right-content">
<header className="header">
<div className="inner">
<div
className="menu-button"
onClick={() => setMenuShown(!menuShown)}
>
<MenuIcon />
</div>
{header}
</div>
{header}
</div>
<div className="shadow"></div>
</header>
<section className="content">{children}</section>
<UserMenu shown={menuShown} onHide={() => setMenuShown(false)} />
{/*<div className="shadow"></div>*/}
</header>
<section className="content">{children}</section>
</div>
</main>
)
}

View File

@ -0,0 +1,19 @@
import { cn } from '@/utils/cn'
import { useHashRouterLocation } from '@/utils/hooks/useHashLocation'
import { ComponentChildren } from 'preact'
import { Link } from 'wouter'
type Props = {
href: string
children: ComponentChildren
}
export const NavLink = ({ href, children }: Props) => {
const [location] = useHashRouterLocation()
return (
<Link href={href} className={cn(location === href && 'current')}>
{children}
</Link>
)
}

View File

@ -1,33 +1,44 @@
import { CancelIcon } from '@/icons'
import { Link } from 'wouter'
import { cn } from '@/utils/cn'
import { NavLink } from './NavLink'
type Props = {
popup: boolean
shown: boolean
onHide: () => void
}
export const UserMenu = ({ shown, onHide }: Props) => {
export const UserMenu = ({ shown, popup, onHide }: Props) => {
return (
<>
<div className="menu" style={{ left: !shown ? '-20rem' : '0' }}>
<div
className={cn('menu', popup && 'as-popup')}
style={
popup
? { left: !shown ? '-20rem' : '0' }
: { marginLeft: !shown ? '-15rem' : '0' }
}
>
<div className="inner">
<div className="menu-header">
<div className="menu-close" onClick={onHide}>
<CancelIcon />
</div>
{popup && (
<div className="menu-close" onClick={onHide}>
<CancelIcon />
</div>
)}
<h2>Graphicek</h2>
</div>
<nav>
<a href="#">📈 Dashboards</a>
<Link href="/sensors"> Sensors</Link>
<Link href="/alerts">🚨 Alerts</Link>
<a href="#"> Settings</a>
<NavLink href="/">📈 Dashboards</NavLink>
<NavLink href="/sensors"> Sensors</NavLink>
<NavLink href="/alerts">🚨 Alerts</NavLink>
{/*<a href="#">⚙️ Settings</a>*/}
</nav>
</div>
<div className="shadow"></div>
{popup && <div className="shadow"></div>}
</div>
{shown && <div className="menu-overlay" onClick={onHide}></div>}
{popup && shown && <div className="menu-overlay" onClick={onHide}></div>}
</>
)
}

View File

@ -6,7 +6,11 @@ import { DashboardContextProvider } from './contexts/DashboardContext'
export const NewDashboardPage = () => {
return (
<DashboardContextProvider>
<UserLayout className="dashboard" header={<DashboardHeader />}>
<UserLayout
className="dashboard-page"
header={<DashboardHeader />}
popupMenu
>
<DashboardGrid />
</UserLayout>
</DashboardContextProvider>

View File

@ -3,27 +3,17 @@ package routes
import (
"basic-sensor-receiver/app"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
type postAlertsBody struct {
ContactPointId int64 `json:"contactPointId"`
Name string `json:"name"`
Condition string `json:"condition"`
TriggerInterval int64 `json:"triggerInterval"`
CustomMessage string `json:"customMessage"`
CustomResolvedMessage string `json:"customResolvedMessage"`
}
type putAlertsBody struct {
ContactPointId int64 `json:"contactPointId"`
Name string `json:"name"`
Condition string `json:"condition"`
TriggerInterval int64 `json:"triggerInterval"`
CustomMessage string `json:"customMessage"`
CustomResolvedMessage string `json:"customResolvedMessage"`
type postAndPutAlertsBody struct {
ContactPointId int64 `json:"contactPointId" binding:"required"`
Name string `json:"name" binding:"required"`
Condition string `json:"condition" binding:"required"`
TriggerInterval int64 `json:"triggerInterval" binding:"required"`
CustomMessage string `json:"customMessage" binding:"required"`
CustomResolvedMessage string `json:"customResolvedMessage" binding:"required"`
}
func GetAlerts(s *app.Server) gin.HandlerFunc {
@ -41,12 +31,8 @@ func GetAlerts(s *app.Server) gin.HandlerFunc {
func PostAlerts(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
body := postAlertsBody{}
if err := c.BindJSON(&body); err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
body := postAndPutAlertsBody{}
bindJSONBodyOrAbort(c, &body)
alert, err := s.Services.Alerts.Create(body.ContactPointId, body.Name, body.Condition, body.TriggerInterval, body.CustomMessage, body.CustomResolvedMessage)
@ -61,19 +47,10 @@ func PostAlerts(s *app.Server) gin.HandlerFunc {
func PutAlert(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
body := putAlertsBody{}
alertId := getIntParamOrAbort(c, "alertId")
alertId, err := getAlertId(c)
if err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
if err := c.BindJSON(&body); err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
body := postAndPutAlertsBody{}
bindJSONBodyOrAbort(c, &body)
alert, err := s.Services.Alerts.Update(alertId, body.ContactPointId, body.Name, body.Condition, body.TriggerInterval, body.CustomMessage, body.CustomResolvedMessage)
@ -88,12 +65,7 @@ func PutAlert(s *app.Server) gin.HandlerFunc {
func GetAlert(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
alertId, err := getAlertId(c)
if err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
alertId := getIntParamOrAbort(c, "alertId")
alert, err := s.Services.Alerts.GetById(alertId)
@ -108,12 +80,7 @@ func GetAlert(s *app.Server) gin.HandlerFunc {
func DeleteAlert(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
alertId, err := getAlertId(c)
if err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
alertId := getIntParamOrAbort(c, "alertId")
if err := s.Services.Alerts.DeleteById(alertId); err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
@ -123,9 +90,3 @@ func DeleteAlert(s *app.Server) gin.HandlerFunc {
c.Status(http.StatusOK)
}
}
func getAlertId(c *gin.Context) (int64, error) {
id := c.Param("alertId")
return strconv.ParseInt(id, 10, 64)
}

View File

@ -8,18 +8,14 @@ import (
)
type postLoginBody struct {
Username string `json:"username"`
Password string `json:"password"`
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
func Login(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
body := postLoginBody{}
if err := c.BindJSON(&body); err != nil {
c.AbortWithError(400, err)
return
}
bindJSONBodyOrAbort(c, &body)
if body.Password != s.Config.AuthPassword || body.Username != s.Config.AuthUsername {
c.AbortWithStatus(401)
@ -36,19 +32,7 @@ func Login(s *app.Server) gin.HandlerFunc {
func Logout(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
body := postLoginBody{}
if err := c.BindJSON(&body); err != nil {
c.AbortWithError(400, err)
return
}
if body.Password != s.Config.AuthPassword || body.Username != s.Config.AuthUsername {
c.AbortWithStatus(401)
return
}
if err := s.Services.Auth.Login(c); err != nil {
if err := s.Services.Auth.Logout(c); err != nil {
c.AbortWithError(500, err)
}

View File

@ -3,26 +3,19 @@ package routes
import (
"basic-sensor-receiver/app"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
type postContactPointsBody struct {
Name string `json:"name"`
Type string `json:"type"`
TypeConfig string `json:"typeConfig"`
}
type putContactPointsBody struct {
Name string `json:"name"`
Type string `json:"type"`
TypeConfig string `json:"typeConfig"`
type postOrPutContactPointsBody struct {
Name string `json:"name" binding:"required"`
Type string `json:"type" binding:"required,oneof=telegram"`
TypeConfig string `json:"typeConfig" binding:"required"`
}
type testContactPointBody struct {
Type string `json:"type"`
TypeConfig string `json:"typeConfig"`
Type string `json:"type" binding:"required,oneof=telegram"`
TypeConfig string `json:"typeConfig" binding:"required"`
}
func GetContactPoints(s *app.Server) gin.HandlerFunc {
@ -40,12 +33,8 @@ func GetContactPoints(s *app.Server) gin.HandlerFunc {
func PostContactPoints(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
body := postContactPointsBody{}
if err := c.BindJSON(&body); err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
body := postOrPutContactPointsBody{}
bindJSONBodyOrAbort(c, &body)
contactPoint, err := s.Services.ContactPoints.Create(body.Name, body.Type, body.TypeConfig)
@ -60,19 +49,10 @@ func PostContactPoints(s *app.Server) gin.HandlerFunc {
func PutContactPoint(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
body := putContactPointsBody{}
contactPointId := getIntParamOrAbort(c, "contactPointId")
contactPointId, err := getContactPointId(c)
if err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
if err := c.BindJSON(&body); err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
body := postOrPutContactPointsBody{}
bindJSONBodyOrAbort(c, &body)
contactPoint, err := s.Services.ContactPoints.Update(contactPointId, body.Name, body.Type, body.TypeConfig)
@ -87,12 +67,7 @@ func PutContactPoint(s *app.Server) gin.HandlerFunc {
func GetContactPoint(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
contactPointId, err := getContactPointId(c)
if err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
contactPointId := getIntParamOrAbort(c, "contactPointId")
contactPoint, err := s.Services.ContactPoints.GetById(contactPointId)
@ -107,14 +82,9 @@ func GetContactPoint(s *app.Server) gin.HandlerFunc {
func DeleteContactPoint(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
contactPointId, err := getContactPointId(c)
contactPointId := getIntParamOrAbort(c, "contactPointId")
if err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
err = s.Services.ContactPoints.Delete(contactPointId)
err := s.Services.ContactPoints.Delete(contactPointId)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
@ -128,11 +98,7 @@ func DeleteContactPoint(s *app.Server) gin.HandlerFunc {
func TestContactPoint(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
body := testContactPointBody{}
if err := c.BindJSON(&body); err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
bindJSONBodyOrAbort(c, &body)
err := s.Services.ContactPoints.Test(body.Type, body.TypeConfig)
@ -144,9 +110,3 @@ func TestContactPoint(s *app.Server) gin.HandlerFunc {
c.AbortWithStatus(http.StatusOK)
}
}
func getContactPointId(c *gin.Context) (int64, error) {
sensor := c.Param("contactPointId")
return strconv.ParseInt(sensor, 10, 64)
}

View File

@ -4,20 +4,19 @@ import (
"basic-sensor-receiver/app"
"database/sql"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
type postDashboardBody struct {
Id int64 `json:"id"`
Name string `json:"name"`
Contents string `json:"contents"`
Id int64 `json:"id" binding:"required"`
Name string `json:"name" binding:"required"`
Contents string `json:"contents" binding:"required"`
}
type putDashboardBody struct {
Name string `json:"name"`
Contents string `json:"contents"`
Name string `json:"name" binding:"required"`
Contents string `json:"contents" binding:"required"`
}
func GetDashboards(s *app.Server) gin.HandlerFunc {
@ -35,11 +34,7 @@ func GetDashboards(s *app.Server) gin.HandlerFunc {
func GetDashboardById(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
id, err := getIntParam(c, "id")
if err != nil {
c.AbortWithError(400, err)
return
}
id := getIntParamOrAbort(c, "id")
item, err := s.Services.Dashboards.GetById(id)
@ -60,11 +55,7 @@ func GetDashboardById(s *app.Server) gin.HandlerFunc {
func PostDashboard(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
body := postDashboardBody{}
if err := c.BindJSON(&body); err != nil {
c.AbortWithError(400, err)
return
}
bindJSONBodyOrAbort(c, &body)
item, err := s.Services.Dashboards.Create(body.Id, body.Name, body.Contents)
@ -79,18 +70,10 @@ func PostDashboard(s *app.Server) gin.HandlerFunc {
func PutDashboard(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
id, err := getIntParam(c, "id")
if err != nil {
c.AbortWithError(400, err)
return
}
id := getIntParamOrAbort(c, "id")
body := putDashboardBody{}
if err := c.BindJSON(&body); err != nil {
c.AbortWithError(400, err)
return
}
bindJSONBodyOrAbort(c, &body)
item, err := s.Services.Dashboards.Update(id, body.Name, body.Contents)
@ -105,13 +88,9 @@ func PutDashboard(s *app.Server) gin.HandlerFunc {
func DeleteDashboard(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
id, err := getIntParam(c, "id")
if err != nil {
c.AbortWithError(400, err)
return
}
id := getIntParamOrAbort(c, "id")
err = s.Services.Dashboards.Delete(id)
err := s.Services.Dashboards.Delete(id)
if err != nil {
c.AbortWithError(500, err)
@ -121,9 +100,3 @@ func DeleteDashboard(s *app.Server) gin.HandlerFunc {
c.Status(http.StatusOK)
}
}
func getIntParam(c *gin.Context, key string) (int64, error) {
value := c.Param(key)
return strconv.ParseInt(value, 10, 64)
}

View File

@ -8,7 +8,7 @@ import (
)
type sensorConfigValue struct {
Value string `json:"value"`
Value string `json:"value" binding:"required"`
}
func PutSensorConfig(s *app.Server) gin.HandlerFunc {
@ -17,10 +17,7 @@ func PutSensorConfig(s *app.Server) gin.HandlerFunc {
sensor := c.Param("sensor")
key := c.Param("key")
if err := c.BindJSON(&configValue); err != nil {
c.AbortWithError(400, err)
return
}
bindJSONBodyOrAbort(c, &configValue)
if err := s.Services.SensorConfig.SetValue(sensor, key, configValue.Value); err != nil {
c.AbortWithError(500, err)

View File

@ -4,39 +4,30 @@ import (
"basic-sensor-receiver/app"
"database/sql"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
type postSensorValueBody struct {
Value float64 `json:"value"`
Value float64 `json:"value" binding:"required"`
}
type getSensorValuesQuery struct {
From int64 `form:"from"`
To int64 `form:"to"`
From int64 `form:"from" binding:"required"`
To int64 `form:"to" binding:"required"`
}
type getLatestSensorValueQuery struct {
To int64 `form:"to"`
To int64 `form:"to" binding:"required"`
}
func PostSensorValues(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
var newValue postSensorValueBody
if err := c.BindJSON(&newValue); err != nil {
c.AbortWithError(400, err)
return
}
bindJSONBodyOrAbort(c, &newValue)
sensorId, err := getSensorId(c)
if err != nil {
c.AbortWithError(400, err)
return
}
sensorId := getIntParamOrAbort(c, "sensor")
if _, err := s.Services.SensorValues.Push(sensorId, newValue.Value); err != nil {
c.AbortWithError(400, err)
@ -51,15 +42,10 @@ func GetSensorValues(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
var query getSensorValuesQuery
sensorId, err := getSensorId(c)
sensorId := getIntParamOrAbort(c, "sensor")
if err != nil {
c.AbortWithError(400, err)
return
}
if err := c.BindQuery(&query); err != nil {
c.AbortWithError(500, err)
if err := c.ShouldBindQuery(&query); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
@ -77,15 +63,10 @@ func GetSensorLatestValue(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
var query getLatestSensorValueQuery
sensorId, err := getSensorId(c)
sensorId := getIntParamOrAbort(c, "sensor")
if err != nil {
c.AbortWithError(400, err)
return
}
if err := c.BindQuery(&query); err != nil {
c.AbortWithError(500, err)
if err := c.ShouldBindQuery(&query); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
@ -104,9 +85,3 @@ func GetSensorLatestValue(s *app.Server) gin.HandlerFunc {
c.JSON(http.StatusOK, value)
}
}
func getSensorId(c *gin.Context) (int64, error) {
sensor := c.Param("sensor")
return strconv.ParseInt(sensor, 10, 64)
}

View File

@ -2,17 +2,14 @@ package routes
import (
"basic-sensor-receiver/app"
"log"
"net/http"
"github.com/gin-gonic/gin"
)
type postSensorsBody struct {
Name string `json:"name"`
}
type putSensorsBody struct {
Name string `json:"name"`
type postOrPutSensorsBody struct {
Name string `json:"name" binding:"required"`
}
func GetSensors(s *app.Server) gin.HandlerFunc {
@ -20,7 +17,8 @@ func GetSensors(s *app.Server) gin.HandlerFunc {
sensors, err := s.Services.Sensors.GetList()
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
log.Println(err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
@ -30,12 +28,8 @@ func GetSensors(s *app.Server) gin.HandlerFunc {
func PostSensors(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
body := postSensorsBody{}
if err := c.BindJSON(&body); err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
body := postOrPutSensorsBody{}
bindJSONBodyOrAbort(c, &body)
sensor, err := s.Services.Sensors.Create(body.Name)
@ -50,19 +44,10 @@ func PostSensors(s *app.Server) gin.HandlerFunc {
func PutSensor(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
body := putSensorsBody{}
sensorId := getIntParamOrAbort(c, "sensor")
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
}
body := postOrPutSensorsBody{}
bindJSONBodyOrAbort(c, &body)
sensor, err := s.Services.Sensors.Update(sensorId, body.Name)
@ -77,19 +62,10 @@ func PutSensor(s *app.Server) gin.HandlerFunc {
func GetSensor(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
body := putSensorsBody{}
sensorId := getIntParamOrAbort(c, "sensor")
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
}
body := postOrPutSensorsBody{}
bindJSONBodyOrAbort(c, &body)
sensor, err := s.Services.Sensors.GetById(sensorId)
@ -104,14 +80,9 @@ func GetSensor(s *app.Server) gin.HandlerFunc {
func DeleteSensor(s *app.Server) gin.HandlerFunc {
return func(c *gin.Context) {
sensorId, err := getSensorId(c)
sensorId := getIntParamOrAbort(c, "sensor")
if err != nil {
c.AbortWithError(http.StatusBadRequest, err)
return
}
err = s.Services.Sensors.DeleteById(sensorId)
err := s.Services.Sensors.DeleteById(sensorId)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)

24
server/routes/utils.go Normal file
View File

@ -0,0 +1,24 @@
package routes
import (
"strconv"
"github.com/gin-gonic/gin"
)
func getIntParamOrAbort(c *gin.Context, key string) int64 {
value := c.Param(key)
val, err := strconv.ParseInt(value, 10, 64)
if err != nil {
c.AbortWithStatusJSON(400, gin.H{"error": "Invalid " + key})
}
return val
}
func bindJSONBodyOrAbort(c *gin.Context, body interface{}) {
if err := c.ShouldBindJSON(body); err != nil {
c.AbortWithStatusJSON(400, gin.H{"error": err.Error()})
}
}