WebUI/src/server/server.go
Theta-Dev 4e53dd9412
Some checks failed
continuous-integration/drone/push Build is failing
add settings page
2022-02-07 00:05:21 +01:00

709 lines
17 KiB
Go

// TSGRain WebUI
//
// REST API for the TSGRain WebUI
//
// ---
// Schemes: http, https
// Version: 0.1.0
// License: MIT
//
// swagger:meta
package server
import (
"errors"
"fmt"
"net/http"
"strconv"
"time"
"code.thetadev.de/TSGRain/WebUI/src/config"
"code.thetadev.de/TSGRain/WebUI/src/model"
"code.thetadev.de/TSGRain/WebUI/src/server/middleware"
"code.thetadev.de/TSGRain/WebUI/src/server/stream"
"code.thetadev.de/TSGRain/WebUI/src/server/swagger"
"code.thetadev.de/TSGRain/WebUI/src/tsgrain_rpc"
"code.thetadev.de/TSGRain/WebUI/src/util"
"code.thetadev.de/TSGRain/WebUI/src/util/mode"
"code.thetadev.de/TSGRain/WebUI/ui"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
)
type WebUIServer struct {
config *config.Config
streamer *stream.API
rpc *tsgrain_rpc.RPCClient
nZones int32
}
func NewServer(config *config.Config) *WebUIServer {
streamer := stream.New(
time.Duration(config.Server.Websocket.Ping)*time.Second,
time.Duration(config.Server.Websocket.Timeout)*time.Second,
[]string{},
)
rpc := tsgrain_rpc.NewClient(config.GRPC.Address)
return &WebUIServer{
config: config,
streamer: streamer,
rpc: rpc,
}
}
func (srv *WebUIServer) getRouter() *gin.Engine {
router := gin.New()
router.Use(gin.Logger())
_ = router.SetTrustedProxies(nil)
if mode.IsDev() {
router.Use(cors.Default())
}
router.Use(middleware.ErrorHandler(false), middleware.PanicHandler(false))
router.NoRoute(func(c *gin.Context) { c.Error(util.ErrPageNotFound) })
if srv.config.Authentication.Enable {
router.Use(middleware.Authentication(srv.config.Authentication.PasswdFile))
}
api := router.Group("/api",
middleware.ErrorHandler(true), middleware.PanicHandler(true))
// API ROUTES
api.GET("/ws", srv.streamer.Handle)
// Error routes for testing
if mode.IsDev() {
router.GET("/error", srv.controllerError)
router.GET("/panic", srv.controllerPanic)
api.GET("/error", srv.controllerError)
api.GET("/panic", srv.controllerPanic)
}
api.POST("/task/start", srv.controllerStartTask)
api.POST("/task/stop", srv.controllerStopTask)
api.GET("/tasks", srv.controllerGetTasks)
api.GET("/jobs", srv.controllerGetJobs)
api.GET("/job", srv.controllerGetJob)
api.POST("/job", srv.controllerCreateJob)
api.PUT("/job", srv.controllerUpdateJob)
api.DELETE("/job", srv.controllerDeleteJob)
api.GET("config/auto", srv.controllerGetAutoMode)
api.POST("config/auto", srv.controllerSetAutoMode)
api.GET("config/defaultIrrigationTime", srv.controllerGetDefaultIrrigationTime)
api.POST("config/defaultIrrigationTime", srv.controllerSetDefaultIrrigationTime)
api.GET("config/time", srv.controllerGetSystemTime)
api.POST("config/time", srv.controllerSetSystemTime)
api.POST("config/timezone", srv.controllerSetSystemTimezone)
// UI
uiGroup := router.Group("/", middleware.Compression(
srv.config.Server.Compression.Gzip,
srv.config.Server.Compression.Brotli),
)
ui.Register(uiGroup, srv.nZones)
swagger.Register(uiGroup)
return router
}
func (srv *WebUIServer) Run() error {
err := srv.rpc.Start()
if err != nil {
return err
}
// Get TSGRain info
nZones, err := srv.rpc.GetNZones()
if err != nil {
return err
}
srv.nZones = nZones
go srv.rpc.StreamTasks(srv.streamer)
router := srv.getRouter()
err = router.Run(fmt.Sprintf("%s:%d",
srv.config.Server.Address, srv.config.Server.Port))
_ = srv.rpc.Stop()
return err
}
// swagger:operation POST /task/start startTask
//
// Starte eine neue manuelle Bewässerungsaufgabe
// mit der eingestellten Standardzeit.
//
// ---
// consumes: [application/json]
// produces: [application/json]
// parameters:
// - name: zoneId
// in: body
// description: ZoneID
// schema:
// $ref: '#/definitions/ZoneID'
// responses:
// 200:
// description: OK
// schema:
// $ref: "#/definitions/StatusMessage"
// 500:
// description: Serverfehler
// schema:
// $ref: "#/definitions/Error"
func (srv *WebUIServer) controllerStartTask(c *gin.Context) {
var zoneId model.ZoneID
if err := c.ShouldBindJSON(&zoneId); err != nil {
log.Error().Err(err).Msg("StartTask input error")
writeStatus(c, http.StatusBadRequest, "invalid input data")
return
}
ok, err := srv.rpc.StartManualTask(zoneId.Id, 0)
if err != nil {
log.Error().Err(err).Msg("StartTask")
writeStatus(c, http.StatusInternalServerError, "error starting task")
return
}
if !ok {
log.Error().Msg("StartTask: alrady running")
writeStatus(c, http.StatusBadRequest, "task already running")
return
}
writeStatus(c, http.StatusOK, "task started")
}
// swagger:operation POST /task/stop stopTask
//
// Stoppe eine manuelle Bewässerungsaufgabe.
//
// ---
// consumes: [application/json]
// produces: [application/json]
// parameters:
// - name: zoneId
// in: body
// description: ZoneID
// schema:
// $ref: '#/definitions/ZoneID'
// responses:
// 200:
// description: OK
// schema:
// $ref: "#/definitions/StatusMessage"
// 500:
// description: Serverfehler
// schema:
// $ref: "#/definitions/Error"
func (srv *WebUIServer) controllerStopTask(c *gin.Context) {
var zoneId model.ZoneID
if err := c.ShouldBindJSON(&zoneId); err != nil {
log.Error().Err(err).Msg("StopTask input error")
writeStatus(c, http.StatusBadRequest, "invalid input data")
return
}
ok, err := srv.rpc.StopManualTask(zoneId.Id)
if err != nil {
log.Error().Err(err).Msg("StopTask")
writeStatus(c, http.StatusInternalServerError, "error stopping task")
return
}
if !ok {
log.Error().Msg("StopTask: no task to stop")
writeStatus(c, http.StatusBadRequest, "no task to stop")
return
}
writeStatus(c, http.StatusOK, "task stopped")
}
// swagger:operation GET /tasks getTasks
//
// Rufe alle momentan laufenden Bewässerungsaufgaben ab.
//
// ---
// produces: [application/json]
// responses:
// 200:
// description: Liste der Bewässerungsaufgaben
// schema:
// "$ref": "#/definitions/TaskList"
// 500:
// description: Serverfehler
// schema:
// "$ref": "#/definitions/Error"
func (srv *WebUIServer) controllerGetTasks(c *gin.Context) {
tasks, err := srv.rpc.GetTasks()
if err != nil {
log.Error().Err(err).Msg("GetTasks")
writeStatus(c, http.StatusInternalServerError, "error getting tasks")
return
}
c.JSON(200, tasks)
}
// swagger:operation GET /jobs getJobs
//
// Rufe alle gespeicherten Zeitpläne ab.
//
// ---
// produces: [application/json]
// responses:
// 200:
// description: Job-Liste
// schema:
// "$ref": "#/definitions/JobList"
// 500:
// description: Serverfehler
// schema:
// "$ref": "#/definitions/Error"
func (srv *WebUIServer) controllerGetJobs(c *gin.Context) {
jobs, err := srv.rpc.GetJobs()
if err != nil {
log.Error().Err(err).Msg("GetJobs")
writeStatus(c, http.StatusInternalServerError, "error getting jobs")
return
}
c.JSON(200, jobs)
}
// swagger:operation GET /job getJob
//
// Rufe einen gespeicherten Zeitplan ab.
//
// ---
// produces: [application/json]
// parameters:
// - name: id
// in: query
// description: Job-ID
// type: integer
// required: true
// responses:
// 200:
// description: Job
// schema:
// $ref: "#/definitions/Job"
// 500:
// description: Serverfehler
// schema:
// $ref: "#/definitions/Error"
func (srv *WebUIServer) controllerGetJob(c *gin.Context) {
idStr := c.Query("id")
id, err := strconv.ParseInt(idStr, 10, 32)
if err != nil {
log.Error().Err(err).Msg("GetJob input error")
writeStatus(c, http.StatusBadRequest, "invalid input data")
return
}
job, err := srv.rpc.GetJob(int32(id))
if err != nil {
log.Error().Err(err).Msg("GetJob")
writeStatus(c, http.StatusInternalServerError, "error getting job")
return
}
c.JSON(200, job)
}
// swagger:operation POST /job createJob
//
// Erstelle einen neuen Zeitplan.
//
// ---
// consumes: [application/json]
// produces: [application/json]
// parameters:
// - name: job
// in: body
// description: Neuer Job
// schema:
// $ref: '#/definitions/NewJob'
// required: true
// responses:
// 200:
// description: OK
// schema:
// $ref: "#/definitions/JobID"
// 500:
// description: Serverfehler
// schema:
// $ref: "#/definitions/Error"
func (srv *WebUIServer) controllerCreateJob(c *gin.Context) {
var newJob model.NewJob
if err := c.ShouldBindJSON(&newJob); err != nil {
log.Error().Err(err).Msg("CreateJob input error")
writeStatus(c, http.StatusBadRequest, "invalid input data")
return
}
jobId, err := srv.rpc.CreateJob(newJob)
if err != nil {
log.Error().Err(err).Msg("CreateJob")
writeStatus(c, http.StatusInternalServerError, "error creating job")
return
}
c.JSON(200, model.JobID{Id: jobId})
}
// swagger:operation PUT /job updateJob
//
// Aktualisiere einen gespeicherten Zeitplan.
//
// ---
// consumes: [application/json]
// produces: [application/json]
// parameters:
// - name: job
// in: body
// description: Aktualisierter Job
// schema:
// $ref: '#/definitions/Job'
// required: true
// responses:
// 200:
// description: OK
// schema:
// $ref: "#/definitions/StatusMessage"
// 500:
// description: Serverfehler
// schema:
// $ref: "#/definitions/Error"
func (srv *WebUIServer) controllerUpdateJob(c *gin.Context) {
var job model.Job
if err := c.ShouldBindJSON(&job); err != nil {
log.Error().Err(err).Msg("UpdateJob input error")
writeStatus(c, http.StatusBadRequest, "invalid input data")
return
}
err := srv.rpc.UpdateJob(job)
if err != nil {
log.Error().Err(err).Msg("UpdateJob")
writeStatus(c, http.StatusInternalServerError, "error updating job")
return
}
writeStatus(c, http.StatusOK, "updated job")
}
// swagger:operation DELETE /job deleteJob
//
// Lösche einen gespeicherten Zeitplan.
//
// ---
// consumes: [application/json]
// produces: [application/json]
// parameters:
// - name: jobId
// in: body
// description: JobID
// schema:
// $ref: '#/definitions/JobID'
// responses:
// 200:
// description: OK
// schema:
// $ref: "#/definitions/StatusMessage"
// 500:
// description: Serverfehler
// schema:
// $ref: "#/definitions/Error"
func (srv *WebUIServer) controllerDeleteJob(c *gin.Context) {
var jobId model.JobID
if err := c.ShouldBindJSON(&jobId); err != nil {
log.Error().Err(err).Msg("DeleteJob input error")
writeStatus(c, http.StatusBadRequest, "invalid input data")
return
}
err := srv.rpc.DeleteJob(jobId.Id)
if err != nil {
log.Error().Err(err).Msg("DeleteJob")
writeStatus(c, http.StatusInternalServerError, "error deleting job")
return
}
writeStatus(c, http.StatusOK, "deleted job")
}
// swagger:operation GET /config/auto getAutoMode
//
// Rufe ab, ob der Automatikmodus aktiviert ist.
//
// ---
// produces: [application/json]
// responses:
// 200:
// description: Status des Automatikmodus
// schema:
// $ref: "#/definitions/AutoMode"
// 500:
// description: Serverfehler
// schema:
// $ref: "#/definitions/Error"
func (srv *WebUIServer) controllerGetAutoMode(c *gin.Context) {
state, err := srv.rpc.GetAutoMode()
if err != nil {
log.Error().Err(err).Msg("GetAutoMode")
writeStatus(c, http.StatusInternalServerError, "error getting autoMode")
return
}
c.JSON(200, model.AutoMode{State: state})
}
// swagger:operation POST /config/auto setAutoMode
//
// Automatikmodus aktivieren/deaktivieren
//
// ---
// consumes: [application/json]
// produces: [application/json]
// parameters:
// - in: body
// name: state
// description: Zustand des Automatikmodus
// schema:
// $ref: '#/definitions/AutoMode'
// responses:
// 200:
// description: OK
// schema:
// $ref: "#/definitions/StatusMessage"
// 500:
// description: Serverfehler
// schema:
// $ref: "#/definitions/Error"
func (srv *WebUIServer) controllerSetAutoMode(c *gin.Context) {
var autoMode model.AutoMode
if err := c.ShouldBindJSON(&autoMode); err != nil {
log.Error().Err(err).Msg("SetAutoMode input error")
writeStatus(c, http.StatusBadRequest, "invalid input data")
return
}
err := srv.rpc.SetAutoMode(autoMode.State)
if err != nil {
log.Error().Err(err).Msg("SetAutoMode")
writeStatus(c, http.StatusInternalServerError, "error setting autoMode")
return
}
writeStatus(c, http.StatusOK, "set autoMode")
}
// swagger:operation GET /config/time getSystemTime
//
// Rufe die aktuelle Systemzeit/Zeitzone ab
//
// ---
// produces: [application/json]
// responses:
// 200:
// description: Aktuelle Systemzeit/Zeitzone
// schema:
// $ref: "#/definitions/SystemTime"
// 500:
// description: Serverfehler
// schema:
// $ref: "#/definitions/Error"
func (srv *WebUIServer) controllerGetSystemTime(c *gin.Context) {
systemTime, err := srv.rpc.GetSystemTime()
if err != nil {
log.Error().Err(err).Msg("GetSystemTime")
writeStatus(c, http.StatusInternalServerError, "error getting system time")
return
}
c.JSON(200, systemTime)
}
// swagger:operation POST /config/time setSystemTime
//
// Systemzeit einstellen
//
// ---
// consumes: [application/json]
// produces: [application/json]
// parameters:
// - in: body
// name: timestamp
// schema:
// $ref: "#/definitions/Timestamp"
// responses:
// 200:
// description: OK
// schema:
// $ref: "#/definitions/StatusMessage"
// 500:
// description: Serverfehler
// schema:
// $ref: "#/definitions/Error"
func (srv *WebUIServer) controllerSetSystemTime(c *gin.Context) {
var systemTime model.Timestamp
if err := c.ShouldBindJSON(&systemTime); err != nil {
log.Error().Err(err).Msg("SetSystemTime input error")
writeStatus(c, http.StatusBadRequest, "invalid input data")
return
}
err := srv.rpc.SetSystemTime(systemTime.Time)
if err != nil {
log.Error().Err(err).Msg("SetSystemTime")
writeStatus(c, http.StatusInternalServerError, "error setting systemTime")
return
}
writeStatus(c, http.StatusOK, "set systemTime")
}
// swagger:operation POST /config/timezone setSystemTimezone
//
// Systemzeitzone einstellen
//
// ---
// consumes: [application/json]
// produces: [application/json]
// parameters:
// - in: body
// name: timezone
// schema:
// $ref: "#/definitions/Timezone"
// responses:
// 200:
// description: OK
// schema:
// $ref: "#/definitions/StatusMessage"
// 500:
// description: Serverfehler
// schema:
// $ref: "#/definitions/Error"
func (srv *WebUIServer) controllerSetSystemTimezone(c *gin.Context) {
var timezone model.Timezone
if err := c.ShouldBindJSON(&timezone); err != nil {
log.Error().Err(err).Msg("SetSystemTimezone input error")
writeStatus(c, http.StatusBadRequest, "invalid input data")
return
}
err := srv.rpc.SetSystemTimezone(timezone.Timezone)
if err != nil {
log.Error().Err(err).Msg("SetSystemTimezone")
writeStatus(c, http.StatusInternalServerError, "error setting systemTimezone")
return
}
writeStatus(c, http.StatusOK, "set systemTimezone")
}
// swagger:operation GET /config/defaultIrrigationTime getDefaultIrrigationTime
//
// Rufe die Standardzeit bei manueller Bewässerung ab.
//
// ---
// produces: [application/json]
// responses:
// 200:
// description: Manuelle Bewässerungszeit in Sekunden
// schema:
// $ref: "#/definitions/DefaultIrrigationTime"
// 500:
// description: Serverfehler
// schema:
// $ref: "#/definitions/Error"
func (srv *WebUIServer) controllerGetDefaultIrrigationTime(c *gin.Context) {
defaultIrrigationTime, err := srv.rpc.GetDefaultIrrigationTime()
if err != nil {
log.Error().Err(err).Msg("GetDefaultIrrigationTime")
writeStatus(c, http.StatusInternalServerError,
"error getting defaultIrrigationTime")
return
}
c.JSON(200, model.DefaultIrrigationTime{Time: defaultIrrigationTime})
}
// swagger:operation POST /config/defaultIrrigationTime setDefaultIrrigationTime
//
// Setze die die Standardzeit bei manueller Bewässerung.
//
// ---
// consumes: [application/json]
// produces: [application/json]
// parameters:
// - in: body
// name: defaultIrrigationTime
// schema:
// $ref: "#/definitions/DefaultIrrigationTime"
// responses:
// 200:
// description: OK
// schema:
// $ref: "#/definitions/StatusMessage"
// 500:
// description: Serverfehler
// schema:
// $ref: "#/definitions/Error"
func (srv *WebUIServer) controllerSetDefaultIrrigationTime(c *gin.Context) {
var defaultIrrigationTime model.DefaultIrrigationTime
if err := c.ShouldBindJSON(&defaultIrrigationTime); err != nil {
log.Error().Err(err).Msg("SetDefaultIrrigationTime input error")
writeStatus(c, http.StatusBadRequest, "invalid input data")
return
}
err := srv.rpc.SetDefaultIrrigationTime(defaultIrrigationTime.Time)
if err != nil {
log.Error().Err(err).Msg("SetDefaultIrrigationTime")
writeStatus(c, http.StatusInternalServerError,
"error setting defaultIrrigationTime")
return
}
writeStatus(c, http.StatusOK, "set defaultIrrigationTime")
}
// controllerError throws an error for testing
func (srv *WebUIServer) controllerError(c *gin.Context) {
c.Error(util.HttpErrNew("error test", http.StatusBadRequest))
}
// controllerPanic panics for testing
func (srv *WebUIServer) controllerPanic(c *gin.Context) {
panic(errors.New("panic message"))
}
func (srv *WebUIServer) validateZoneID(zoneId int32) bool {
return 0 < zoneId && zoneId <= srv.nZones
}
func writeStatus(c *gin.Context, code int, msg string) {
c.JSON(code, model.StatusMessage{
Success: code == http.StatusOK,
Msg: msg,
})
}