Compare commits
3 commits
60034b4815
...
834092a101
Author | SHA1 | Date | |
---|---|---|---|
834092a101 | |||
207db1c7a8 | |||
73208b6f51 |
43 changed files with 5060 additions and 113 deletions
|
@ -16,7 +16,7 @@ tmp_dir = "tmp"
|
||||||
kill_delay = "0s"
|
kill_delay = "0s"
|
||||||
log = "build-errors.log"
|
log = "build-errors.log"
|
||||||
send_interrupt = false
|
send_interrupt = false
|
||||||
stop_on_error = true
|
stop_on_error = false
|
||||||
|
|
||||||
[color]
|
[color]
|
||||||
app = ""
|
app = ""
|
||||||
|
|
|
@ -7,16 +7,16 @@ repos:
|
||||||
- id: go-test-repo-mod
|
- id: go-test-repo-mod
|
||||||
name: Backend tests
|
name: Backend tests
|
||||||
|
|
||||||
# - repo: local
|
- repo: local
|
||||||
# hooks:
|
hooks:
|
||||||
# - id: tsc
|
- id: tsc
|
||||||
# name: tsc
|
name: tsc
|
||||||
# entry: tsc
|
entry: tsc
|
||||||
# language: node
|
language: node
|
||||||
# files: \.tsx?$
|
files: \.tsx?$
|
||||||
# args: ["-p", "./ui/tsconfig.json"]
|
args: ["-p", "./ui/tsconfig.json"]
|
||||||
# additional_dependencies: ["typescript@4.5.2"]
|
additional_dependencies: ["typescript@4.5.2"]
|
||||||
# pass_filenames: false
|
pass_filenames: false
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||||
rev: v2.4.1
|
rev: v2.4.1
|
||||||
|
|
10
.vscode/launch.json
vendored
10
.vscode/launch.json
vendored
|
@ -2,9 +2,10 @@
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "SEBRAUC server",
|
"name": "TSGRain WebUI server",
|
||||||
"type": "go",
|
"type": "go",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
"mode": "auto",
|
"mode": "auto",
|
||||||
"program": "${workspaceFolder}/src"
|
"program": "${workspaceFolder}/src"
|
||||||
},
|
},
|
||||||
|
@ -16,13 +17,6 @@
|
||||||
"runtimeExecutable": "npm",
|
"runtimeExecutable": "npm",
|
||||||
"skipFiles": ["<node_internals>/**"],
|
"skipFiles": ["<node_internals>/**"],
|
||||||
"type": "pwa-node"
|
"type": "pwa-node"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Test program",
|
|
||||||
"type": "go",
|
|
||||||
"request": "launch",
|
|
||||||
"mode": "auto",
|
|
||||||
"program": "${workspaceFolder}/tmp"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
3
Makefile
3
Makefile
|
@ -35,9 +35,10 @@ protoc:
|
||||||
|
|
||||||
generate-apidoc:
|
generate-apidoc:
|
||||||
SWAGGER_GENERATE_EXTENSION=false swagger generate spec --scan-models -o ${APIDOC_FILE}
|
SWAGGER_GENERATE_EXTENSION=false swagger generate spec --scan-models -o ${APIDOC_FILE}
|
||||||
|
swagger validate ${APIDOC_FILE}
|
||||||
|
|
||||||
generate-apiclient:
|
generate-apiclient:
|
||||||
openapi-generator generate -i ${APIDOC_FILE} -g typescript-axios -o ${UI_DIR}/src/sebrauc-client -p "supportsES6=true"
|
openapi-generator generate -i ${APIDOC_FILE} -g typescript-axios -o ${UI_DIR}/src/tsgrain-client -p "supportsES6=true"
|
||||||
cd ${UI_DIR} && npm run format
|
cd ${UI_DIR} && npm run format
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|
|
@ -87,6 +87,7 @@ message Task {
|
||||||
message TaskList {
|
message TaskList {
|
||||||
repeated Task tasks = 1;
|
repeated Task tasks = 1;
|
||||||
Timestamp now = 2;
|
Timestamp now = 2;
|
||||||
|
bool auto_mode = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Job {
|
message Job {
|
||||||
|
|
74
src/model/job.go
Normal file
74
src/model/job.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
// Job model
|
||||||
|
//
|
||||||
|
// Job stellt einen Bewässerungszeitplan dar.
|
||||||
|
//
|
||||||
|
//swagger:model Job
|
||||||
|
type Job struct {
|
||||||
|
// ID des Jobs
|
||||||
|
// required: true
|
||||||
|
// example: 1
|
||||||
|
Id int32 `json:"id"`
|
||||||
|
|
||||||
|
// Zritstempel des Bewässerungsjobs
|
||||||
|
// required: true
|
||||||
|
// example: 1643756107
|
||||||
|
Date int64 `json:"date"`
|
||||||
|
|
||||||
|
// Bewässerungsdauer in Sekunden
|
||||||
|
// required: true
|
||||||
|
// example: 300
|
||||||
|
Duration int32 `json:"duration"`
|
||||||
|
|
||||||
|
// Zu bewässernde Zonen
|
||||||
|
// required: true
|
||||||
|
// example: [2, 3]
|
||||||
|
Zones []int32 `json:"zones"`
|
||||||
|
|
||||||
|
// Job aktiviert?
|
||||||
|
// required: true
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
|
||||||
|
// Job täglich wiederholen
|
||||||
|
// required: true
|
||||||
|
Repeat bool `json:"repeat"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJob model
|
||||||
|
//
|
||||||
|
// NewJob stellt einen zu erstellenden Bewässerungszeitplan dar.
|
||||||
|
//
|
||||||
|
//swagger:model NewJob
|
||||||
|
type NewJob struct {
|
||||||
|
// Zritstempel des Bewässerungsjobs
|
||||||
|
// required: true
|
||||||
|
// example: 1643756107
|
||||||
|
Date int64 `json:"date"`
|
||||||
|
|
||||||
|
// Bewässerungsdauer in Sekunden
|
||||||
|
// required: true
|
||||||
|
// example: 300
|
||||||
|
Duration int32 `json:"duration"`
|
||||||
|
|
||||||
|
// Zu bewässernde Zonen
|
||||||
|
// required: true
|
||||||
|
// example: [2, 3]
|
||||||
|
Zones []int32 `json:"zones"`
|
||||||
|
|
||||||
|
// Job aktiviert?
|
||||||
|
// required: true
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
|
||||||
|
// Job täglich wiederholen
|
||||||
|
// required: true
|
||||||
|
Repeat bool `json:"repeat"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//swagger:model JobList
|
||||||
|
type JobList []Job
|
||||||
|
|
||||||
|
//swagger:model JobID
|
||||||
|
type JobID struct {
|
||||||
|
Id int32 `json:"id" binding:"required"`
|
||||||
|
}
|
38
src/model/options.go
Normal file
38
src/model/options.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
// AutoMode model
|
||||||
|
//
|
||||||
|
// Zustand des Automatikmodus
|
||||||
|
//
|
||||||
|
//swagger:model AutoMode
|
||||||
|
type AutoMode struct {
|
||||||
|
// required: true
|
||||||
|
State bool `json:"state" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultIrrigationTime model
|
||||||
|
//
|
||||||
|
// Manuelle Bewässerungszeit in Sekunden
|
||||||
|
//
|
||||||
|
//swagger:model DefaultIrrigationTime
|
||||||
|
type DefaultIrrigationTime struct {
|
||||||
|
// required: true
|
||||||
|
Time int32 `json:"time" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigTime model
|
||||||
|
//
|
||||||
|
// Aktuelle Systemzeit/Zeitzone
|
||||||
|
//
|
||||||
|
//swagger:model ConfigTime
|
||||||
|
type ConfigTime struct {
|
||||||
|
// Aktuelle Systemzeit
|
||||||
|
//
|
||||||
|
// required: true
|
||||||
|
Time int64 `json:"time"`
|
||||||
|
|
||||||
|
// Aktuelle Zeitzone
|
||||||
|
//
|
||||||
|
// required: true
|
||||||
|
Timezone string `json:"timezone"`
|
||||||
|
}
|
65
src/model/task.go
Normal file
65
src/model/task.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
// Task model
|
||||||
|
//
|
||||||
|
// Task stellt eine Bewässerungsaufgabe dar.
|
||||||
|
//
|
||||||
|
//swagger:model Task
|
||||||
|
type Task struct {
|
||||||
|
// Quelle der Bewässerungsaufgabe (0: MANUAL, 1: SCHEDULE)
|
||||||
|
// required: true
|
||||||
|
// example: 0
|
||||||
|
Source int32 `json:"source"`
|
||||||
|
|
||||||
|
// Nummer der Bewässerungszone
|
||||||
|
// required: true
|
||||||
|
// example: 2
|
||||||
|
ZoneId int32 `json:"zone_id"`
|
||||||
|
|
||||||
|
// Bewässerungsdauer in Sekunden
|
||||||
|
// required: true
|
||||||
|
// example: 300
|
||||||
|
Duration int32 `json:"duration"`
|
||||||
|
|
||||||
|
// Zeitstempel, wann die Bewässerung gestartet wurde
|
||||||
|
// required: true
|
||||||
|
// nullable: true
|
||||||
|
// example: 1643756107
|
||||||
|
DatetimeStarted *int64 `json:"datetime_started"`
|
||||||
|
|
||||||
|
// Zeitstempel, wann die Bewässerung beendet sein wird
|
||||||
|
// required: true
|
||||||
|
// nullable: true
|
||||||
|
// example: 1643756407
|
||||||
|
DatetimeFinished *int64 `json:"datetime_finished"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskList model
|
||||||
|
//
|
||||||
|
// TaskList ist eine Liste der aktuell laufenden Bewässerungsaufgaben.
|
||||||
|
//
|
||||||
|
//swagger:model TaskList
|
||||||
|
type TaskList struct {
|
||||||
|
// Aktueller Zeitstempel
|
||||||
|
// required: true
|
||||||
|
// example: 1643756107
|
||||||
|
Now int64 `json:"now"`
|
||||||
|
|
||||||
|
// Liste der laufenden Tasks
|
||||||
|
// required: true
|
||||||
|
Tasks []Task `json:"tasks"`
|
||||||
|
|
||||||
|
// Automatikmodus aktiv
|
||||||
|
// required: true
|
||||||
|
AutoMode bool `json:"auto_mode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskRequestResult model
|
||||||
|
//
|
||||||
|
// TaskRequestResult wird beim Starten eines Tasks zurückgegeben
|
||||||
|
//
|
||||||
|
//swagger:model TaskRequestResult
|
||||||
|
type TaskRequestResult struct {
|
||||||
|
Started bool
|
||||||
|
Stopped bool
|
||||||
|
}
|
|
@ -24,14 +24,17 @@ import (
|
||||||
"code.thetadev.de/TSGRain/WebUI/src/tsgrain_rpc"
|
"code.thetadev.de/TSGRain/WebUI/src/tsgrain_rpc"
|
||||||
"code.thetadev.de/TSGRain/WebUI/src/util"
|
"code.thetadev.de/TSGRain/WebUI/src/util"
|
||||||
"code.thetadev.de/TSGRain/WebUI/src/util/mode"
|
"code.thetadev.de/TSGRain/WebUI/src/util/mode"
|
||||||
|
"code.thetadev.de/TSGRain/WebUI/ui"
|
||||||
"github.com/gin-contrib/cors"
|
"github.com/gin-contrib/cors"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WebUIServer struct {
|
type WebUIServer struct {
|
||||||
config *config.Config
|
config *config.Config
|
||||||
streamer *stream.API
|
streamer *stream.API
|
||||||
rpc *tsgrain_rpc.RPCClient
|
rpc *tsgrain_rpc.RPCClient
|
||||||
|
nZones int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(config *config.Config) *WebUIServer {
|
func NewServer(config *config.Config) *WebUIServer {
|
||||||
|
@ -81,12 +84,26 @@ func (srv *WebUIServer) getRouter() *gin.Engine {
|
||||||
api.GET("/panic", srv.controllerPanic)
|
api.GET("/panic", srv.controllerPanic)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
api.POST("/task/manual", srv.controllerStartTask)
|
||||||
|
|
||||||
|
api.GET("/jobs", srv.controllerGetJobs)
|
||||||
|
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.controllerGetConfigTime)
|
||||||
|
api.POST("config/time", srv.controllerSetConfigTime)
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
uiGroup := router.Group("/", middleware.Compression(
|
uiGroup := router.Group("/", middleware.Compression(
|
||||||
srv.config.Server.Compression.Gzip,
|
srv.config.Server.Compression.Gzip,
|
||||||
srv.config.Server.Compression.Brotli),
|
srv.config.Server.Compression.Brotli),
|
||||||
)
|
)
|
||||||
// ui.Register(uiGroup)
|
ui.Register(uiGroup, srv.nZones)
|
||||||
swagger.Register(uiGroup)
|
swagger.Register(uiGroup)
|
||||||
|
|
||||||
return router
|
return router
|
||||||
|
@ -98,12 +115,496 @@ func (srv *WebUIServer) Run() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get TSGRain info
|
||||||
|
nZones, err := srv.rpc.GetNZones()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
srv.nZones = nZones
|
||||||
|
|
||||||
go srv.rpc.StreamTasks(srv.streamer)
|
go srv.rpc.StreamTasks(srv.streamer)
|
||||||
|
|
||||||
router := srv.getRouter()
|
router := srv.getRouter()
|
||||||
|
|
||||||
return router.Run(fmt.Sprintf("%s:%d",
|
err = router.Run(fmt.Sprintf("%s:%d",
|
||||||
srv.config.Server.Address, srv.config.Server.Port))
|
srv.config.Server.Address, srv.config.Server.Port))
|
||||||
|
|
||||||
|
_ = srv.rpc.Stop()
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// swagger:operation POST /task/manual startManualTask
|
||||||
|
//
|
||||||
|
// Starte/stoppe manuell eine neue Bewässerungsaufgabe
|
||||||
|
//
|
||||||
|
// ---
|
||||||
|
// consumes: [application/json]
|
||||||
|
// produces: [application/json]
|
||||||
|
// parameters:
|
||||||
|
// - in: body
|
||||||
|
// name: taskRequest
|
||||||
|
// schema:
|
||||||
|
// type: object
|
||||||
|
// properties:
|
||||||
|
// zone_id:
|
||||||
|
// description: Nummer der Bewässerungszone
|
||||||
|
// type: integer
|
||||||
|
// duration:
|
||||||
|
// description: "Bewässerungsdauer in Sekunden (0: Standarddauer)"
|
||||||
|
// type: integer
|
||||||
|
// queuing:
|
||||||
|
// description: |
|
||||||
|
// Aufgabe in die Warteschlange einreihen,
|
||||||
|
// wenn sie nicht sofort ausgeführt werden kann.
|
||||||
|
// type: boolean
|
||||||
|
// cancelling:
|
||||||
|
// description: |
|
||||||
|
// Aufgabe stoppen/aus der Warteschlange entfernen,
|
||||||
|
// wenn sie bereits läuft oder sich in der Warteschlange befindet.
|
||||||
|
// type: boolean
|
||||||
|
// required:
|
||||||
|
// - zone_id
|
||||||
|
// - duration
|
||||||
|
// - queuing
|
||||||
|
// - cancelling
|
||||||
|
// responses:
|
||||||
|
// 200:
|
||||||
|
// description: Bewässerungsaufgabe erfolgreich gestarted/gestoppt
|
||||||
|
// schema:
|
||||||
|
// $ref: "#/definitions/StatusMessage"
|
||||||
|
// 400:
|
||||||
|
// description: Bewässerungsaufgabe läuft schon und kann nicht gestoppt werden.
|
||||||
|
// schema:
|
||||||
|
// $ref: "#/definitions/Error"
|
||||||
|
// 500:
|
||||||
|
// description: Serverfehler
|
||||||
|
// schema:
|
||||||
|
// $ref: "#/definitions/Error"
|
||||||
|
func (srv *WebUIServer) controllerStartTask(c *gin.Context) {
|
||||||
|
var params struct {
|
||||||
|
ZoneID int32 `json:"zone_id"`
|
||||||
|
Duration int32 `json:"duration"`
|
||||||
|
Queuing bool `json:"queuing"`
|
||||||
|
Cancelling bool `json:"cancelling"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.ShouldBindJSON(¶ms); err != nil {
|
||||||
|
log.Error().Err(err).Msg("StartTask input error")
|
||||||
|
writeStatus(c, http.StatusBadRequest, "invalid input data")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !srv.validateZoneID(params.ZoneID) {
|
||||||
|
writeStatus(c, http.StatusBadRequest, "invalid zone_id")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := srv.rpc.StartManualTask(
|
||||||
|
params.ZoneID, params.Duration, params.Queuing, params.Cancelling)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("StartManualTask")
|
||||||
|
writeStatus(c, http.StatusInternalServerError, "error starting task")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
statusCode := 200
|
||||||
|
if !res.Started && !res.Stopped {
|
||||||
|
statusCode = 400
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(statusCode, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
var params struct {
|
||||||
|
Id int32 `uri:"id" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.ShouldBindUri(¶ms); err != nil {
|
||||||
|
log.Error().Err(err).Msg("GetJob input error")
|
||||||
|
writeStatus(c, http.StatusBadRequest, "invalid input data")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
job, err := srv.rpc.GetJob(params.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: id
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// type: integer
|
||||||
|
// 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:
|
||||||
|
// type: object
|
||||||
|
// properties:
|
||||||
|
// state:
|
||||||
|
// type: boolean
|
||||||
|
// required:
|
||||||
|
// - state
|
||||||
|
// 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 getConfigTime
|
||||||
|
//
|
||||||
|
// Rufe die aktuelle Systemzeit/Zeitzone ab
|
||||||
|
//
|
||||||
|
// ---
|
||||||
|
// produces: [application/json]
|
||||||
|
// responses:
|
||||||
|
// 200:
|
||||||
|
// description: Aktuelle Systemzeit/Zeitzone
|
||||||
|
// schema:
|
||||||
|
// $ref: "#/definitions/ConfigTime"
|
||||||
|
// 500:
|
||||||
|
// description: Serverfehler
|
||||||
|
// schema:
|
||||||
|
// $ref: "#/definitions/Error"
|
||||||
|
func (srv *WebUIServer) controllerGetConfigTime(c *gin.Context) {
|
||||||
|
configTime, err := srv.rpc.GetConfigTime()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("GetConfigTime")
|
||||||
|
writeStatus(c, http.StatusInternalServerError, "error setting configTime")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, configTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// swagger:operation POST /config/time setConfigTime
|
||||||
|
//
|
||||||
|
// Automatikmodus aktivieren/deaktivieren
|
||||||
|
//
|
||||||
|
// ---
|
||||||
|
// consumes: [application/json]
|
||||||
|
// produces: [application/json]
|
||||||
|
// parameters:
|
||||||
|
// - in: body
|
||||||
|
// name: configTime
|
||||||
|
// schema:
|
||||||
|
// $ref: "#/definitions/ConfigTime"
|
||||||
|
// responses:
|
||||||
|
// 200:
|
||||||
|
// description: OK
|
||||||
|
// schema:
|
||||||
|
// $ref: "#/definitions/StatusMessage"
|
||||||
|
// 500:
|
||||||
|
// description: Serverfehler
|
||||||
|
// schema:
|
||||||
|
// $ref: "#/definitions/Error"
|
||||||
|
func (srv *WebUIServer) controllerSetConfigTime(c *gin.Context) {
|
||||||
|
var configTime model.ConfigTime
|
||||||
|
if err := c.ShouldBindJSON(&configTime); err != nil {
|
||||||
|
log.Error().Err(err).Msg("SetConfigTime input error")
|
||||||
|
writeStatus(c, http.StatusBadRequest, "invalid input data")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := srv.rpc.SetConfigTime(configTime)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("SetConfigTime")
|
||||||
|
writeStatus(c, http.StatusInternalServerError, "error setting configTime")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
writeStatus(c, http.StatusOK, "set configTime")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, 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
|
// controllerError throws an error for testing
|
||||||
|
@ -116,9 +617,13 @@ func (srv *WebUIServer) controllerPanic(c *gin.Context) {
|
||||||
panic(errors.New("panic message"))
|
panic(errors.New("panic message"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeStatus(c *gin.Context, success bool, msg string) {
|
func (srv *WebUIServer) validateZoneID(zoneId int32) bool {
|
||||||
c.JSON(http.StatusOK, model.StatusMessage{
|
return 0 < zoneId && zoneId <= srv.nZones
|
||||||
Success: success,
|
}
|
||||||
|
|
||||||
|
func writeStatus(c *gin.Context, code int, msg string) {
|
||||||
|
c.JSON(code, model.StatusMessage{
|
||||||
|
Success: code == http.StatusOK,
|
||||||
Msg: msg,
|
Msg: msg,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>SEBRAUC API documentation</title>
|
<title>TSGRain WebUI API documentation</title>
|
||||||
|
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
|
@ -1,4 +1,38 @@
|
||||||
definitions:
|
definitions:
|
||||||
|
AutoMode:
|
||||||
|
description: Zustand des Automatikmodus
|
||||||
|
properties:
|
||||||
|
state:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- state
|
||||||
|
title: AutoMode model
|
||||||
|
type: object
|
||||||
|
ConfigTime:
|
||||||
|
description: Aktuelle Systemzeit/Zeitzone
|
||||||
|
properties:
|
||||||
|
time:
|
||||||
|
description: Aktuelle Systemzeit
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
timezone:
|
||||||
|
description: Aktuelle Zeitzone
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- time
|
||||||
|
- timezone
|
||||||
|
title: ConfigTime model
|
||||||
|
type: object
|
||||||
|
DefaultIrrigationTime:
|
||||||
|
description: Manuelle Bewässerungszeit in Sekunden
|
||||||
|
properties:
|
||||||
|
time:
|
||||||
|
format: int32
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- time
|
||||||
|
title: DefaultIrrigationTime model
|
||||||
|
type: object
|
||||||
Error:
|
Error:
|
||||||
description: The Error contains error relevant information.
|
description: The Error contains error relevant information.
|
||||||
properties:
|
properties:
|
||||||
|
@ -21,6 +55,94 @@ definitions:
|
||||||
- msg
|
- msg
|
||||||
title: Error model
|
title: Error model
|
||||||
type: object
|
type: object
|
||||||
|
Job:
|
||||||
|
description: Job stellt einen Bewässerungszeitplan dar.
|
||||||
|
properties:
|
||||||
|
date:
|
||||||
|
description: Zritstempel des Bewässerungsjobs
|
||||||
|
example: 1643756107
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
duration:
|
||||||
|
description: Bewässerungsdauer in Sekunden
|
||||||
|
example: 300
|
||||||
|
format: int32
|
||||||
|
type: integer
|
||||||
|
enable:
|
||||||
|
description: Job aktiviert?
|
||||||
|
type: boolean
|
||||||
|
id:
|
||||||
|
description: ID des Jobs
|
||||||
|
example: 1
|
||||||
|
format: int32
|
||||||
|
type: integer
|
||||||
|
repeat:
|
||||||
|
description: Job täglich wiederholen
|
||||||
|
type: boolean
|
||||||
|
zones:
|
||||||
|
description: Zu bewässernde Zonen
|
||||||
|
example:
|
||||||
|
- 2
|
||||||
|
- 3
|
||||||
|
items:
|
||||||
|
format: int32
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- date
|
||||||
|
- duration
|
||||||
|
- zones
|
||||||
|
- enable
|
||||||
|
- repeat
|
||||||
|
title: Job model
|
||||||
|
type: object
|
||||||
|
JobID:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
format: int32
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
|
JobList:
|
||||||
|
items:
|
||||||
|
$ref: "#/definitions/Job"
|
||||||
|
type: array
|
||||||
|
NewJob:
|
||||||
|
description: NewJob stellt einen zu erstellenden Bewässerungszeitplan dar.
|
||||||
|
properties:
|
||||||
|
date:
|
||||||
|
description: Zritstempel des Bewässerungsjobs
|
||||||
|
example: 1643756107
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
duration:
|
||||||
|
description: Bewässerungsdauer in Sekunden
|
||||||
|
example: 300
|
||||||
|
format: int32
|
||||||
|
type: integer
|
||||||
|
enable:
|
||||||
|
description: Job aktiviert?
|
||||||
|
type: boolean
|
||||||
|
repeat:
|
||||||
|
description: Job täglich wiederholen
|
||||||
|
type: boolean
|
||||||
|
zones:
|
||||||
|
description: Zu bewässernde Zonen
|
||||||
|
example:
|
||||||
|
- 2
|
||||||
|
- 3
|
||||||
|
items:
|
||||||
|
format: int32
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- date
|
||||||
|
- duration
|
||||||
|
- zones
|
||||||
|
- enable
|
||||||
|
- repeat
|
||||||
|
title: NewJob model
|
||||||
|
type: object
|
||||||
StatusMessage:
|
StatusMessage:
|
||||||
description: StatusMessage contains the status of an operation.
|
description: StatusMessage contains the status of an operation.
|
||||||
properties:
|
properties:
|
||||||
|
@ -36,13 +158,345 @@ definitions:
|
||||||
- msg
|
- msg
|
||||||
title: StatusMessage model
|
title: StatusMessage model
|
||||||
type: object
|
type: object
|
||||||
|
Task:
|
||||||
|
description: Task stellt eine Bewässerungsaufgabe dar.
|
||||||
|
properties:
|
||||||
|
datetime_finished:
|
||||||
|
description: Zeitstempel, wann die Bewässerung beendet sein wird
|
||||||
|
example: 1643756407
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
datetime_started:
|
||||||
|
description: Zeitstempel, wann die Bewässerung gestartet wurde
|
||||||
|
example: 1643756107
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
duration:
|
||||||
|
description: Bewässerungsdauer in Sekunden
|
||||||
|
example: 300
|
||||||
|
format: int32
|
||||||
|
type: integer
|
||||||
|
source:
|
||||||
|
description: "Quelle der Bewässerungsaufgabe (0: MANUAL, 1: SCHEDULE)"
|
||||||
|
example: 0
|
||||||
|
format: int32
|
||||||
|
type: integer
|
||||||
|
zone_id:
|
||||||
|
description: Nummer der Bewässerungszone
|
||||||
|
example: 2
|
||||||
|
format: int32
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- source
|
||||||
|
- zone_id
|
||||||
|
- duration
|
||||||
|
- datetime_started
|
||||||
|
- datetime_finished
|
||||||
|
title: Task model
|
||||||
|
type: object
|
||||||
|
TaskList:
|
||||||
|
description: TaskList ist eine Liste der aktuell laufenden Bewässerungsaufgaben.
|
||||||
|
properties:
|
||||||
|
auto_mode:
|
||||||
|
description: Automatikmodus aktiv
|
||||||
|
type: boolean
|
||||||
|
now:
|
||||||
|
description: Aktueller Zeitstempel
|
||||||
|
example: 1643756107
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
tasks:
|
||||||
|
description: Liste der laufenden Tasks
|
||||||
|
items:
|
||||||
|
$ref: "#/definitions/Task"
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- now
|
||||||
|
- tasks
|
||||||
|
- auto_mode
|
||||||
|
title: TaskList model
|
||||||
|
type: object
|
||||||
|
TaskRequestResult:
|
||||||
|
description: TaskRequestResult wird beim Starten eines Tasks zurückgegeben
|
||||||
|
properties:
|
||||||
|
Started:
|
||||||
|
type: boolean
|
||||||
|
Stopped:
|
||||||
|
type: boolean
|
||||||
|
title: TaskRequestResult model
|
||||||
|
type: object
|
||||||
info:
|
info:
|
||||||
description: REST API for the TSGRain WebUI
|
description: REST API for the TSGRain WebUI
|
||||||
license:
|
license:
|
||||||
name: MIT
|
name: MIT
|
||||||
title: TSGRain WebUI
|
title: TSGRain WebUI
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
paths: {}
|
paths:
|
||||||
|
/config/auto:
|
||||||
|
get:
|
||||||
|
operationId: getAutoMode
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Status des Automatikmodus
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/AutoMode"
|
||||||
|
"500":
|
||||||
|
description: Serverfehler
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/Error"
|
||||||
|
summary: Rufe ab, ob der Automatikmodus aktiviert ist.
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Automatikmodus aktivieren/deaktivieren
|
||||||
|
operationId: setAutoMode
|
||||||
|
parameters:
|
||||||
|
- description: Zustand des Automatikmodus
|
||||||
|
in: body
|
||||||
|
name: state
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
state:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- state
|
||||||
|
type: object
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/StatusMessage"
|
||||||
|
"500":
|
||||||
|
description: Serverfehler
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/Error"
|
||||||
|
/config/defaultIrrigationTime:
|
||||||
|
get:
|
||||||
|
operationId: getDefaultIrrigationTime
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Manuelle Bewässerungszeit in Sekunden
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/DefaultIrrigationTime"
|
||||||
|
"500":
|
||||||
|
description: Serverfehler
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/Error"
|
||||||
|
summary: Rufe die Standardzeit bei manueller Bewässerung ab.
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
operationId: setDefaultIrrigationTime
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: defaultIrrigationTime
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/DefaultIrrigationTime"
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/StatusMessage"
|
||||||
|
"500":
|
||||||
|
description: Serverfehler
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/Error"
|
||||||
|
summary: Setze die die Standardzeit bei manueller Bewässerung.
|
||||||
|
/config/time:
|
||||||
|
get:
|
||||||
|
description: Rufe die aktuelle Systemzeit/Zeitzone ab
|
||||||
|
operationId: getConfigTime
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Aktuelle Systemzeit/Zeitzone
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ConfigTime"
|
||||||
|
"500":
|
||||||
|
description: Serverfehler
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/Error"
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Automatikmodus aktivieren/deaktivieren
|
||||||
|
operationId: setConfigTime
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: configTime
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ConfigTime"
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/StatusMessage"
|
||||||
|
"500":
|
||||||
|
description: Serverfehler
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/Error"
|
||||||
|
/job:
|
||||||
|
delete:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
operationId: deleteJob
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/StatusMessage"
|
||||||
|
"500":
|
||||||
|
description: Serverfehler
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/Error"
|
||||||
|
summary: Lösche einen gespeicherten Zeitplan.
|
||||||
|
get:
|
||||||
|
operationId: getJob
|
||||||
|
parameters:
|
||||||
|
- description: Job-ID
|
||||||
|
in: query
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Job
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/Job"
|
||||||
|
"500":
|
||||||
|
description: Serverfehler
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/Error"
|
||||||
|
summary: Rufe einen gespeicherten Zeitplan ab.
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
operationId: createJob
|
||||||
|
parameters:
|
||||||
|
- description: Neuer Job
|
||||||
|
in: body
|
||||||
|
name: job
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/NewJob"
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/JobID"
|
||||||
|
"500":
|
||||||
|
description: Serverfehler
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/Error"
|
||||||
|
summary: Erstelle einen neuen Zeitplan.
|
||||||
|
put:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
operationId: updateJob
|
||||||
|
parameters:
|
||||||
|
- description: Aktualisierter Job
|
||||||
|
in: body
|
||||||
|
name: job
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/Job"
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/StatusMessage"
|
||||||
|
"500":
|
||||||
|
description: Serverfehler
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/Error"
|
||||||
|
summary: Aktualisiere einen gespeicherten Zeitplan.
|
||||||
|
/jobs:
|
||||||
|
get:
|
||||||
|
operationId: getJobs
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Job-Liste
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/JobList"
|
||||||
|
"500":
|
||||||
|
description: Serverfehler
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/Error"
|
||||||
|
summary: Rufe alle gespeicherten Zeitpläne ab.
|
||||||
|
/task/manual:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Starte/stoppe manuell eine neue Bewässerungsaufgabe
|
||||||
|
operationId: startManualTask
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: taskRequest
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
cancelling:
|
||||||
|
description: |
|
||||||
|
Aufgabe stoppen/aus der Warteschlange entfernen,
|
||||||
|
wenn sie bereits läuft oder sich in der Warteschlange befindet.
|
||||||
|
type: boolean
|
||||||
|
duration:
|
||||||
|
description: "Bewässerungsdauer in Sekunden (0: Standarddauer)"
|
||||||
|
type: integer
|
||||||
|
queuing:
|
||||||
|
description: |
|
||||||
|
Aufgabe in die Warteschlange einreihen,
|
||||||
|
wenn sie nicht sofort ausgeführt werden kann.
|
||||||
|
type: boolean
|
||||||
|
zone_id:
|
||||||
|
description: Nummer der Bewässerungszone
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- zone_id
|
||||||
|
- duration
|
||||||
|
- queuing
|
||||||
|
- cancelling
|
||||||
|
type: object
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Bewässerungsaufgabe erfolgreich gestarted/gestoppt
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/StatusMessage"
|
||||||
|
"400":
|
||||||
|
description: Bewässerungsaufgabe läuft schon und kann nicht gestoppt werden.
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/Error"
|
||||||
|
"500":
|
||||||
|
description: Serverfehler
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/Error"
|
||||||
schemes:
|
schemes:
|
||||||
- http
|
- http
|
||||||
- https
|
- https
|
||||||
|
|
|
@ -335,8 +335,9 @@ type TaskList struct {
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
Tasks []*Task `protobuf:"bytes,1,rep,name=tasks,proto3" json:"tasks,omitempty"`
|
Tasks []*Task `protobuf:"bytes,1,rep,name=tasks,proto3" json:"tasks,omitempty"`
|
||||||
Now *Timestamp `protobuf:"bytes,2,opt,name=now,proto3" json:"now,omitempty"`
|
Now *Timestamp `protobuf:"bytes,2,opt,name=now,proto3" json:"now,omitempty"`
|
||||||
|
AutoMode bool `protobuf:"varint,3,opt,name=auto_mode,json=autoMode,proto3" json:"auto_mode,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *TaskList) Reset() {
|
func (x *TaskList) Reset() {
|
||||||
|
@ -385,6 +386,13 @@ func (x *TaskList) GetNow() *Timestamp {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *TaskList) GetAutoMode() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.AutoMode
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type Job struct {
|
type Job struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
|
@ -659,89 +667,91 @@ var file_proto_tsgrain_proto_rawDesc = []byte{
|
||||||
0x12, 0x37, 0x0a, 0x11, 0x64, 0x61, 0x74, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x66, 0x69, 0x6e,
|
0x12, 0x37, 0x0a, 0x11, 0x64, 0x61, 0x74, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x66, 0x69, 0x6e,
|
||||||
0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x54, 0x69,
|
0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x54, 0x69,
|
||||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x64, 0x61, 0x74, 0x65, 0x74, 0x69, 0x6d,
|
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x64, 0x61, 0x74, 0x65, 0x74, 0x69, 0x6d,
|
||||||
0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x22, 0x45, 0x0a, 0x08, 0x54, 0x61, 0x73,
|
0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x22, 0x62, 0x0a, 0x08, 0x54, 0x61, 0x73,
|
||||||
0x6b, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x01,
|
0x6b, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x01,
|
||||||
0x20, 0x03, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x05, 0x74, 0x61, 0x73,
|
0x20, 0x03, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x05, 0x74, 0x61, 0x73,
|
||||||
0x6b, 0x73, 0x12, 0x1c, 0x0a, 0x03, 0x6e, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
0x6b, 0x73, 0x12, 0x1c, 0x0a, 0x03, 0x6e, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||||
0x0a, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x6e, 0x6f, 0x77,
|
0x0a, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x03, 0x6e, 0x6f, 0x77,
|
||||||
0x22, 0x97, 0x01, 0x0a, 0x03, 0x4a, 0x6f, 0x62, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
|
0x12, 0x1b, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20,
|
||||||
0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1e, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65,
|
0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x75, 0x74, 0x6f, 0x4d, 0x6f, 0x64, 0x65, 0x22, 0x97, 0x01,
|
||||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
|
0x0a, 0x03, 0x4a, 0x6f, 0x62, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
0x6d, 0x70, 0x52, 0x04, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61,
|
0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1e, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20,
|
||||||
0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61,
|
0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
|
||||||
0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x7a, 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x04, 0x20,
|
0x04, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f,
|
||||||
0x03, 0x28, 0x05, 0x52, 0x05, 0x7a, 0x6f, 0x6e, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e,
|
0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f,
|
||||||
0x61, 0x62, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x65, 0x6e, 0x61, 0x62,
|
0x6e, 0x12, 0x14, 0x0a, 0x05, 0x7a, 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x05,
|
||||||
0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01,
|
0x52, 0x05, 0x7a, 0x6f, 0x6e, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x61, 0x62, 0x6c,
|
||||||
0x28, 0x08, 0x52, 0x06, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x22, 0x17, 0x0a, 0x05, 0x4a, 0x6f,
|
0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12,
|
||||||
0x62, 0x49, 0x44, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52,
|
0x16, 0x0a, 0x06, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52,
|
||||||
0x02, 0x69, 0x64, 0x22, 0x23, 0x0a, 0x07, 0x4a, 0x6f, 0x62, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x18,
|
0x06, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x22, 0x17, 0x0a, 0x05, 0x4a, 0x6f, 0x62, 0x49, 0x44,
|
||||||
0x0a, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x4a,
|
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64,
|
||||||
0x6f, 0x62, 0x52, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x22, 0x50, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x66,
|
0x22, 0x23, 0x0a, 0x07, 0x4a, 0x6f, 0x62, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x04, 0x6a,
|
||||||
0x69, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x65, 0x74, 0x69,
|
0x6f, 0x62, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x4a, 0x6f, 0x62, 0x52,
|
||||||
0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
|
0x04, 0x6a, 0x6f, 0x62, 0x73, 0x22, 0x50, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x54,
|
||||||
0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x64, 0x61, 0x74, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x1a,
|
0x69, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x18,
|
||||||
0x0a, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
|
||||||
0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x2a, 0x26, 0x0a, 0x0a, 0x54, 0x61,
|
0x70, 0x52, 0x08, 0x64, 0x61, 0x74, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74,
|
||||||
0x73, 0x6b, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x41, 0x4e, 0x55,
|
0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74,
|
||||||
0x41, 0x4c, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x43, 0x48, 0x45, 0x44, 0x55, 0x4c, 0x45,
|
0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x2a, 0x26, 0x0a, 0x0a, 0x54, 0x61, 0x73, 0x6b, 0x53,
|
||||||
0x10, 0x01, 0x32, 0xc7, 0x06, 0x0a, 0x07, 0x54, 0x53, 0x47, 0x52, 0x61, 0x69, 0x6e, 0x12, 0x2f,
|
0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x41, 0x4e, 0x55, 0x41, 0x4c, 0x10,
|
||||||
0x0a, 0x09, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x0c, 0x2e, 0x54, 0x61,
|
0x00, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x43, 0x48, 0x45, 0x44, 0x55, 0x4c, 0x45, 0x10, 0x01, 0x32,
|
||||||
0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x54, 0x61, 0x73, 0x6b,
|
0xc7, 0x06, 0x0a, 0x07, 0x54, 0x53, 0x47, 0x52, 0x61, 0x69, 0x6e, 0x12, 0x2f, 0x0a, 0x09, 0x53,
|
||||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x12,
|
0x74, 0x61, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x0c, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52,
|
||||||
0x2f, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f,
|
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71,
|
||||||
|
0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x08,
|
||||||
|
0x47, 0x65, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
||||||
|
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
||||||
|
0x1a, 0x09, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x34, 0x0a,
|
||||||
|
0x0b, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x16, 0x2e, 0x67,
|
||||||
|
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45,
|
||||||
|
0x6d, 0x70, 0x74, 0x79, 0x1a, 0x09, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x22,
|
||||||
|
0x00, 0x30, 0x01, 0x12, 0x1b, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62,
|
||||||
|
0x12, 0x04, 0x2e, 0x4a, 0x6f, 0x62, 0x1a, 0x06, 0x2e, 0x4a, 0x6f, 0x62, 0x49, 0x44, 0x22, 0x00,
|
||||||
|
0x12, 0x18, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x4a, 0x6f, 0x62, 0x12, 0x06, 0x2e, 0x4a, 0x6f, 0x62,
|
||||||
|
0x49, 0x44, 0x1a, 0x04, 0x2e, 0x4a, 0x6f, 0x62, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x07, 0x47, 0x65,
|
||||||
|
0x74, 0x4a, 0x6f, 0x62, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||||
|
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x08, 0x2e,
|
||||||
|
0x4a, 0x6f, 0x62, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x2b, 0x0a, 0x09, 0x55, 0x70, 0x64,
|
||||||
|
0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x04, 0x2e, 0x4a, 0x6f, 0x62, 0x1a, 0x16, 0x2e, 0x67,
|
||||||
|
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45,
|
||||||
|
0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x09, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,
|
||||||
|
0x4a, 0x6f, 0x62, 0x12, 0x06, 0x2e, 0x4a, 0x6f, 0x62, 0x49, 0x44, 0x1a, 0x16, 0x2e, 0x67, 0x6f,
|
||||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
|
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
|
||||||
0x70, 0x74, 0x79, 0x1a, 0x09, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00,
|
0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x41, 0x75, 0x74, 0x6f,
|
||||||
0x12, 0x34, 0x0a, 0x0b, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12,
|
0x4d, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
|
||||||
0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67,
|
||||||
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x09, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x4c, 0x69,
|
|
||||||
0x73, 0x74, 0x22, 0x00, 0x30, 0x01, 0x12, 0x1b, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
|
|
||||||
0x4a, 0x6f, 0x62, 0x12, 0x04, 0x2e, 0x4a, 0x6f, 0x62, 0x1a, 0x06, 0x2e, 0x4a, 0x6f, 0x62, 0x49,
|
|
||||||
0x44, 0x22, 0x00, 0x12, 0x18, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x4a, 0x6f, 0x62, 0x12, 0x06, 0x2e,
|
|
||||||
0x4a, 0x6f, 0x62, 0x49, 0x44, 0x1a, 0x04, 0x2e, 0x4a, 0x6f, 0x62, 0x22, 0x00, 0x12, 0x2d, 0x0a,
|
|
||||||
0x07, 0x47, 0x65, 0x74, 0x4a, 0x6f, 0x62, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
|
||||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
|
||||||
0x1a, 0x08, 0x2e, 0x4a, 0x6f, 0x62, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x2b, 0x0a, 0x09,
|
|
||||||
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x04, 0x2e, 0x4a, 0x6f, 0x62, 0x1a,
|
|
||||||
0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
|
||||||
0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x09, 0x44, 0x65, 0x6c,
|
|
||||||
0x65, 0x74, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x06, 0x2e, 0x4a, 0x6f, 0x62, 0x49, 0x44, 0x1a, 0x16,
|
|
||||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
|
||||||
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x41,
|
|
||||||
0x75, 0x74, 0x6f, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
|
|
||||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a,
|
|
||||||
0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
|
|
||||||
0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x12, 0x43, 0x0a,
|
|
||||||
0x0b, 0x53, 0x65, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x2e, 0x67,
|
|
||||||
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42,
|
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42,
|
||||||
0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0b, 0x53, 0x65,
|
||||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
0x74, 0x41, 0x75, 0x74, 0x6f, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
|
||||||
0x22, 0x00, 0x12, 0x36, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x54,
|
0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c,
|
||||||
0x69, 0x6d, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||||
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e, 0x43, 0x6f,
|
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12,
|
||||||
0x6e, 0x66, 0x69, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x0d, 0x53, 0x65,
|
0x36, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x54, 0x69, 0x6d, 0x65,
|
||||||
0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x0b, 0x2e, 0x43, 0x6f,
|
0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||||
0x6e, 0x66, 0x69, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
|
0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0b, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
||||||
0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
0x67, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x43, 0x6f,
|
||||||
0x22, 0x00, 0x12, 0x51, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74,
|
0x6e, 0x66, 0x69, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x0b, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69,
|
||||||
0x49, 0x72, 0x72, 0x69, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x16,
|
0x67, 0x54, 0x69, 0x6d, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||||
|
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12,
|
||||||
|
0x51, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x49, 0x72, 0x72,
|
||||||
|
0x69, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f,
|
||||||
|
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
|
||||||
|
0x70, 0x74, 0x79, 0x1a, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
|
||||||
|
0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65,
|
||||||
|
0x22, 0x00, 0x12, 0x51, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74,
|
||||||
|
0x49, 0x72, 0x72, 0x69, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1b,
|
||||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
||||||
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f,
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61,
|
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
|
||||||
0x6c, 0x75, 0x65, 0x22, 0x00, 0x12, 0x51, 0x0a, 0x18, 0x53, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61,
|
0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x4e, 0x5a, 0x6f, 0x6e,
|
||||||
0x75, 0x6c, 0x74, 0x49, 0x72, 0x72, 0x69, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d,
|
0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||||
0x65, 0x12, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1b, 0x2e, 0x67, 0x6f, 0x6f,
|
||||||
0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16,
|
0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74,
|
||||||
0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
|
0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x42, 0x30, 0x5a, 0x2e, 0x63, 0x6f, 0x64,
|
||||||
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x42, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x4e,
|
0x65, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x61, 0x64, 0x65, 0x76, 0x2e, 0x64, 0x65, 0x2f, 0x54, 0x53,
|
||||||
0x5a, 0x6f, 0x6e, 0x65, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
0x47, 0x52, 0x61, 0x69, 0x6e, 0x2f, 0x57, 0x65, 0x62, 0x55, 0x49, 0x2f, 0x73, 0x72, 0x63, 0x2f,
|
||||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1b, 0x2e,
|
0x74, 0x73, 0x67, 0x72, 0x61, 0x69, 0x6e, 0x5f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f,
|
||||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
0x74, 0x6f, 0x33,
|
||||||
0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x42, 0x30, 0x5a, 0x2e,
|
|
||||||
0x63, 0x6f, 0x64, 0x65, 0x2e, 0x74, 0x68, 0x65, 0x74, 0x61, 0x64, 0x65, 0x76, 0x2e, 0x64, 0x65,
|
|
||||||
0x2f, 0x54, 0x53, 0x47, 0x52, 0x61, 0x69, 0x6e, 0x2f, 0x57, 0x65, 0x62, 0x55, 0x49, 0x2f, 0x73,
|
|
||||||
0x72, 0x63, 0x2f, 0x74, 0x73, 0x67, 0x72, 0x61, 0x69, 0x6e, 0x5f, 0x72, 0x70, 0x63, 0x62, 0x06,
|
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -7,12 +7,14 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.thetadev.de/TSGRain/WebUI/src/model"
|
||||||
tsgrain_grpc "code.thetadev.de/TSGRain/WebUI/src/tsgrain_rpc/proto"
|
tsgrain_grpc "code.thetadev.de/TSGRain/WebUI/src/tsgrain_rpc/proto"
|
||||||
"code.thetadev.de/TSGRain/WebUI/src/util"
|
"code.thetadev.de/TSGRain/WebUI/src/util"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
|
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RPCClient struct {
|
type RPCClient struct {
|
||||||
|
@ -50,6 +52,78 @@ func (c *RPCClient) Stop() error {
|
||||||
return c.conn.Close()
|
return c.conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mapTask(pbTask *tsgrain_grpc.Task) model.Task {
|
||||||
|
task := model.Task{
|
||||||
|
Source: int32(pbTask.Source),
|
||||||
|
ZoneId: pbTask.ZoneId,
|
||||||
|
Duration: pbTask.Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
if pbTask.DatetimeStarted != nil {
|
||||||
|
task.DatetimeStarted = &pbTask.DatetimeStarted.Seconds
|
||||||
|
}
|
||||||
|
if pbTask.DatetimeFinished != nil {
|
||||||
|
task.DatetimeFinished = &pbTask.DatetimeFinished.Seconds
|
||||||
|
}
|
||||||
|
return task
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapTaskList(pbTaskList *tsgrain_grpc.TaskList) model.TaskList {
|
||||||
|
taskList := model.TaskList{
|
||||||
|
Now: pbTaskList.Now.Seconds,
|
||||||
|
Tasks: []model.Task{},
|
||||||
|
AutoMode: pbTaskList.AutoMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pbTask := range pbTaskList.Tasks {
|
||||||
|
if pbTask != nil {
|
||||||
|
taskList.Tasks = append(taskList.Tasks, mapTask(pbTask))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return taskList
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) StartManualTask(
|
||||||
|
zone_id int32, duration int32, queuing bool, cancelling bool) (
|
||||||
|
model.TaskRequestResult, error) {
|
||||||
|
res, err := c.tsgrain.StartTask(c.ctx, &tsgrain_grpc.TaskRequest{
|
||||||
|
Source: tsgrain_grpc.TaskSource_MANUAL,
|
||||||
|
ZoneId: zone_id,
|
||||||
|
Duration: duration,
|
||||||
|
Queuing: queuing,
|
||||||
|
Cancelling: cancelling,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return model.TaskRequestResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return model.TaskRequestResult{
|
||||||
|
Started: res.Started,
|
||||||
|
Stopped: res.Stopped,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) GetTasks() (model.TaskList, error) {
|
||||||
|
pbTaskList, err := c.tsgrain.GetTasks(c.ctx, &emptypb.Empty{})
|
||||||
|
if err != nil {
|
||||||
|
return model.TaskList{}, err
|
||||||
|
}
|
||||||
|
return mapTaskList(pbTaskList), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func broadcastTaskList(bc util.Broadcaster, pbTaskList *tsgrain_grpc.TaskList) error {
|
||||||
|
taskList := mapTaskList(pbTaskList)
|
||||||
|
|
||||||
|
taskListJson, err := json.Marshal(taskList)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
bc.Broadcast(taskListJson)
|
||||||
|
log.Debug().RawJSON("task_list", taskListJson).Msg("TaskList received")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *RPCClient) streamTasks(bc util.Broadcaster) error {
|
func (c *RPCClient) streamTasks(bc util.Broadcaster) error {
|
||||||
stream, err := c.tsgrain.StreamTasks(c.ctx, &emptypb.Empty{})
|
stream, err := c.tsgrain.StreamTasks(c.ctx, &emptypb.Empty{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -57,25 +131,25 @@ func (c *RPCClient) streamTasks(bc util.Broadcaster) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
taskList, err := stream.Recv()
|
pbTaskList, err := stream.Recv()
|
||||||
if errors.Is(err, io.EOF) {
|
if errors.Is(err, io.EOF) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
taskListJson, err := json.Marshal(taskList)
|
_ = broadcastTaskList(bc, pbTaskList)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
bc.Broadcast(taskListJson)
|
|
||||||
log.Debug().RawJSON("task_list", taskListJson).Msg("TaskList received")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RPCClient) StreamTasks(bc util.Broadcaster) {
|
func (c *RPCClient) StreamTasks(bc util.Broadcaster) {
|
||||||
|
// Get initial state
|
||||||
|
pbTaskList, err := c.tsgrain.GetTasks(c.ctx, &emptypb.Empty{})
|
||||||
|
if err == nil {
|
||||||
|
_ = broadcastTaskList(bc, pbTaskList)
|
||||||
|
}
|
||||||
|
|
||||||
// Keep stream running if it errors
|
// Keep stream running if it errors
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
@ -90,3 +164,142 @@ func (c *RPCClient) StreamTasks(bc util.Broadcaster) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) CreateJob(job model.NewJob) (int32, error) {
|
||||||
|
pbJob := &tsgrain_grpc.Job{
|
||||||
|
Date: &tsgrain_grpc.Timestamp{Seconds: job.Date},
|
||||||
|
Duration: job.Duration,
|
||||||
|
Zones: job.Zones,
|
||||||
|
Enable: job.Enable,
|
||||||
|
Repeat: job.Repeat,
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.tsgrain.CreateJob(c.ctx, pbJob)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.Id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) GetJobs() (model.JobList, error) {
|
||||||
|
jobs := []model.Job{}
|
||||||
|
|
||||||
|
res, err := c.tsgrain.GetJobs(c.ctx, &emptypb.Empty{})
|
||||||
|
if err != nil {
|
||||||
|
return jobs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pbJob := range res.Jobs {
|
||||||
|
job := model.Job{
|
||||||
|
Id: pbJob.Id,
|
||||||
|
Date: pbJob.Date.Seconds,
|
||||||
|
Duration: pbJob.Duration,
|
||||||
|
Zones: pbJob.Zones,
|
||||||
|
Enable: pbJob.Enable,
|
||||||
|
Repeat: pbJob.Repeat,
|
||||||
|
}
|
||||||
|
jobs = append(jobs, job)
|
||||||
|
}
|
||||||
|
|
||||||
|
return jobs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) GetJob(id int32) (model.Job, error) {
|
||||||
|
res, err := c.tsgrain.GetJob(c.ctx, &tsgrain_grpc.JobID{Id: id})
|
||||||
|
if err != nil {
|
||||||
|
return model.Job{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
job := model.Job{
|
||||||
|
Id: res.Id,
|
||||||
|
Date: res.Date.Seconds,
|
||||||
|
Duration: res.Duration,
|
||||||
|
Zones: res.Zones,
|
||||||
|
Enable: res.Enable,
|
||||||
|
Repeat: res.Repeat,
|
||||||
|
}
|
||||||
|
return job, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) UpdateJob(job model.Job) error {
|
||||||
|
pbJob := tsgrain_grpc.Job{
|
||||||
|
Id: job.Id,
|
||||||
|
Date: &tsgrain_grpc.Timestamp{Seconds: job.Date},
|
||||||
|
Duration: job.Duration,
|
||||||
|
Zones: job.Zones,
|
||||||
|
Enable: job.Enable,
|
||||||
|
Repeat: job.Repeat,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := c.tsgrain.UpdateJob(c.ctx, &pbJob)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) DeleteJob(job_id int32) error {
|
||||||
|
_, err := c.tsgrain.DeleteJob(c.ctx, &tsgrain_grpc.JobID{Id: job_id})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) GetAutoMode() (bool, error) {
|
||||||
|
res, err := c.tsgrain.GetAutoMode(c.ctx, &emptypb.Empty{})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.Value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) SetAutoMode(state bool) error {
|
||||||
|
_, err := c.tsgrain.SetAutoMode(c.ctx, wrapperspb.Bool(state))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) GetConfigTime() (model.ConfigTime, error) {
|
||||||
|
configTime, err := c.tsgrain.GetConfigTime(c.ctx, &emptypb.Empty{})
|
||||||
|
if err != nil {
|
||||||
|
return model.ConfigTime{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return model.ConfigTime{
|
||||||
|
Time: configTime.Datetime.Seconds,
|
||||||
|
Timezone: configTime.Timezone,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) SetConfigTime(configTime model.ConfigTime) error {
|
||||||
|
_, err := c.tsgrain.SetConfigTime(c.ctx, &tsgrain_grpc.ConfigTime{
|
||||||
|
Datetime: &tsgrain_grpc.Timestamp{Seconds: configTime.Time},
|
||||||
|
Timezone: configTime.Timezone,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) SetSystemTimezone(timezone string) error {
|
||||||
|
_, err := c.tsgrain.SetConfigTime(c.ctx, &tsgrain_grpc.ConfigTime{
|
||||||
|
Timezone: timezone,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) GetDefaultIrrigationTime() (int32, error) {
|
||||||
|
res, err := c.tsgrain.GetDefaultIrrigationTime(c.ctx, &emptypb.Empty{})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.Value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) SetDefaultIrrigationTime(defaultTime int32) error {
|
||||||
|
_, err := c.tsgrain.SetDefaultIrrigationTime(c.ctx, wrapperspb.Int32(defaultTime))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClient) GetNZones() (int32, error) {
|
||||||
|
val, err := c.tsgrain.GetNZones(c.ctx, &emptypb.Empty{})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return val.Value, nil
|
||||||
|
}
|
||||||
|
|
1
ui/.env.development
Normal file
1
ui/.env.development
Normal file
|
@ -0,0 +1 @@
|
||||||
|
VITE_API_HOST=127.0.0.1:8001
|
5
ui/.gitignore
vendored
Normal file
5
ui/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
4
ui/.prettierignore
Normal file
4
ui/.prettierignore
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
tmp
|
||||||
|
.tmp
|
17
ui/index.html
Normal file
17
ui/index.html
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/src/assets/favicon.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>TSGRain</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You have to enable JavaScript to use TSGRain.</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script>
|
||||||
|
window.config = "%CONFIG%"
|
||||||
|
</script>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
24
ui/package.json
Normal file
24
ui/package.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"name": "ui",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"serve": "vite preview",
|
||||||
|
"lint": "tsc",
|
||||||
|
"format": "prettier --write ../"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@mdi/js": "^6.5.95",
|
||||||
|
"axios": "^0.24.0",
|
||||||
|
"preact": "^10.5.15"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@preact/preset-vite": "^2.1.5",
|
||||||
|
"prettier": "^2.4.1",
|
||||||
|
"sass": "^1.43.4",
|
||||||
|
"typescript": "^4.5.2",
|
||||||
|
"vite": "^2.6.14",
|
||||||
|
"@babel/core": ">=7.12.10 <8.0.0"
|
||||||
|
}
|
||||||
|
}
|
1242
ui/pnpm-lock.yaml
Normal file
1242
ui/pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load diff
10
ui/src/components/app.tsx
Normal file
10
ui/src/components/app.tsx
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import {Component} from "preact"
|
||||||
|
// import UpdaterView from "./Updater/UpdaterView"
|
||||||
|
// import logo from "../assets/logo.svg"
|
||||||
|
import {getConfig} from "../util/config"
|
||||||
|
|
||||||
|
export default class App extends Component {
|
||||||
|
render() {
|
||||||
|
return <div>{getConfig().version}</div>
|
||||||
|
}
|
||||||
|
}
|
5
ui/src/main.tsx
Normal file
5
ui/src/main.tsx
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import {render} from "preact"
|
||||||
|
import App from "./components/app"
|
||||||
|
import "./style/index.scss"
|
||||||
|
|
||||||
|
render(<App />, document.getElementById("app")!)
|
1
ui/src/preact.d.ts
vendored
Normal file
1
ui/src/preact.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
import JSX = preact.JSX
|
0
ui/src/style/index.scss
Normal file
0
ui/src/style/index.scss
Normal file
4
ui/src/tsgrain-client/.gitignore
vendored
Normal file
4
ui/src/tsgrain-client/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
wwwroot/*.js
|
||||||
|
node_modules
|
||||||
|
typings
|
||||||
|
dist
|
1
ui/src/tsgrain-client/.npmignore
Normal file
1
ui/src/tsgrain-client/.npmignore
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# empty npmignore to ensure all required files (e.g., in the dist folder) are published by npm
|
23
ui/src/tsgrain-client/.openapi-generator-ignore
Normal file
23
ui/src/tsgrain-client/.openapi-generator-ignore
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# OpenAPI Generator Ignore
|
||||||
|
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
|
||||||
|
|
||||||
|
# Use this file to prevent files from being overwritten by the generator.
|
||||||
|
# The patterns follow closely to .gitignore or .dockerignore.
|
||||||
|
|
||||||
|
# As an example, the C# client generator defines ApiClient.cs.
|
||||||
|
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
|
||||||
|
#ApiClient.cs
|
||||||
|
|
||||||
|
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
|
||||||
|
#foo/*/qux
|
||||||
|
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
|
||||||
|
|
||||||
|
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
|
||||||
|
#foo/**/qux
|
||||||
|
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
|
||||||
|
|
||||||
|
# You can also negate patterns with an exclamation (!).
|
||||||
|
# For example, you can ignore all files in a docs folder with the file extension .md:
|
||||||
|
#docs/*.md
|
||||||
|
# Then explicitly reverse the ignore rule for a single file:
|
||||||
|
#!docs/README.md
|
9
ui/src/tsgrain-client/.openapi-generator/FILES
Normal file
9
ui/src/tsgrain-client/.openapi-generator/FILES
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
.gitignore
|
||||||
|
.npmignore
|
||||||
|
.openapi-generator-ignore
|
||||||
|
api.ts
|
||||||
|
base.ts
|
||||||
|
common.ts
|
||||||
|
configuration.ts
|
||||||
|
git_push.sh
|
||||||
|
index.ts
|
1
ui/src/tsgrain-client/.openapi-generator/VERSION
Normal file
1
ui/src/tsgrain-client/.openapi-generator/VERSION
Normal file
|
@ -0,0 +1 @@
|
||||||
|
5.3.1
|
1455
ui/src/tsgrain-client/api.ts
Normal file
1455
ui/src/tsgrain-client/api.ts
Normal file
File diff suppressed because it is too large
Load diff
74
ui/src/tsgrain-client/base.ts
Normal file
74
ui/src/tsgrain-client/base.ts
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* TSGRain WebUI
|
||||||
|
* REST API for the TSGRain WebUI
|
||||||
|
*
|
||||||
|
* The version of the OpenAPI document: 0.1.0
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
* https://openapi-generator.tech
|
||||||
|
* Do not edit the class manually.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Configuration} from "./configuration"
|
||||||
|
// Some imports not used depending on template conditions
|
||||||
|
// @ts-ignore
|
||||||
|
import globalAxios, {AxiosPromise, AxiosInstance, AxiosRequestConfig} from "axios"
|
||||||
|
|
||||||
|
export const BASE_PATH = "http://localhost".replace(/\/+$/, "")
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const COLLECTION_FORMATS = {
|
||||||
|
csv: ",",
|
||||||
|
ssv: " ",
|
||||||
|
tsv: "\t",
|
||||||
|
pipes: "|",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface RequestArgs
|
||||||
|
*/
|
||||||
|
export interface RequestArgs {
|
||||||
|
url: string
|
||||||
|
options: AxiosRequestConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class BaseAPI
|
||||||
|
*/
|
||||||
|
export class BaseAPI {
|
||||||
|
protected configuration: Configuration | undefined
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
configuration?: Configuration,
|
||||||
|
protected basePath: string = BASE_PATH,
|
||||||
|
protected axios: AxiosInstance = globalAxios
|
||||||
|
) {
|
||||||
|
if (configuration) {
|
||||||
|
this.configuration = configuration
|
||||||
|
this.basePath = configuration.basePath || this.basePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class RequiredError
|
||||||
|
* @extends {Error}
|
||||||
|
*/
|
||||||
|
export class RequiredError extends Error {
|
||||||
|
name: "RequiredError" = "RequiredError"
|
||||||
|
constructor(public field: string, msg?: string) {
|
||||||
|
super(msg)
|
||||||
|
}
|
||||||
|
}
|
181
ui/src/tsgrain-client/common.ts
Normal file
181
ui/src/tsgrain-client/common.ts
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* TSGRain WebUI
|
||||||
|
* REST API for the TSGRain WebUI
|
||||||
|
*
|
||||||
|
* The version of the OpenAPI document: 0.1.0
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
* https://openapi-generator.tech
|
||||||
|
* Do not edit the class manually.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Configuration} from "./configuration"
|
||||||
|
import {RequiredError, RequestArgs} from "./base"
|
||||||
|
import {AxiosInstance, AxiosResponse} from "axios"
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const DUMMY_BASE_URL = "https://example.com"
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const assertParamExists = function (
|
||||||
|
functionName: string,
|
||||||
|
paramName: string,
|
||||||
|
paramValue: unknown
|
||||||
|
) {
|
||||||
|
if (paramValue === null || paramValue === undefined) {
|
||||||
|
throw new RequiredError(
|
||||||
|
paramName,
|
||||||
|
`Required parameter ${paramName} was null or undefined when calling ${functionName}.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const setApiKeyToObject = async function (
|
||||||
|
object: any,
|
||||||
|
keyParamName: string,
|
||||||
|
configuration?: Configuration
|
||||||
|
) {
|
||||||
|
if (configuration && configuration.apiKey) {
|
||||||
|
const localVarApiKeyValue =
|
||||||
|
typeof configuration.apiKey === "function"
|
||||||
|
? await configuration.apiKey(keyParamName)
|
||||||
|
: await configuration.apiKey
|
||||||
|
object[keyParamName] = localVarApiKeyValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const setBasicAuthToObject = function (
|
||||||
|
object: any,
|
||||||
|
configuration?: Configuration
|
||||||
|
) {
|
||||||
|
if (configuration && (configuration.username || configuration.password)) {
|
||||||
|
object["auth"] = {
|
||||||
|
username: configuration.username,
|
||||||
|
password: configuration.password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const setBearerAuthToObject = async function (
|
||||||
|
object: any,
|
||||||
|
configuration?: Configuration
|
||||||
|
) {
|
||||||
|
if (configuration && configuration.accessToken) {
|
||||||
|
const accessToken =
|
||||||
|
typeof configuration.accessToken === "function"
|
||||||
|
? await configuration.accessToken()
|
||||||
|
: await configuration.accessToken
|
||||||
|
object["Authorization"] = "Bearer " + accessToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const setOAuthToObject = async function (
|
||||||
|
object: any,
|
||||||
|
name: string,
|
||||||
|
scopes: string[],
|
||||||
|
configuration?: Configuration
|
||||||
|
) {
|
||||||
|
if (configuration && configuration.accessToken) {
|
||||||
|
const localVarAccessTokenValue =
|
||||||
|
typeof configuration.accessToken === "function"
|
||||||
|
? await configuration.accessToken(name, scopes)
|
||||||
|
: await configuration.accessToken
|
||||||
|
object["Authorization"] = "Bearer " + localVarAccessTokenValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const setSearchParams = function (url: URL, ...objects: any[]) {
|
||||||
|
const searchParams = new URLSearchParams(url.search)
|
||||||
|
for (const object of objects) {
|
||||||
|
for (const key in object) {
|
||||||
|
if (Array.isArray(object[key])) {
|
||||||
|
searchParams.delete(key)
|
||||||
|
for (const item of object[key]) {
|
||||||
|
searchParams.append(key, item)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
searchParams.set(key, object[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
url.search = searchParams.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const serializeDataIfNeeded = function (
|
||||||
|
value: any,
|
||||||
|
requestOptions: any,
|
||||||
|
configuration?: Configuration
|
||||||
|
) {
|
||||||
|
const nonString = typeof value !== "string"
|
||||||
|
const needsSerialization =
|
||||||
|
nonString && configuration && configuration.isJsonMime
|
||||||
|
? configuration.isJsonMime(requestOptions.headers["Content-Type"])
|
||||||
|
: nonString
|
||||||
|
return needsSerialization
|
||||||
|
? JSON.stringify(value !== undefined ? value : {})
|
||||||
|
: value || ""
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const toPathString = function (url: URL) {
|
||||||
|
return url.pathname + url.search + url.hash
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
*/
|
||||||
|
export const createRequestFunction = function (
|
||||||
|
axiosArgs: RequestArgs,
|
||||||
|
globalAxios: AxiosInstance,
|
||||||
|
BASE_PATH: string,
|
||||||
|
configuration?: Configuration
|
||||||
|
) {
|
||||||
|
return <T = unknown, R = AxiosResponse<T>>(
|
||||||
|
axios: AxiosInstance = globalAxios,
|
||||||
|
basePath: string = BASE_PATH
|
||||||
|
) => {
|
||||||
|
const axiosRequestArgs = {
|
||||||
|
...axiosArgs.options,
|
||||||
|
url: (configuration?.basePath || basePath) + axiosArgs.url,
|
||||||
|
}
|
||||||
|
return axios.request<T, R>(axiosRequestArgs)
|
||||||
|
}
|
||||||
|
}
|
123
ui/src/tsgrain-client/configuration.ts
Normal file
123
ui/src/tsgrain-client/configuration.ts
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* TSGRain WebUI
|
||||||
|
* REST API for the TSGRain WebUI
|
||||||
|
*
|
||||||
|
* The version of the OpenAPI document: 0.1.0
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
* https://openapi-generator.tech
|
||||||
|
* Do not edit the class manually.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ConfigurationParameters {
|
||||||
|
apiKey?:
|
||||||
|
| string
|
||||||
|
| Promise<string>
|
||||||
|
| ((name: string) => string)
|
||||||
|
| ((name: string) => Promise<string>)
|
||||||
|
username?: string
|
||||||
|
password?: string
|
||||||
|
accessToken?:
|
||||||
|
| string
|
||||||
|
| Promise<string>
|
||||||
|
| ((name?: string, scopes?: string[]) => string)
|
||||||
|
| ((name?: string, scopes?: string[]) => Promise<string>)
|
||||||
|
basePath?: string
|
||||||
|
baseOptions?: any
|
||||||
|
formDataCtor?: new () => any
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Configuration {
|
||||||
|
/**
|
||||||
|
* parameter for apiKey security
|
||||||
|
* @param name security name
|
||||||
|
* @memberof Configuration
|
||||||
|
*/
|
||||||
|
apiKey?:
|
||||||
|
| string
|
||||||
|
| Promise<string>
|
||||||
|
| ((name: string) => string)
|
||||||
|
| ((name: string) => Promise<string>)
|
||||||
|
/**
|
||||||
|
* parameter for basic security
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof Configuration
|
||||||
|
*/
|
||||||
|
username?: string
|
||||||
|
/**
|
||||||
|
* parameter for basic security
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof Configuration
|
||||||
|
*/
|
||||||
|
password?: string
|
||||||
|
/**
|
||||||
|
* parameter for oauth2 security
|
||||||
|
* @param name security name
|
||||||
|
* @param scopes oauth2 scope
|
||||||
|
* @memberof Configuration
|
||||||
|
*/
|
||||||
|
accessToken?:
|
||||||
|
| string
|
||||||
|
| Promise<string>
|
||||||
|
| ((name?: string, scopes?: string[]) => string)
|
||||||
|
| ((name?: string, scopes?: string[]) => Promise<string>)
|
||||||
|
/**
|
||||||
|
* override base path
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof Configuration
|
||||||
|
*/
|
||||||
|
basePath?: string
|
||||||
|
/**
|
||||||
|
* base options for axios calls
|
||||||
|
*
|
||||||
|
* @type {any}
|
||||||
|
* @memberof Configuration
|
||||||
|
*/
|
||||||
|
baseOptions?: any
|
||||||
|
/**
|
||||||
|
* The FormData constructor that will be used to create multipart form data
|
||||||
|
* requests. You can inject this here so that execution environments that
|
||||||
|
* do not support the FormData class can still run the generated client.
|
||||||
|
*
|
||||||
|
* @type {new () => FormData}
|
||||||
|
*/
|
||||||
|
formDataCtor?: new () => any
|
||||||
|
|
||||||
|
constructor(param: ConfigurationParameters = {}) {
|
||||||
|
this.apiKey = param.apiKey
|
||||||
|
this.username = param.username
|
||||||
|
this.password = param.password
|
||||||
|
this.accessToken = param.accessToken
|
||||||
|
this.basePath = param.basePath
|
||||||
|
this.baseOptions = param.baseOptions
|
||||||
|
this.formDataCtor = param.formDataCtor
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given MIME is a JSON MIME.
|
||||||
|
* JSON MIME examples:
|
||||||
|
* application/json
|
||||||
|
* application/json; charset=UTF8
|
||||||
|
* APPLICATION/JSON
|
||||||
|
* application/vnd.company+json
|
||||||
|
* @param mime - MIME (Multipurpose Internet Mail Extensions)
|
||||||
|
* @return True if the given MIME is JSON, false otherwise.
|
||||||
|
*/
|
||||||
|
public isJsonMime(mime: string): boolean {
|
||||||
|
const jsonMime: RegExp = new RegExp(
|
||||||
|
"^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$",
|
||||||
|
"i"
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
mime !== null &&
|
||||||
|
(jsonMime.test(mime) ||
|
||||||
|
mime.toLowerCase() === "application/json-patch+json")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
57
ui/src/tsgrain-client/git_push.sh
Normal file
57
ui/src/tsgrain-client/git_push.sh
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/
|
||||||
|
#
|
||||||
|
# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com"
|
||||||
|
|
||||||
|
git_user_id=$1
|
||||||
|
git_repo_id=$2
|
||||||
|
release_note=$3
|
||||||
|
git_host=$4
|
||||||
|
|
||||||
|
if [ "$git_host" = "" ]; then
|
||||||
|
git_host="github.com"
|
||||||
|
echo "[INFO] No command line input provided. Set \$git_host to $git_host"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$git_user_id" = "" ]; then
|
||||||
|
git_user_id="GIT_USER_ID"
|
||||||
|
echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$git_repo_id" = "" ]; then
|
||||||
|
git_repo_id="GIT_REPO_ID"
|
||||||
|
echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$release_note" = "" ]; then
|
||||||
|
release_note="Minor update"
|
||||||
|
echo "[INFO] No command line input provided. Set \$release_note to $release_note"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Initialize the local directory as a Git repository
|
||||||
|
git init
|
||||||
|
|
||||||
|
# Adds the files in the local repository and stages them for commit.
|
||||||
|
git add .
|
||||||
|
|
||||||
|
# Commits the tracked changes and prepares them to be pushed to a remote repository.
|
||||||
|
git commit -m "$release_note"
|
||||||
|
|
||||||
|
# Sets the new remote
|
||||||
|
git_remote=$(git remote)
|
||||||
|
if [ "$git_remote" = "" ]; then # git remote not defined
|
||||||
|
|
||||||
|
if [ "$GIT_TOKEN" = "" ]; then
|
||||||
|
echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment."
|
||||||
|
git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git
|
||||||
|
else
|
||||||
|
git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
git pull origin master
|
||||||
|
|
||||||
|
# Pushes (Forces) the changes in the local repository up to the remote repository
|
||||||
|
echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git"
|
||||||
|
git push origin master 2>&1 | grep -v 'To https'
|
16
ui/src/tsgrain-client/index.ts
Normal file
16
ui/src/tsgrain-client/index.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* TSGRain WebUI
|
||||||
|
* REST API for the TSGRain WebUI
|
||||||
|
*
|
||||||
|
* The version of the OpenAPI document: 0.1.0
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
* https://openapi-generator.tech
|
||||||
|
* Do not edit the class manually.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from "./api"
|
||||||
|
export * from "./configuration"
|
20
ui/src/util/apiUrls.ts
Normal file
20
ui/src/util/apiUrls.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import {Configuration, DefaultApi} from "../tsgrain-client"
|
||||||
|
|
||||||
|
let apiHost = document.location.host
|
||||||
|
const httpProto = document.location.protocol
|
||||||
|
const wsProto = httpProto === "https:" ? "wss:" : "ws:"
|
||||||
|
|
||||||
|
if (import.meta.env.VITE_API_HOST !== undefined) {
|
||||||
|
apiHost = import.meta.env.VITE_API_HOST as string
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiUrl = `${httpProto}//${apiHost}/api`
|
||||||
|
const wsUrl = `${wsProto}//${apiHost}/api/ws`
|
||||||
|
|
||||||
|
let apicfg = new Configuration({
|
||||||
|
basePath: apiUrl,
|
||||||
|
})
|
||||||
|
|
||||||
|
const sebraucApi = new DefaultApi(apicfg)
|
||||||
|
|
||||||
|
export {apiUrl, wsUrl, sebraucApi}
|
27
ui/src/util/config.ts
Normal file
27
ui/src/util/config.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
export interface Config {
|
||||||
|
version: string
|
||||||
|
n_zones: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
config?: any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isConfig(object: any): object is Config {
|
||||||
|
return typeof object === "object" && "version" in object
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getConfig(): Config {
|
||||||
|
if (isConfig(window.config)) {
|
||||||
|
return window.config
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error("App config not found")
|
||||||
|
return {
|
||||||
|
version: "dev",
|
||||||
|
n_zones: 0,
|
||||||
|
}
|
||||||
|
}
|
18
ui/src/util/functions.ts
Normal file
18
ui/src/util/functions.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
function secondsToString(seconds: number): string {
|
||||||
|
const numyears = Math.floor(seconds / 31536000)
|
||||||
|
const numdays = Math.floor((seconds % 31536000) / 86400)
|
||||||
|
const numhours = Math.floor(((seconds % 31536000) % 86400) / 3600)
|
||||||
|
const numminutes = Math.floor((((seconds % 31536000) % 86400) % 3600) / 60)
|
||||||
|
const numseconds = (((seconds % 31536000) % 86400) % 3600) % 60
|
||||||
|
|
||||||
|
let res = []
|
||||||
|
if (numyears > 0) res.push(numyears + "yr")
|
||||||
|
if (numdays > 0) res.push(numdays + "d")
|
||||||
|
if (numhours > 0) res.push(numhours + "h")
|
||||||
|
if (numminutes > 0) res.push(numminutes + "m")
|
||||||
|
if (seconds < 60) res.push(numseconds + "s")
|
||||||
|
|
||||||
|
return res.join(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
export {secondsToString}
|
92
ui/src/util/websocket.ts
Normal file
92
ui/src/util/websocket.ts
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import {wsUrl} from "./apiUrls"
|
||||||
|
|
||||||
|
class WebsocketAPI {
|
||||||
|
private static ws: WebsocketAPI | undefined
|
||||||
|
|
||||||
|
private conn: WebSocket | undefined
|
||||||
|
private wsConnected: boolean
|
||||||
|
|
||||||
|
private clients: Set<WebsocketClient>
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
this.clients = new Set()
|
||||||
|
this.wsConnected = false
|
||||||
|
|
||||||
|
if (window.WebSocket) {
|
||||||
|
this.connect()
|
||||||
|
} else {
|
||||||
|
console.log("Your browser does not support WebSockets")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setStatus(wsConnected: boolean) {
|
||||||
|
if (wsConnected !== this.wsConnected) {
|
||||||
|
this.wsConnected = wsConnected
|
||||||
|
this.clients.forEach((client) => {
|
||||||
|
client.statusCallback(this.wsConnected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private connect() {
|
||||||
|
this.conn = new WebSocket(wsUrl)
|
||||||
|
this.conn.onopen = () => {
|
||||||
|
this.setStatus(true)
|
||||||
|
console.log("WS connected")
|
||||||
|
}
|
||||||
|
this.conn.onclose = () => {
|
||||||
|
this.setStatus(false)
|
||||||
|
console.log("WS connection closed")
|
||||||
|
window.setTimeout(() => this.connect(), 3000)
|
||||||
|
}
|
||||||
|
this.conn.onmessage = (evt) => {
|
||||||
|
this.clients.forEach((client) => {
|
||||||
|
client.msgCallback(evt)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Get(): WebsocketAPI {
|
||||||
|
if (this.ws === undefined) {
|
||||||
|
this.ws = new WebsocketAPI()
|
||||||
|
}
|
||||||
|
return this.ws
|
||||||
|
}
|
||||||
|
|
||||||
|
isConnected(): boolean {
|
||||||
|
return this.wsConnected
|
||||||
|
}
|
||||||
|
|
||||||
|
addClient(client: WebsocketClient) {
|
||||||
|
console.log("added client", client)
|
||||||
|
this.clients.add(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
removeClient(client: WebsocketClient) {
|
||||||
|
console.log("removed client", client)
|
||||||
|
this.clients.delete(client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class WebsocketClient {
|
||||||
|
statusCallback: (wsConnected: boolean) => void
|
||||||
|
msgCallback: (evt: MessageEvent) => void
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
statusCallback: (wsConnected: boolean) => void,
|
||||||
|
msgCallback: (evt: MessageEvent) => void
|
||||||
|
) {
|
||||||
|
this.statusCallback = statusCallback
|
||||||
|
this.msgCallback = msgCallback
|
||||||
|
|
||||||
|
this.api().addClient(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
api(): WebsocketAPI {
|
||||||
|
return WebsocketAPI.Get()
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.api().removeClient(this)
|
||||||
|
}
|
||||||
|
}
|
1
ui/src/vite-env.d.ts
vendored
Normal file
1
ui/src/vite-env.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/// <reference types="vite/client" />
|
21
ui/tsconfig.json
Normal file
21
ui/tsconfig.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||||
|
"allowJs": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": false,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"jsxFactory": "h",
|
||||||
|
"jsxFragmentFactory": "Fragment"
|
||||||
|
}
|
||||||
|
}
|
66
ui/ui.go
Normal file
66
ui/ui.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"embed"
|
||||||
|
"encoding/json"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"code.thetadev.de/TSGRain/WebUI/src/server/middleware"
|
||||||
|
"code.thetadev.de/TSGRain/WebUI/src/util"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
const distDir = "dist"
|
||||||
|
|
||||||
|
//go:embed dist/**
|
||||||
|
var assets embed.FS
|
||||||
|
|
||||||
|
type uiConfig struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
NZones int32 `json:"n_zones"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func subFS(fsys fs.FS, dir string) fs.FS {
|
||||||
|
sub, err := fs.Sub(fsys, dir)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
|
||||||
|
func distFS() fs.FS {
|
||||||
|
return subFS(assets, distDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Register(r gin.IRouter, nZones int32) {
|
||||||
|
indexHandler := getIndexHandler(nZones)
|
||||||
|
|
||||||
|
uiAssets := r.Group("/assets", middleware.Cache)
|
||||||
|
|
||||||
|
r.GET("/", indexHandler)
|
||||||
|
r.GET("/index.html", indexHandler)
|
||||||
|
|
||||||
|
uiAssets.StaticFS("/", http.FS(subFS(distFS(), "assets")))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIndexHandler(nZones int32) gin.HandlerFunc {
|
||||||
|
content, err := fs.ReadFile(distFS(), "index.html")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
uiConfigBytes, err := json.Marshal(uiConfig{
|
||||||
|
Version: util.Version(),
|
||||||
|
NZones: nZones,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
content = bytes.ReplaceAll(content, []byte("\"%CONFIG%\""), uiConfigBytes)
|
||||||
|
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
c.Data(200, "text/html", content)
|
||||||
|
}
|
||||||
|
}
|
87
ui/ui_test.go
Normal file
87
ui/ui_test.go
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.thetadev.de/TSGRain/WebUI/src/fixtures"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUI(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
contains string
|
||||||
|
cached bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "index_html",
|
||||||
|
path: "/",
|
||||||
|
contains: "TSGRain",
|
||||||
|
cached: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "index_html2",
|
||||||
|
path: "/index.html",
|
||||||
|
contains: "TSGRain",
|
||||||
|
cached: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "index_js",
|
||||||
|
path: path.Join("/assets", getIndexJS()),
|
||||||
|
contains: "app",
|
||||||
|
cached: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
router := gin.New()
|
||||||
|
Register(router, 3)
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest("GET", tt.path, nil)
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), tt.contains)
|
||||||
|
|
||||||
|
ccHeader := w.Header().Get("Cache-Control")
|
||||||
|
|
||||||
|
if tt.cached {
|
||||||
|
assert.Equal(t, "public, max-age=604800, immutable", ccHeader)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, "", ccHeader)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIndexJS() string {
|
||||||
|
baseDir := "ui/dist/assets"
|
||||||
|
indexExp := regexp.MustCompile(`index\.[0-9a-f]{8}\.js`)
|
||||||
|
|
||||||
|
fixtures.CdProjectRoot()
|
||||||
|
distDir, err := os.Open(baseDir)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := distDir.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range list {
|
||||||
|
if indexExp.MatchString(f.Name()) {
|
||||||
|
return f.Name()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("no index.js found")
|
||||||
|
}
|
7
ui/vite.config.ts
Normal file
7
ui/vite.config.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import {defineConfig} from "vite"
|
||||||
|
import preact from "@preact/preset-vite"
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [preact()],
|
||||||
|
})
|
Loading…
Reference in a new issue