Compare commits

...

5 commits

Author SHA1 Message Date
4e53dd9412 add settings page
Some checks failed
continuous-integration/drone/push Build is failing
2022-02-07 00:05:21 +01:00
8e7c63a9fb removed chips from job table 2022-02-06 01:42:50 +01:00
65c3cef77f finished jobs component 2022-02-06 01:41:29 +01:00
7e8528eef2 add jobs component 2022-02-05 22:30:38 +01:00
80ad7a3a3d created web UI 2022-02-05 12:09:32 +01:00
35 changed files with 3887 additions and 981 deletions

View file

@ -10,6 +10,9 @@ max_line_length = 88
[*.py]
indent_style = space
[*.{jsx,tsx}]
indent_size = 2
[*.{json,md,rst,ini,yml,yaml}]
indent_style = space
indent_size = 2

View file

@ -25,10 +25,11 @@ build-server:
build: build-ui build-server
# Protobuf-API-Definition herunterladen und Code generieren
protoc:
download-proto:
wget "https://code.thetadev.de/TSGRain/Controller/raw/branch/main/proto/tsgrain.proto" -O ${PROTO_FILE}
echo 'option go_package = "code.thetadev.de/TSGRain/WebUI/src/tsgrain_rpc";' >> ${PROTO_FILE}
protoc:
protoc --go_out ${SRC_DIR}/tsgrain_rpc --go_opt=paths=source_relative \
--go-grpc_out ${SRC_DIR}/tsgrain_rpc --go-grpc_opt=paths=source_relative \
${PROTO_FILE}

View file

@ -6,7 +6,13 @@ import "google/protobuf/wrappers.proto";
service TSGRain {
// Starte eine neue Bewässerungsaufgabe (oder stoppe eine laufende, wenn
// diese bereits läuft).
rpc StartTask(TaskRequest) returns (TaskRequestResult) {}
rpc RequestTask(TaskRequest) returns (TaskRequestResult) {}
// Starte eine Bewässerungsaufgabe
rpc StartTask(TaskStart) returns (google.protobuf.BoolValue) {}
// Stoppe eine Bewässerungsaufgabe
rpc StopTask(TaskStop) returns (google.protobuf.BoolValue) {}
// Gibt sämtliche in der Warteschlange befindlichen Bewässerungsaufgaben zurück.
rpc GetTasks(google.protobuf.Empty) returns (TaskList) {}
@ -30,6 +36,12 @@ service TSGRain {
// Lösche den Bewässerungsjob mit der gegebenen ID.
rpc DeleteJob(JobID) returns (google.protobuf.Empty) {}
// Aktiviere Bewässerungsjob
rpc EnableJob(JobID) returns (google.protobuf.Empty) {}
// Deaktiviere Bewässerungsjob
rpc DisableJob(JobID) returns (google.protobuf.Empty) {}
// Gibt zurück, ob der Automatikmodus aktiviert ist.
rpc GetAutoMode(google.protobuf.Empty) returns (google.protobuf.BoolValue) {}
@ -37,10 +49,13 @@ service TSGRain {
rpc SetAutoMode(google.protobuf.BoolValue) returns (google.protobuf.Empty) {}
// Datum/Uhrzeit/Zeitzone abrufen
rpc GetConfigTime(google.protobuf.Empty) returns (ConfigTime) {}
rpc GetSystemTime(google.protobuf.Empty) returns (SystemTime) {}
// Datum/Uhrzeit/Zeitzone einstellen
rpc SetConfigTime(ConfigTime) returns (google.protobuf.Empty) {}
// Datum/Uhrzeit einstellen
rpc SetSystemTime(Timestamp) returns (google.protobuf.Empty) {}
// Zeitzone einstellen
rpc SetSystemTimezone(google.protobuf.StringValue) returns (google.protobuf.Empty) {}
// Standardzeit bei manueller Bewässerung abrufen
rpc GetDefaultIrrigationTime(google.protobuf.Empty) returns (google.protobuf.Int32Value) {}
@ -76,6 +91,18 @@ message TaskRequestResult {
bool stopped = 2;
}
message TaskStart {
TaskSource source = 1;
int32 zone_id = 2;
int32 duration = 3;
bool queuing = 4;
}
message TaskStop {
TaskSource source = 1;
int32 zone_id = 2;
}
message Task {
TaskSource source = 1;
int32 zone_id = 2;
@ -107,7 +134,7 @@ message JobList {
repeated Job jobs = 1;
}
message ConfigTime {
message SystemTime {
Timestamp datetime = 1;
string timezone = 2;
}

View file

@ -7,7 +7,7 @@ package model
//swagger:model AutoMode
type AutoMode struct {
// required: true
State bool `json:"state" binding:"required"`
State bool `json:"state"`
}
// DefaultIrrigationTime model
@ -20,12 +20,32 @@ type DefaultIrrigationTime struct {
Time int32 `json:"time" binding:"required"`
}
// ConfigTime model
// Timestamp model
//
// Zeitstempel
//
//swagger:model Timestamp
type Timestamp struct {
// required: true
Time int64 `json:"time" binding:"required"`
}
// Timezone model
//
// Systemzeitzone
//
//swagger:model Timezone
type Timezone struct {
// required: true
Timezone string `json:"timezone" binding:"required"`
}
// SystemTime model
//
// Aktuelle Systemzeit/Zeitzone
//
//swagger:model ConfigTime
type ConfigTime struct {
//swagger:model SystemTime
type SystemTime struct {
// Aktuelle Systemzeit
//
// required: true

View file

@ -54,12 +54,7 @@ type TaskList struct {
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
//swagger:model ZoneID
type ZoneID struct {
Id int32 `json:"id" binding:"required"`
}

View file

@ -14,6 +14,7 @@ import (
"errors"
"fmt"
"net/http"
"strconv"
"time"
"code.thetadev.de/TSGRain/WebUI/src/config"
@ -84,19 +85,27 @@ func (srv *WebUIServer) getRouter() *gin.Engine {
api.GET("/panic", srv.controllerPanic)
}
api.POST("/task/manual", srv.controllerStartTask)
api.POST("/task/start", srv.controllerStartTask)
api.POST("/task/stop", srv.controllerStopTask)
api.GET("/tasks", srv.controllerGetTasks)
api.GET("/jobs", srv.controllerGetJobs)
api.GET("/job", srv.controllerGetJob)
api.POST("/job", srv.controllerCreateJob)
api.PUT("/job", srv.controllerUpdateJob)
api.DELETE("/job", srv.controllerDeleteJob)
api.GET("config/auto", srv.controllerGetAutoMode)
api.POST("config/auto", srv.controllerSetAutoMode)
api.GET("config/defaultIrrigationTime", srv.controllerGetDefaultIrrigationTime)
api.POST("config/defaultIrrigationTime", srv.controllerSetDefaultIrrigationTime)
api.GET("config/time", srv.controllerGetConfigTime)
api.POST("config/time", srv.controllerSetConfigTime)
api.GET("config/time", srv.controllerGetSystemTime)
api.POST("config/time", srv.controllerSetSystemTime)
api.POST("config/timezone", srv.controllerSetSystemTimezone)
// UI
uiGroup := router.Group("/", middleware.Compression(
@ -134,85 +143,122 @@ func (srv *WebUIServer) Run() error {
return err
}
// swagger:operation POST /task/manual startManualTask
// swagger:operation POST /task/start startTask
//
// Starte/stoppe manuell eine neue Bewässerungsaufgabe
// Starte eine neue manuelle Bewässerungsaufgabe
// mit der eingestellten Standardzeit.
//
// ---
// consumes: [application/json]
// produces: [application/json]
// parameters:
// - in: body
// name: taskRequest
// - name: zoneId
// in: body
// description: ZoneID
// 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
// $ref: '#/definitions/ZoneID'
// responses:
// 200:
// description: Bewässerungsaufgabe erfolgreich gestarted/gestoppt
// description: OK
// 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(&params); err != nil {
var zoneId model.ZoneID
if err := c.ShouldBindJSON(&zoneId); err != nil {
log.Error().Err(err).Msg("StartTask input error")
writeStatus(c, http.StatusBadRequest, "invalid input data")
return
}
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)
ok, err := srv.rpc.StartManualTask(zoneId.Id, 0)
if err != nil {
log.Error().Err(err).Msg("StartManualTask")
log.Error().Err(err).Msg("StartTask")
writeStatus(c, http.StatusInternalServerError, "error starting task")
return
}
statusCode := 200
if !res.Started && !res.Stopped {
statusCode = 400
if !ok {
log.Error().Msg("StartTask: alrady running")
writeStatus(c, http.StatusBadRequest, "task already running")
return
}
c.JSON(statusCode, res)
writeStatus(c, http.StatusOK, "task started")
}
// swagger:operation POST /task/stop stopTask
//
// Stoppe eine manuelle Bewässerungsaufgabe.
//
// ---
// consumes: [application/json]
// produces: [application/json]
// parameters:
// - name: zoneId
// in: body
// description: ZoneID
// schema:
// $ref: '#/definitions/ZoneID'
// responses:
// 200:
// description: OK
// schema:
// $ref: "#/definitions/StatusMessage"
// 500:
// description: Serverfehler
// schema:
// $ref: "#/definitions/Error"
func (srv *WebUIServer) controllerStopTask(c *gin.Context) {
var zoneId model.ZoneID
if err := c.ShouldBindJSON(&zoneId); err != nil {
log.Error().Err(err).Msg("StopTask input error")
writeStatus(c, http.StatusBadRequest, "invalid input data")
return
}
ok, err := srv.rpc.StopManualTask(zoneId.Id)
if err != nil {
log.Error().Err(err).Msg("StopTask")
writeStatus(c, http.StatusInternalServerError, "error stopping task")
return
}
if !ok {
log.Error().Msg("StopTask: no task to stop")
writeStatus(c, http.StatusBadRequest, "no task to stop")
return
}
writeStatus(c, http.StatusOK, "task stopped")
}
// swagger:operation GET /tasks getTasks
//
// Rufe alle momentan laufenden Bewässerungsaufgaben ab.
//
// ---
// produces: [application/json]
// responses:
// 200:
// description: Liste der Bewässerungsaufgaben
// schema:
// "$ref": "#/definitions/TaskList"
// 500:
// description: Serverfehler
// schema:
// "$ref": "#/definitions/Error"
func (srv *WebUIServer) controllerGetTasks(c *gin.Context) {
tasks, err := srv.rpc.GetTasks()
if err != nil {
log.Error().Err(err).Msg("GetTasks")
writeStatus(c, http.StatusInternalServerError, "error getting tasks")
return
}
c.JSON(200, tasks)
}
// swagger:operation GET /jobs getJobs
@ -263,17 +309,16 @@ func (srv *WebUIServer) controllerGetJobs(c *gin.Context) {
// schema:
// $ref: "#/definitions/Error"
func (srv *WebUIServer) controllerGetJob(c *gin.Context) {
var params struct {
Id int32 `uri:"id" binding:"required"`
}
idStr := c.Query("id")
if err := c.ShouldBindUri(&params); err != nil {
id, err := strconv.ParseInt(idStr, 10, 32)
if err != nil {
log.Error().Err(err).Msg("GetJob input error")
writeStatus(c, http.StatusBadRequest, "invalid input data")
return
}
job, err := srv.rpc.GetJob(params.Id)
job, err := srv.rpc.GetJob(int32(id))
if err != nil {
log.Error().Err(err).Msg("GetJob")
writeStatus(c, http.StatusInternalServerError, "error getting job")
@ -373,10 +418,11 @@ func (srv *WebUIServer) controllerUpdateJob(c *gin.Context) {
// consumes: [application/json]
// produces: [application/json]
// parameters:
// - name: id
// - name: jobId
// in: body
// description: JobID
// schema:
// type: integer
// $ref: '#/definitions/JobID'
// responses:
// 200:
// description: OK
@ -442,12 +488,7 @@ func (srv *WebUIServer) controllerGetAutoMode(c *gin.Context) {
// name: state
// description: Zustand des Automatikmodus
// schema:
// type: object
// properties:
// state:
// type: boolean
// required:
// - state
// $ref: '#/definitions/AutoMode'
// responses:
// 200:
// description: OK
@ -475,7 +516,7 @@ func (srv *WebUIServer) controllerSetAutoMode(c *gin.Context) {
writeStatus(c, http.StatusOK, "set autoMode")
}
// swagger:operation GET /config/time getConfigTime
// swagger:operation GET /config/time getSystemTime
//
// Rufe die aktuelle Systemzeit/Zeitzone ab
//
@ -485,34 +526,34 @@ func (srv *WebUIServer) controllerSetAutoMode(c *gin.Context) {
// 200:
// description: Aktuelle Systemzeit/Zeitzone
// schema:
// $ref: "#/definitions/ConfigTime"
// $ref: "#/definitions/SystemTime"
// 500:
// description: Serverfehler
// schema:
// $ref: "#/definitions/Error"
func (srv *WebUIServer) controllerGetConfigTime(c *gin.Context) {
configTime, err := srv.rpc.GetConfigTime()
func (srv *WebUIServer) controllerGetSystemTime(c *gin.Context) {
systemTime, err := srv.rpc.GetSystemTime()
if err != nil {
log.Error().Err(err).Msg("GetConfigTime")
writeStatus(c, http.StatusInternalServerError, "error setting configTime")
log.Error().Err(err).Msg("GetSystemTime")
writeStatus(c, http.StatusInternalServerError, "error getting system time")
return
}
c.JSON(200, configTime)
c.JSON(200, systemTime)
}
// swagger:operation POST /config/time setConfigTime
// swagger:operation POST /config/time setSystemTime
//
// Automatikmodus aktivieren/deaktivieren
// Systemzeit einstellen
//
// ---
// consumes: [application/json]
// produces: [application/json]
// parameters:
// - in: body
// name: configTime
// name: timestamp
// schema:
// $ref: "#/definitions/ConfigTime"
// $ref: "#/definitions/Timestamp"
// responses:
// 200:
// description: OK
@ -522,22 +563,61 @@ func (srv *WebUIServer) controllerGetConfigTime(c *gin.Context) {
// 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")
func (srv *WebUIServer) controllerSetSystemTime(c *gin.Context) {
var systemTime model.Timestamp
if err := c.ShouldBindJSON(&systemTime); err != nil {
log.Error().Err(err).Msg("SetSystemTime input error")
writeStatus(c, http.StatusBadRequest, "invalid input data")
return
}
err := srv.rpc.SetConfigTime(configTime)
err := srv.rpc.SetSystemTime(systemTime.Time)
if err != nil {
log.Error().Err(err).Msg("SetConfigTime")
writeStatus(c, http.StatusInternalServerError, "error setting configTime")
log.Error().Err(err).Msg("SetSystemTime")
writeStatus(c, http.StatusInternalServerError, "error setting systemTime")
return
}
writeStatus(c, http.StatusOK, "set configTime")
writeStatus(c, http.StatusOK, "set systemTime")
}
// swagger:operation POST /config/timezone setSystemTimezone
//
// Systemzeitzone einstellen
//
// ---
// consumes: [application/json]
// produces: [application/json]
// parameters:
// - in: body
// name: timezone
// schema:
// $ref: "#/definitions/Timezone"
// responses:
// 200:
// description: OK
// schema:
// $ref: "#/definitions/StatusMessage"
// 500:
// description: Serverfehler
// schema:
// $ref: "#/definitions/Error"
func (srv *WebUIServer) controllerSetSystemTimezone(c *gin.Context) {
var timezone model.Timezone
if err := c.ShouldBindJSON(&timezone); err != nil {
log.Error().Err(err).Msg("SetSystemTimezone input error")
writeStatus(c, http.StatusBadRequest, "invalid input data")
return
}
err := srv.rpc.SetSystemTimezone(timezone.Timezone)
if err != nil {
log.Error().Err(err).Msg("SetSystemTimezone")
writeStatus(c, http.StatusInternalServerError, "error setting systemTimezone")
return
}
writeStatus(c, http.StatusOK, "set systemTimezone")
}
// swagger:operation GET /config/defaultIrrigationTime getDefaultIrrigationTime
@ -564,7 +644,7 @@ func (srv *WebUIServer) controllerGetDefaultIrrigationTime(c *gin.Context) {
return
}
c.JSON(200, defaultIrrigationTime)
c.JSON(200, model.DefaultIrrigationTime{Time: defaultIrrigationTime})
}
// swagger:operation POST /config/defaultIrrigationTime setDefaultIrrigationTime

View file

@ -8,21 +8,6 @@ definitions:
- 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:
@ -158,6 +143,21 @@ definitions:
- msg
title: StatusMessage model
type: object
SystemTime:
description: Aktuelle Systemzeit/Zeitzone
properties:
time:
description: Aktuelle Systemzeit
format: int64
type: integer
timezone:
description: Aktuelle Zeitzone
type: string
required:
- time
- timezone
title: SystemTime model
type: object
Task:
description: Task stellt eine Bewässerungsaufgabe dar.
properties:
@ -216,14 +216,30 @@ definitions:
- auto_mode
title: TaskList model
type: object
TaskRequestResult:
description: TaskRequestResult wird beim Starten eines Tasks zurückgegeben
Timestamp:
description: Zeitstempel
properties:
Started:
type: boolean
Stopped:
type: boolean
title: TaskRequestResult model
time:
format: int64
type: integer
required:
- time
title: Timestamp model
type: object
Timezone:
description: Systemzeitzone
properties:
timezone:
type: string
required:
- timezone
title: Timezone model
type: object
ZoneID:
properties:
id:
format: int32
type: integer
type: object
info:
description: REST API for the TSGRain WebUI
@ -257,12 +273,7 @@ paths:
in: body
name: state
schema:
properties:
state:
type: boolean
required:
- state
type: object
$ref: "#/definitions/AutoMode"
produces:
- application/json
responses:
@ -313,14 +324,14 @@ paths:
/config/time:
get:
description: Rufe die aktuelle Systemzeit/Zeitzone ab
operationId: getConfigTime
operationId: getSystemTime
produces:
- application/json
responses:
"200":
description: Aktuelle Systemzeit/Zeitzone
schema:
$ref: "#/definitions/ConfigTime"
$ref: "#/definitions/SystemTime"
"500":
description: Serverfehler
schema:
@ -328,13 +339,35 @@ paths:
post:
consumes:
- application/json
description: Automatikmodus aktivieren/deaktivieren
operationId: setConfigTime
description: Systemzeit einstellen
operationId: setSystemTime
parameters:
- in: body
name: configTime
name: timestamp
schema:
$ref: "#/definitions/ConfigTime"
$ref: "#/definitions/Timestamp"
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: "#/definitions/StatusMessage"
"500":
description: Serverfehler
schema:
$ref: "#/definitions/Error"
/config/timezone:
post:
consumes:
- application/json
description: Systemzeitzone einstellen
operationId: setSystemTimezone
parameters:
- in: body
name: timezone
schema:
$ref: "#/definitions/Timezone"
produces:
- application/json
responses:
@ -352,10 +385,11 @@ paths:
- application/json
operationId: deleteJob
parameters:
- in: body
name: id
- description: JobID
in: body
name: jobId
schema:
type: integer
$ref: "#/definitions/JobID"
produces:
- application/json
responses:
@ -449,54 +483,69 @@ paths:
schema:
$ref: "#/definitions/Error"
summary: Rufe alle gespeicherten Zeitpläne ab.
/task/manual:
/task/start:
post:
consumes:
- application/json
description: Starte/stoppe manuell eine neue Bewässerungsaufgabe
operationId: startManualTask
description: |-
Starte eine neue manuelle Bewässerungsaufgabe
mit der eingestellten Standardzeit.
operationId: startTask
parameters:
- in: body
name: taskRequest
- description: ZoneID
in: body
name: zoneId
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
$ref: "#/definitions/ZoneID"
produces:
- application/json
responses:
"200":
description: Bewässerungsaufgabe erfolgreich gestarted/gestoppt
description: OK
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"
/task/stop:
post:
consumes:
- application/json
operationId: stopTask
parameters:
- description: ZoneID
in: body
name: zoneId
schema:
$ref: "#/definitions/ZoneID"
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: "#/definitions/StatusMessage"
"500":
description: Serverfehler
schema:
$ref: "#/definitions/Error"
summary: Stoppe eine manuelle Bewässerungsaufgabe.
/tasks:
get:
operationId: getTasks
produces:
- application/json
responses:
"200":
description: Liste der Bewässerungsaufgaben
schema:
$ref: "#/definitions/TaskList"
"500":
description: Serverfehler
schema:
$ref: "#/definitions/Error"
summary: Rufe alle momentan laufenden Bewässerungsaufgaben ab.
schemes:
- http
- https

View file

@ -251,6 +251,132 @@ func (x *TaskRequestResult) GetStopped() bool {
return false
}
type TaskStart struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Source TaskSource `protobuf:"varint,1,opt,name=source,proto3,enum=TaskSource" json:"source,omitempty"`
ZoneId int32 `protobuf:"varint,2,opt,name=zone_id,json=zoneId,proto3" json:"zone_id,omitempty"`
Duration int32 `protobuf:"varint,3,opt,name=duration,proto3" json:"duration,omitempty"`
Queuing bool `protobuf:"varint,4,opt,name=queuing,proto3" json:"queuing,omitempty"`
}
func (x *TaskStart) Reset() {
*x = TaskStart{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_tsgrain_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TaskStart) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TaskStart) ProtoMessage() {}
func (x *TaskStart) ProtoReflect() protoreflect.Message {
mi := &file_proto_tsgrain_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TaskStart.ProtoReflect.Descriptor instead.
func (*TaskStart) Descriptor() ([]byte, []int) {
return file_proto_tsgrain_proto_rawDescGZIP(), []int{3}
}
func (x *TaskStart) GetSource() TaskSource {
if x != nil {
return x.Source
}
return TaskSource_MANUAL
}
func (x *TaskStart) GetZoneId() int32 {
if x != nil {
return x.ZoneId
}
return 0
}
func (x *TaskStart) GetDuration() int32 {
if x != nil {
return x.Duration
}
return 0
}
func (x *TaskStart) GetQueuing() bool {
if x != nil {
return x.Queuing
}
return false
}
type TaskStop struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Source TaskSource `protobuf:"varint,1,opt,name=source,proto3,enum=TaskSource" json:"source,omitempty"`
ZoneId int32 `protobuf:"varint,2,opt,name=zone_id,json=zoneId,proto3" json:"zone_id,omitempty"`
}
func (x *TaskStop) Reset() {
*x = TaskStop{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_tsgrain_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *TaskStop) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*TaskStop) ProtoMessage() {}
func (x *TaskStop) ProtoReflect() protoreflect.Message {
mi := &file_proto_tsgrain_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use TaskStop.ProtoReflect.Descriptor instead.
func (*TaskStop) Descriptor() ([]byte, []int) {
return file_proto_tsgrain_proto_rawDescGZIP(), []int{4}
}
func (x *TaskStop) GetSource() TaskSource {
if x != nil {
return x.Source
}
return TaskSource_MANUAL
}
func (x *TaskStop) GetZoneId() int32 {
if x != nil {
return x.ZoneId
}
return 0
}
type Task struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@ -266,7 +392,7 @@ type Task struct {
func (x *Task) Reset() {
*x = Task{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_tsgrain_proto_msgTypes[3]
mi := &file_proto_tsgrain_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -279,7 +405,7 @@ func (x *Task) String() string {
func (*Task) ProtoMessage() {}
func (x *Task) ProtoReflect() protoreflect.Message {
mi := &file_proto_tsgrain_proto_msgTypes[3]
mi := &file_proto_tsgrain_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -292,7 +418,7 @@ func (x *Task) ProtoReflect() protoreflect.Message {
// Deprecated: Use Task.ProtoReflect.Descriptor instead.
func (*Task) Descriptor() ([]byte, []int) {
return file_proto_tsgrain_proto_rawDescGZIP(), []int{3}
return file_proto_tsgrain_proto_rawDescGZIP(), []int{5}
}
func (x *Task) GetSource() TaskSource {
@ -343,7 +469,7 @@ type TaskList struct {
func (x *TaskList) Reset() {
*x = TaskList{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_tsgrain_proto_msgTypes[4]
mi := &file_proto_tsgrain_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -356,7 +482,7 @@ func (x *TaskList) String() string {
func (*TaskList) ProtoMessage() {}
func (x *TaskList) ProtoReflect() protoreflect.Message {
mi := &file_proto_tsgrain_proto_msgTypes[4]
mi := &file_proto_tsgrain_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -369,7 +495,7 @@ func (x *TaskList) ProtoReflect() protoreflect.Message {
// Deprecated: Use TaskList.ProtoReflect.Descriptor instead.
func (*TaskList) Descriptor() ([]byte, []int) {
return file_proto_tsgrain_proto_rawDescGZIP(), []int{4}
return file_proto_tsgrain_proto_rawDescGZIP(), []int{6}
}
func (x *TaskList) GetTasks() []*Task {
@ -409,7 +535,7 @@ type Job struct {
func (x *Job) Reset() {
*x = Job{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_tsgrain_proto_msgTypes[5]
mi := &file_proto_tsgrain_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -422,7 +548,7 @@ func (x *Job) String() string {
func (*Job) ProtoMessage() {}
func (x *Job) ProtoReflect() protoreflect.Message {
mi := &file_proto_tsgrain_proto_msgTypes[5]
mi := &file_proto_tsgrain_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -435,7 +561,7 @@ func (x *Job) ProtoReflect() protoreflect.Message {
// Deprecated: Use Job.ProtoReflect.Descriptor instead.
func (*Job) Descriptor() ([]byte, []int) {
return file_proto_tsgrain_proto_rawDescGZIP(), []int{5}
return file_proto_tsgrain_proto_rawDescGZIP(), []int{7}
}
func (x *Job) GetId() int32 {
@ -491,7 +617,7 @@ type JobID struct {
func (x *JobID) Reset() {
*x = JobID{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_tsgrain_proto_msgTypes[6]
mi := &file_proto_tsgrain_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -504,7 +630,7 @@ func (x *JobID) String() string {
func (*JobID) ProtoMessage() {}
func (x *JobID) ProtoReflect() protoreflect.Message {
mi := &file_proto_tsgrain_proto_msgTypes[6]
mi := &file_proto_tsgrain_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -517,7 +643,7 @@ func (x *JobID) ProtoReflect() protoreflect.Message {
// Deprecated: Use JobID.ProtoReflect.Descriptor instead.
func (*JobID) Descriptor() ([]byte, []int) {
return file_proto_tsgrain_proto_rawDescGZIP(), []int{6}
return file_proto_tsgrain_proto_rawDescGZIP(), []int{8}
}
func (x *JobID) GetId() int32 {
@ -538,7 +664,7 @@ type JobList struct {
func (x *JobList) Reset() {
*x = JobList{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_tsgrain_proto_msgTypes[7]
mi := &file_proto_tsgrain_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -551,7 +677,7 @@ func (x *JobList) String() string {
func (*JobList) ProtoMessage() {}
func (x *JobList) ProtoReflect() protoreflect.Message {
mi := &file_proto_tsgrain_proto_msgTypes[7]
mi := &file_proto_tsgrain_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -564,7 +690,7 @@ func (x *JobList) ProtoReflect() protoreflect.Message {
// Deprecated: Use JobList.ProtoReflect.Descriptor instead.
func (*JobList) Descriptor() ([]byte, []int) {
return file_proto_tsgrain_proto_rawDescGZIP(), []int{7}
return file_proto_tsgrain_proto_rawDescGZIP(), []int{9}
}
func (x *JobList) GetJobs() []*Job {
@ -574,7 +700,7 @@ func (x *JobList) GetJobs() []*Job {
return nil
}
type ConfigTime struct {
type SystemTime struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
@ -583,23 +709,23 @@ type ConfigTime struct {
Timezone string `protobuf:"bytes,2,opt,name=timezone,proto3" json:"timezone,omitempty"`
}
func (x *ConfigTime) Reset() {
*x = ConfigTime{}
func (x *SystemTime) Reset() {
*x = SystemTime{}
if protoimpl.UnsafeEnabled {
mi := &file_proto_tsgrain_proto_msgTypes[8]
mi := &file_proto_tsgrain_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ConfigTime) String() string {
func (x *SystemTime) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ConfigTime) ProtoMessage() {}
func (*SystemTime) ProtoMessage() {}
func (x *ConfigTime) ProtoReflect() protoreflect.Message {
mi := &file_proto_tsgrain_proto_msgTypes[8]
func (x *SystemTime) ProtoReflect() protoreflect.Message {
mi := &file_proto_tsgrain_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -610,19 +736,19 @@ func (x *ConfigTime) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use ConfigTime.ProtoReflect.Descriptor instead.
func (*ConfigTime) Descriptor() ([]byte, []int) {
return file_proto_tsgrain_proto_rawDescGZIP(), []int{8}
// Deprecated: Use SystemTime.ProtoReflect.Descriptor instead.
func (*SystemTime) Descriptor() ([]byte, []int) {
return file_proto_tsgrain_proto_rawDescGZIP(), []int{10}
}
func (x *ConfigTime) GetDatetime() *Timestamp {
func (x *SystemTime) GetDatetime() *Timestamp {
if x != nil {
return x.Datetime
}
return nil
}
func (x *ConfigTime) GetTimezone() string {
func (x *SystemTime) GetTimezone() string {
if x != nil {
return x.Timezone
}
@ -654,104 +780,134 @@ var file_proto_tsgrain_proto_rawDesc = []byte{
0x6c, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20,
0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12, 0x18, 0x0a, 0x07,
0x73, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73,
0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x22, 0xd0, 0x01, 0x0a, 0x04, 0x54, 0x61, 0x73, 0x6b, 0x12,
0x23, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x0b, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x7a, 0x6f, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18,
0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x7a, 0x6f, 0x6e, 0x65, 0x49, 0x64, 0x12, 0x1a, 0x0a,
0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52,
0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x35, 0x0a, 0x10, 0x64, 0x61, 0x74,
0x65, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
0x0f, 0x64, 0x61, 0x74, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64,
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,
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, 0x62, 0x0a, 0x08, 0x54, 0x61, 0x73,
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,
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,
0x12, 0x1b, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20,
0x01, 0x28, 0x08, 0x52, 0x08, 0x61, 0x75, 0x74, 0x6f, 0x4d, 0x6f, 0x64, 0x65, 0x22, 0x97, 0x01,
0x0a, 0x03, 0x4a, 0x6f, 0x62, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1e, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20,
0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
0x04, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x12, 0x14, 0x0a, 0x05, 0x7a, 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x05,
0x52, 0x05, 0x7a, 0x6f, 0x6e, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x61, 0x62, 0x6c,
0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12,
0x16, 0x0a, 0x06, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52,
0x06, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x22, 0x17, 0x0a, 0x05, 0x4a, 0x6f, 0x62, 0x49, 0x44,
0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x22, 0x7f, 0x0a, 0x09, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74,
0x61, 0x72, 0x74, 0x12, 0x23, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0e, 0x32, 0x0b, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x7a, 0x6f, 0x6e, 0x65,
0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x7a, 0x6f, 0x6e, 0x65, 0x49,
0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20,
0x01, 0x28, 0x05, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a,
0x07, 0x71, 0x75, 0x65, 0x75, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07,
0x71, 0x75, 0x65, 0x75, 0x69, 0x6e, 0x67, 0x22, 0x48, 0x0a, 0x08, 0x54, 0x61, 0x73, 0x6b, 0x53,
0x74, 0x6f, 0x70, 0x12, 0x23, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0e, 0x32, 0x0b, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x7a, 0x6f, 0x6e, 0x65,
0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x7a, 0x6f, 0x6e, 0x65, 0x49,
0x64, 0x22, 0xd0, 0x01, 0x0a, 0x04, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x23, 0x0a, 0x06, 0x73, 0x6f,
0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0b, 0x2e, 0x54, 0x61, 0x73,
0x6b, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12,
0x17, 0x0a, 0x07, 0x7a, 0x6f, 0x6e, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05,
0x52, 0x06, 0x7a, 0x6f, 0x6e, 0x65, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x12, 0x35, 0x0a, 0x10, 0x64, 0x61, 0x74, 0x65, 0x74, 0x69, 0x6d, 0x65,
0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a,
0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f, 0x64, 0x61, 0x74, 0x65,
0x74, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 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, 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, 0x62, 0x0a, 0x08, 0x54, 0x61, 0x73, 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, 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, 0x12, 0x1b, 0x0a, 0x09, 0x61,
0x75, 0x74, 0x6f, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08,
0x61, 0x75, 0x74, 0x6f, 0x4d, 0x6f, 0x64, 0x65, 0x22, 0x97, 0x01, 0x0a, 0x03, 0x4a, 0x6f, 0x62,
0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64,
0x22, 0x23, 0x0a, 0x07, 0x4a, 0x6f, 0x62, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x04, 0x6a,
0x6f, 0x62, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x4a, 0x6f, 0x62, 0x52,
0x04, 0x6a, 0x6f, 0x62, 0x73, 0x22, 0x50, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x54,
0x69, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x18,
0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
0x70, 0x52, 0x08, 0x64, 0x61, 0x74, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74,
0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74,
0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x2a, 0x26, 0x0a, 0x0a, 0x54, 0x61, 0x73, 0x6b, 0x53,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x41, 0x4e, 0x55, 0x41, 0x4c, 0x10,
0x00, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x43, 0x48, 0x45, 0x44, 0x55, 0x4c, 0x45, 0x10, 0x01, 0x32,
0xc7, 0x06, 0x0a, 0x07, 0x54, 0x53, 0x47, 0x52, 0x61, 0x69, 0x6e, 0x12, 0x2f, 0x0a, 0x09, 0x53,
0x74, 0x61, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x0c, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52,
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,
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, 0x6c,
0x56, 0x61, 0x6c, 0x75, 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,
0x36, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 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, 0x0b, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x0b, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69,
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,
0x12, 0x1e, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a,
0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x04, 0x64, 0x61, 0x74, 0x65,
0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01,
0x28, 0x05, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05,
0x7a, 0x6f, 0x6e, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x05, 0x52, 0x05, 0x7a, 0x6f, 0x6e,
0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01,
0x28, 0x08, 0x52, 0x06, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65,
0x70, 0x65, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x72, 0x65, 0x70, 0x65,
0x61, 0x74, 0x22, 0x17, 0x0a, 0x05, 0x4a, 0x6f, 0x62, 0x49, 0x44, 0x12, 0x0e, 0x0a, 0x02, 0x69,
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x22, 0x23, 0x0a, 0x07, 0x4a,
0x6f, 0x62, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x04, 0x6a, 0x6f, 0x62, 0x73, 0x18, 0x01,
0x20, 0x03, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x4a, 0x6f, 0x62, 0x52, 0x04, 0x6a, 0x6f, 0x62, 0x73,
0x22, 0x50, 0x0a, 0x0a, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x26,
0x0a, 0x08, 0x64, 0x61, 0x74, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
0x32, 0x0a, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x64, 0x61,
0x74, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f,
0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f,
0x6e, 0x65, 0x2a, 0x26, 0x0a, 0x0a, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65,
0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x41, 0x4e, 0x55, 0x41, 0x4c, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08,
0x53, 0x43, 0x48, 0x45, 0x44, 0x55, 0x4c, 0x45, 0x10, 0x01, 0x32, 0xe0, 0x08, 0x0a, 0x07, 0x54,
0x53, 0x47, 0x52, 0x61, 0x69, 0x6e, 0x12, 0x31, 0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x0c, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52, 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, 0x35, 0x0a, 0x09, 0x53, 0x74, 0x61,
0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x0a, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61,
0x72, 0x74, 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, 0x33, 0x0a, 0x08, 0x53, 0x74, 0x6f, 0x70, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x09, 0x2e, 0x54,
0x61, 0x73, 0x6b, 0x53, 0x74, 0x6f, 0x70, 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, 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, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 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, 0x42, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x4e, 0x5a, 0x6f, 0x6e,
0x65, 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, 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, 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,
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, 0x2d,
0x0a, 0x09, 0x45, 0x6e, 0x61, 0x62, 0x6c, 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, 0x2e, 0x0a,
0x0a, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 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, 0x6c, 0x56, 0x61, 0x6c, 0x75, 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, 0x36, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x53, 0x79,
0x73, 0x74, 0x65, 0x6d, 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, 0x0b, 0x2e, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x00, 0x12,
0x35, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x54, 0x69, 0x6d, 0x65,
0x12, 0x0a, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 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, 0x4b, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x53, 0x79, 0x73,
0x74, 0x65, 0x6d, 0x54, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x12, 0x1c, 0x2e, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74,
0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 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, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 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, 0x42, 0x0a, 0x09, 0x47, 0x65, 0x74,
0x4e, 0x5a, 0x6f, 0x6e, 0x65, 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, 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, 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 (
@ -767,67 +923,82 @@ func file_proto_tsgrain_proto_rawDescGZIP() []byte {
}
var file_proto_tsgrain_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_proto_tsgrain_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
var file_proto_tsgrain_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
var file_proto_tsgrain_proto_goTypes = []interface{}{
(TaskSource)(0), // 0: TaskSource
(*Timestamp)(nil), // 1: Timestamp
(*TaskRequest)(nil), // 2: TaskRequest
(*TaskRequestResult)(nil), // 3: TaskRequestResult
(*Task)(nil), // 4: Task
(*TaskList)(nil), // 5: TaskList
(*Job)(nil), // 6: Job
(*JobID)(nil), // 7: JobID
(*JobList)(nil), // 8: JobList
(*ConfigTime)(nil), // 9: ConfigTime
(*emptypb.Empty)(nil), // 10: google.protobuf.Empty
(*wrapperspb.BoolValue)(nil), // 11: google.protobuf.BoolValue
(*wrapperspb.Int32Value)(nil), // 12: google.protobuf.Int32Value
(TaskSource)(0), // 0: TaskSource
(*Timestamp)(nil), // 1: Timestamp
(*TaskRequest)(nil), // 2: TaskRequest
(*TaskRequestResult)(nil), // 3: TaskRequestResult
(*TaskStart)(nil), // 4: TaskStart
(*TaskStop)(nil), // 5: TaskStop
(*Task)(nil), // 6: Task
(*TaskList)(nil), // 7: TaskList
(*Job)(nil), // 8: Job
(*JobID)(nil), // 9: JobID
(*JobList)(nil), // 10: JobList
(*SystemTime)(nil), // 11: SystemTime
(*emptypb.Empty)(nil), // 12: google.protobuf.Empty
(*wrapperspb.BoolValue)(nil), // 13: google.protobuf.BoolValue
(*wrapperspb.StringValue)(nil), // 14: google.protobuf.StringValue
(*wrapperspb.Int32Value)(nil), // 15: google.protobuf.Int32Value
}
var file_proto_tsgrain_proto_depIdxs = []int32{
0, // 0: TaskRequest.source:type_name -> TaskSource
0, // 1: Task.source:type_name -> TaskSource
1, // 2: Task.datetime_started:type_name -> Timestamp
1, // 3: Task.datetime_finished:type_name -> Timestamp
4, // 4: TaskList.tasks:type_name -> Task
1, // 5: TaskList.now:type_name -> Timestamp
1, // 6: Job.date:type_name -> Timestamp
6, // 7: JobList.jobs:type_name -> Job
1, // 8: ConfigTime.datetime:type_name -> Timestamp
2, // 9: TSGRain.StartTask:input_type -> TaskRequest
10, // 10: TSGRain.GetTasks:input_type -> google.protobuf.Empty
10, // 11: TSGRain.StreamTasks:input_type -> google.protobuf.Empty
6, // 12: TSGRain.CreateJob:input_type -> Job
7, // 13: TSGRain.GetJob:input_type -> JobID
10, // 14: TSGRain.GetJobs:input_type -> google.protobuf.Empty
6, // 15: TSGRain.UpdateJob:input_type -> Job
7, // 16: TSGRain.DeleteJob:input_type -> JobID
10, // 17: TSGRain.GetAutoMode:input_type -> google.protobuf.Empty
11, // 18: TSGRain.SetAutoMode:input_type -> google.protobuf.BoolValue
10, // 19: TSGRain.GetConfigTime:input_type -> google.protobuf.Empty
9, // 20: TSGRain.SetConfigTime:input_type -> ConfigTime
10, // 21: TSGRain.GetDefaultIrrigationTime:input_type -> google.protobuf.Empty
12, // 22: TSGRain.SetDefaultIrrigationTime:input_type -> google.protobuf.Int32Value
10, // 23: TSGRain.GetNZones:input_type -> google.protobuf.Empty
3, // 24: TSGRain.StartTask:output_type -> TaskRequestResult
5, // 25: TSGRain.GetTasks:output_type -> TaskList
5, // 26: TSGRain.StreamTasks:output_type -> TaskList
7, // 27: TSGRain.CreateJob:output_type -> JobID
6, // 28: TSGRain.GetJob:output_type -> Job
8, // 29: TSGRain.GetJobs:output_type -> JobList
10, // 30: TSGRain.UpdateJob:output_type -> google.protobuf.Empty
10, // 31: TSGRain.DeleteJob:output_type -> google.protobuf.Empty
11, // 32: TSGRain.GetAutoMode:output_type -> google.protobuf.BoolValue
10, // 33: TSGRain.SetAutoMode:output_type -> google.protobuf.Empty
9, // 34: TSGRain.GetConfigTime:output_type -> ConfigTime
10, // 35: TSGRain.SetConfigTime:output_type -> google.protobuf.Empty
12, // 36: TSGRain.GetDefaultIrrigationTime:output_type -> google.protobuf.Int32Value
10, // 37: TSGRain.SetDefaultIrrigationTime:output_type -> google.protobuf.Empty
12, // 38: TSGRain.GetNZones:output_type -> google.protobuf.Int32Value
24, // [24:39] is the sub-list for method output_type
9, // [9:24] is the sub-list for method input_type
9, // [9:9] is the sub-list for extension type_name
9, // [9:9] is the sub-list for extension extendee
0, // [0:9] is the sub-list for field type_name
0, // 1: TaskStart.source:type_name -> TaskSource
0, // 2: TaskStop.source:type_name -> TaskSource
0, // 3: Task.source:type_name -> TaskSource
1, // 4: Task.datetime_started:type_name -> Timestamp
1, // 5: Task.datetime_finished:type_name -> Timestamp
6, // 6: TaskList.tasks:type_name -> Task
1, // 7: TaskList.now:type_name -> Timestamp
1, // 8: Job.date:type_name -> Timestamp
8, // 9: JobList.jobs:type_name -> Job
1, // 10: SystemTime.datetime:type_name -> Timestamp
2, // 11: TSGRain.RequestTask:input_type -> TaskRequest
4, // 12: TSGRain.StartTask:input_type -> TaskStart
5, // 13: TSGRain.StopTask:input_type -> TaskStop
12, // 14: TSGRain.GetTasks:input_type -> google.protobuf.Empty
12, // 15: TSGRain.StreamTasks:input_type -> google.protobuf.Empty
8, // 16: TSGRain.CreateJob:input_type -> Job
9, // 17: TSGRain.GetJob:input_type -> JobID
12, // 18: TSGRain.GetJobs:input_type -> google.protobuf.Empty
8, // 19: TSGRain.UpdateJob:input_type -> Job
9, // 20: TSGRain.DeleteJob:input_type -> JobID
9, // 21: TSGRain.EnableJob:input_type -> JobID
9, // 22: TSGRain.DisableJob:input_type -> JobID
12, // 23: TSGRain.GetAutoMode:input_type -> google.protobuf.Empty
13, // 24: TSGRain.SetAutoMode:input_type -> google.protobuf.BoolValue
12, // 25: TSGRain.GetSystemTime:input_type -> google.protobuf.Empty
1, // 26: TSGRain.SetSystemTime:input_type -> Timestamp
14, // 27: TSGRain.SetSystemTimezone:input_type -> google.protobuf.StringValue
12, // 28: TSGRain.GetDefaultIrrigationTime:input_type -> google.protobuf.Empty
15, // 29: TSGRain.SetDefaultIrrigationTime:input_type -> google.protobuf.Int32Value
12, // 30: TSGRain.GetNZones:input_type -> google.protobuf.Empty
3, // 31: TSGRain.RequestTask:output_type -> TaskRequestResult
13, // 32: TSGRain.StartTask:output_type -> google.protobuf.BoolValue
13, // 33: TSGRain.StopTask:output_type -> google.protobuf.BoolValue
7, // 34: TSGRain.GetTasks:output_type -> TaskList
7, // 35: TSGRain.StreamTasks:output_type -> TaskList
9, // 36: TSGRain.CreateJob:output_type -> JobID
8, // 37: TSGRain.GetJob:output_type -> Job
10, // 38: TSGRain.GetJobs:output_type -> JobList
12, // 39: TSGRain.UpdateJob:output_type -> google.protobuf.Empty
12, // 40: TSGRain.DeleteJob:output_type -> google.protobuf.Empty
12, // 41: TSGRain.EnableJob:output_type -> google.protobuf.Empty
12, // 42: TSGRain.DisableJob:output_type -> google.protobuf.Empty
13, // 43: TSGRain.GetAutoMode:output_type -> google.protobuf.BoolValue
12, // 44: TSGRain.SetAutoMode:output_type -> google.protobuf.Empty
11, // 45: TSGRain.GetSystemTime:output_type -> SystemTime
12, // 46: TSGRain.SetSystemTime:output_type -> google.protobuf.Empty
12, // 47: TSGRain.SetSystemTimezone:output_type -> google.protobuf.Empty
15, // 48: TSGRain.GetDefaultIrrigationTime:output_type -> google.protobuf.Int32Value
12, // 49: TSGRain.SetDefaultIrrigationTime:output_type -> google.protobuf.Empty
15, // 50: TSGRain.GetNZones:output_type -> google.protobuf.Int32Value
31, // [31:51] is the sub-list for method output_type
11, // [11:31] is the sub-list for method input_type
11, // [11:11] is the sub-list for extension type_name
11, // [11:11] is the sub-list for extension extendee
0, // [0:11] is the sub-list for field type_name
}
func init() { file_proto_tsgrain_proto_init() }
@ -873,7 +1044,7 @@ func file_proto_tsgrain_proto_init() {
}
}
file_proto_tsgrain_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Task); i {
switch v := v.(*TaskStart); i {
case 0:
return &v.state
case 1:
@ -885,7 +1056,7 @@ func file_proto_tsgrain_proto_init() {
}
}
file_proto_tsgrain_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TaskList); i {
switch v := v.(*TaskStop); i {
case 0:
return &v.state
case 1:
@ -897,7 +1068,7 @@ func file_proto_tsgrain_proto_init() {
}
}
file_proto_tsgrain_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Job); i {
switch v := v.(*Task); i {
case 0:
return &v.state
case 1:
@ -909,7 +1080,7 @@ func file_proto_tsgrain_proto_init() {
}
}
file_proto_tsgrain_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*JobID); i {
switch v := v.(*TaskList); i {
case 0:
return &v.state
case 1:
@ -921,7 +1092,7 @@ func file_proto_tsgrain_proto_init() {
}
}
file_proto_tsgrain_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*JobList); i {
switch v := v.(*Job); i {
case 0:
return &v.state
case 1:
@ -933,7 +1104,31 @@ func file_proto_tsgrain_proto_init() {
}
}
file_proto_tsgrain_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ConfigTime); i {
switch v := v.(*JobID); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_tsgrain_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*JobList); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proto_tsgrain_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SystemTime); i {
case 0:
return &v.state
case 1:
@ -951,7 +1146,7 @@ func file_proto_tsgrain_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proto_tsgrain_proto_rawDesc,
NumEnums: 1,
NumMessages: 9,
NumMessages: 11,
NumExtensions: 0,
NumServices: 1,
},

View file

@ -26,7 +26,11 @@ const _ = grpc.SupportPackageIsVersion7
type TSGRainClient interface {
// Starte eine neue Bewässerungsaufgabe (oder stoppe eine laufende, wenn
// diese bereits läuft).
StartTask(ctx context.Context, in *TaskRequest, opts ...grpc.CallOption) (*TaskRequestResult, error)
RequestTask(ctx context.Context, in *TaskRequest, opts ...grpc.CallOption) (*TaskRequestResult, error)
// Starte eine Bewässerungsaufgabe
StartTask(ctx context.Context, in *TaskStart, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error)
// Stoppe eine Bewässerungsaufgabe
StopTask(ctx context.Context, in *TaskStop, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error)
// Gibt sämtliche in der Warteschlange befindlichen Bewässerungsaufgaben zurück.
GetTasks(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*TaskList, error)
// Streamt die aktuelle Warteschlange mit ihren Bewässerungsaufgaben,
@ -42,14 +46,20 @@ type TSGRainClient interface {
UpdateJob(ctx context.Context, in *Job, opts ...grpc.CallOption) (*emptypb.Empty, error)
// Lösche den Bewässerungsjob mit der gegebenen ID.
DeleteJob(ctx context.Context, in *JobID, opts ...grpc.CallOption) (*emptypb.Empty, error)
// Aktiviere Bewässerungsjob
EnableJob(ctx context.Context, in *JobID, opts ...grpc.CallOption) (*emptypb.Empty, error)
// Deaktiviere Bewässerungsjob
DisableJob(ctx context.Context, in *JobID, opts ...grpc.CallOption) (*emptypb.Empty, error)
// Gibt zurück, ob der Automatikmodus aktiviert ist.
GetAutoMode(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error)
// Aktiviert/deaktiviert den Automatikmodus.
SetAutoMode(ctx context.Context, in *wrapperspb.BoolValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
// Datum/Uhrzeit/Zeitzone abrufen
GetConfigTime(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ConfigTime, error)
// Datum/Uhrzeit/Zeitzone einstellen
SetConfigTime(ctx context.Context, in *ConfigTime, opts ...grpc.CallOption) (*emptypb.Empty, error)
GetSystemTime(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemTime, error)
// Datum/Uhrzeit einstellen
SetSystemTime(ctx context.Context, in *Timestamp, opts ...grpc.CallOption) (*emptypb.Empty, error)
// Zeitzone einstellen
SetSystemTimezone(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error)
// Standardzeit bei manueller Bewässerung abrufen
GetDefaultIrrigationTime(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.Int32Value, error)
// Standardzeit bei manueller Bewässerung einstellen
@ -66,8 +76,17 @@ func NewTSGRainClient(cc grpc.ClientConnInterface) TSGRainClient {
return &tSGRainClient{cc}
}
func (c *tSGRainClient) StartTask(ctx context.Context, in *TaskRequest, opts ...grpc.CallOption) (*TaskRequestResult, error) {
func (c *tSGRainClient) RequestTask(ctx context.Context, in *TaskRequest, opts ...grpc.CallOption) (*TaskRequestResult, error) {
out := new(TaskRequestResult)
err := c.cc.Invoke(ctx, "/TSGRain/RequestTask", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *tSGRainClient) StartTask(ctx context.Context, in *TaskStart, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error) {
out := new(wrapperspb.BoolValue)
err := c.cc.Invoke(ctx, "/TSGRain/StartTask", in, out, opts...)
if err != nil {
return nil, err
@ -75,6 +94,15 @@ func (c *tSGRainClient) StartTask(ctx context.Context, in *TaskRequest, opts ...
return out, nil
}
func (c *tSGRainClient) StopTask(ctx context.Context, in *TaskStop, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error) {
out := new(wrapperspb.BoolValue)
err := c.cc.Invoke(ctx, "/TSGRain/StopTask", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *tSGRainClient) GetTasks(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*TaskList, error) {
out := new(TaskList)
err := c.cc.Invoke(ctx, "/TSGRain/GetTasks", in, out, opts...)
@ -161,6 +189,24 @@ func (c *tSGRainClient) DeleteJob(ctx context.Context, in *JobID, opts ...grpc.C
return out, nil
}
func (c *tSGRainClient) EnableJob(ctx context.Context, in *JobID, opts ...grpc.CallOption) (*emptypb.Empty, error) {
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, "/TSGRain/EnableJob", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *tSGRainClient) DisableJob(ctx context.Context, in *JobID, opts ...grpc.CallOption) (*emptypb.Empty, error) {
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, "/TSGRain/DisableJob", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *tSGRainClient) GetAutoMode(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*wrapperspb.BoolValue, error) {
out := new(wrapperspb.BoolValue)
err := c.cc.Invoke(ctx, "/TSGRain/GetAutoMode", in, out, opts...)
@ -179,18 +225,27 @@ func (c *tSGRainClient) SetAutoMode(ctx context.Context, in *wrapperspb.BoolValu
return out, nil
}
func (c *tSGRainClient) GetConfigTime(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ConfigTime, error) {
out := new(ConfigTime)
err := c.cc.Invoke(ctx, "/TSGRain/GetConfigTime", in, out, opts...)
func (c *tSGRainClient) GetSystemTime(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemTime, error) {
out := new(SystemTime)
err := c.cc.Invoke(ctx, "/TSGRain/GetSystemTime", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *tSGRainClient) SetConfigTime(ctx context.Context, in *ConfigTime, opts ...grpc.CallOption) (*emptypb.Empty, error) {
func (c *tSGRainClient) SetSystemTime(ctx context.Context, in *Timestamp, opts ...grpc.CallOption) (*emptypb.Empty, error) {
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, "/TSGRain/SetConfigTime", in, out, opts...)
err := c.cc.Invoke(ctx, "/TSGRain/SetSystemTime", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *tSGRainClient) SetSystemTimezone(ctx context.Context, in *wrapperspb.StringValue, opts ...grpc.CallOption) (*emptypb.Empty, error) {
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, "/TSGRain/SetSystemTimezone", in, out, opts...)
if err != nil {
return nil, err
}
@ -230,7 +285,11 @@ func (c *tSGRainClient) GetNZones(ctx context.Context, in *emptypb.Empty, opts .
type TSGRainServer interface {
// Starte eine neue Bewässerungsaufgabe (oder stoppe eine laufende, wenn
// diese bereits läuft).
StartTask(context.Context, *TaskRequest) (*TaskRequestResult, error)
RequestTask(context.Context, *TaskRequest) (*TaskRequestResult, error)
// Starte eine Bewässerungsaufgabe
StartTask(context.Context, *TaskStart) (*wrapperspb.BoolValue, error)
// Stoppe eine Bewässerungsaufgabe
StopTask(context.Context, *TaskStop) (*wrapperspb.BoolValue, error)
// Gibt sämtliche in der Warteschlange befindlichen Bewässerungsaufgaben zurück.
GetTasks(context.Context, *emptypb.Empty) (*TaskList, error)
// Streamt die aktuelle Warteschlange mit ihren Bewässerungsaufgaben,
@ -246,14 +305,20 @@ type TSGRainServer interface {
UpdateJob(context.Context, *Job) (*emptypb.Empty, error)
// Lösche den Bewässerungsjob mit der gegebenen ID.
DeleteJob(context.Context, *JobID) (*emptypb.Empty, error)
// Aktiviere Bewässerungsjob
EnableJob(context.Context, *JobID) (*emptypb.Empty, error)
// Deaktiviere Bewässerungsjob
DisableJob(context.Context, *JobID) (*emptypb.Empty, error)
// Gibt zurück, ob der Automatikmodus aktiviert ist.
GetAutoMode(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error)
// Aktiviert/deaktiviert den Automatikmodus.
SetAutoMode(context.Context, *wrapperspb.BoolValue) (*emptypb.Empty, error)
// Datum/Uhrzeit/Zeitzone abrufen
GetConfigTime(context.Context, *emptypb.Empty) (*ConfigTime, error)
// Datum/Uhrzeit/Zeitzone einstellen
SetConfigTime(context.Context, *ConfigTime) (*emptypb.Empty, error)
GetSystemTime(context.Context, *emptypb.Empty) (*SystemTime, error)
// Datum/Uhrzeit einstellen
SetSystemTime(context.Context, *Timestamp) (*emptypb.Empty, error)
// Zeitzone einstellen
SetSystemTimezone(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error)
// Standardzeit bei manueller Bewässerung abrufen
GetDefaultIrrigationTime(context.Context, *emptypb.Empty) (*wrapperspb.Int32Value, error)
// Standardzeit bei manueller Bewässerung einstellen
@ -267,9 +332,15 @@ type TSGRainServer interface {
type UnimplementedTSGRainServer struct {
}
func (UnimplementedTSGRainServer) StartTask(context.Context, *TaskRequest) (*TaskRequestResult, error) {
func (UnimplementedTSGRainServer) RequestTask(context.Context, *TaskRequest) (*TaskRequestResult, error) {
return nil, status.Errorf(codes.Unimplemented, "method RequestTask not implemented")
}
func (UnimplementedTSGRainServer) StartTask(context.Context, *TaskStart) (*wrapperspb.BoolValue, error) {
return nil, status.Errorf(codes.Unimplemented, "method StartTask not implemented")
}
func (UnimplementedTSGRainServer) StopTask(context.Context, *TaskStop) (*wrapperspb.BoolValue, error) {
return nil, status.Errorf(codes.Unimplemented, "method StopTask not implemented")
}
func (UnimplementedTSGRainServer) GetTasks(context.Context, *emptypb.Empty) (*TaskList, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetTasks not implemented")
}
@ -291,17 +362,26 @@ func (UnimplementedTSGRainServer) UpdateJob(context.Context, *Job) (*emptypb.Emp
func (UnimplementedTSGRainServer) DeleteJob(context.Context, *JobID) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteJob not implemented")
}
func (UnimplementedTSGRainServer) EnableJob(context.Context, *JobID) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method EnableJob not implemented")
}
func (UnimplementedTSGRainServer) DisableJob(context.Context, *JobID) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method DisableJob not implemented")
}
func (UnimplementedTSGRainServer) GetAutoMode(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetAutoMode not implemented")
}
func (UnimplementedTSGRainServer) SetAutoMode(context.Context, *wrapperspb.BoolValue) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetAutoMode not implemented")
}
func (UnimplementedTSGRainServer) GetConfigTime(context.Context, *emptypb.Empty) (*ConfigTime, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetConfigTime not implemented")
func (UnimplementedTSGRainServer) GetSystemTime(context.Context, *emptypb.Empty) (*SystemTime, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetSystemTime not implemented")
}
func (UnimplementedTSGRainServer) SetConfigTime(context.Context, *ConfigTime) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetConfigTime not implemented")
func (UnimplementedTSGRainServer) SetSystemTime(context.Context, *Timestamp) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetSystemTime not implemented")
}
func (UnimplementedTSGRainServer) SetSystemTimezone(context.Context, *wrapperspb.StringValue) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method SetSystemTimezone not implemented")
}
func (UnimplementedTSGRainServer) GetDefaultIrrigationTime(context.Context, *emptypb.Empty) (*wrapperspb.Int32Value, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetDefaultIrrigationTime not implemented")
@ -325,11 +405,29 @@ func RegisterTSGRainServer(s grpc.ServiceRegistrar, srv TSGRainServer) {
s.RegisterService(&TSGRain_ServiceDesc, srv)
}
func _TSGRain_StartTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
func _TSGRain_RequestTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(TaskRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TSGRainServer).RequestTask(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/TSGRain/RequestTask",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TSGRainServer).RequestTask(ctx, req.(*TaskRequest))
}
return interceptor(ctx, in, info, handler)
}
func _TSGRain_StartTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(TaskStart)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TSGRainServer).StartTask(ctx, in)
}
@ -338,7 +436,25 @@ func _TSGRain_StartTask_Handler(srv interface{}, ctx context.Context, dec func(i
FullMethod: "/TSGRain/StartTask",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TSGRainServer).StartTask(ctx, req.(*TaskRequest))
return srv.(TSGRainServer).StartTask(ctx, req.(*TaskStart))
}
return interceptor(ctx, in, info, handler)
}
func _TSGRain_StopTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(TaskStop)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TSGRainServer).StopTask(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/TSGRain/StopTask",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TSGRainServer).StopTask(ctx, req.(*TaskStop))
}
return interceptor(ctx, in, info, handler)
}
@ -472,6 +588,42 @@ func _TSGRain_DeleteJob_Handler(srv interface{}, ctx context.Context, dec func(i
return interceptor(ctx, in, info, handler)
}
func _TSGRain_EnableJob_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(JobID)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TSGRainServer).EnableJob(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/TSGRain/EnableJob",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TSGRainServer).EnableJob(ctx, req.(*JobID))
}
return interceptor(ctx, in, info, handler)
}
func _TSGRain_DisableJob_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(JobID)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TSGRainServer).DisableJob(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/TSGRain/DisableJob",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TSGRainServer).DisableJob(ctx, req.(*JobID))
}
return interceptor(ctx, in, info, handler)
}
func _TSGRain_GetAutoMode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(emptypb.Empty)
if err := dec(in); err != nil {
@ -508,38 +660,56 @@ func _TSGRain_SetAutoMode_Handler(srv interface{}, ctx context.Context, dec func
return interceptor(ctx, in, info, handler)
}
func _TSGRain_GetConfigTime_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
func _TSGRain_GetSystemTime_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(emptypb.Empty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TSGRainServer).GetConfigTime(ctx, in)
return srv.(TSGRainServer).GetSystemTime(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/TSGRain/GetConfigTime",
FullMethod: "/TSGRain/GetSystemTime",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TSGRainServer).GetConfigTime(ctx, req.(*emptypb.Empty))
return srv.(TSGRainServer).GetSystemTime(ctx, req.(*emptypb.Empty))
}
return interceptor(ctx, in, info, handler)
}
func _TSGRain_SetConfigTime_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ConfigTime)
func _TSGRain_SetSystemTime_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Timestamp)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TSGRainServer).SetConfigTime(ctx, in)
return srv.(TSGRainServer).SetSystemTime(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/TSGRain/SetConfigTime",
FullMethod: "/TSGRain/SetSystemTime",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TSGRainServer).SetConfigTime(ctx, req.(*ConfigTime))
return srv.(TSGRainServer).SetSystemTime(ctx, req.(*Timestamp))
}
return interceptor(ctx, in, info, handler)
}
func _TSGRain_SetSystemTimezone_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(wrapperspb.StringValue)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TSGRainServer).SetSystemTimezone(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/TSGRain/SetSystemTimezone",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TSGRainServer).SetSystemTimezone(ctx, req.(*wrapperspb.StringValue))
}
return interceptor(ctx, in, info, handler)
}
@ -605,10 +775,18 @@ var TSGRain_ServiceDesc = grpc.ServiceDesc{
ServiceName: "TSGRain",
HandlerType: (*TSGRainServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "RequestTask",
Handler: _TSGRain_RequestTask_Handler,
},
{
MethodName: "StartTask",
Handler: _TSGRain_StartTask_Handler,
},
{
MethodName: "StopTask",
Handler: _TSGRain_StopTask_Handler,
},
{
MethodName: "GetTasks",
Handler: _TSGRain_GetTasks_Handler,
@ -633,6 +811,14 @@ var TSGRain_ServiceDesc = grpc.ServiceDesc{
MethodName: "DeleteJob",
Handler: _TSGRain_DeleteJob_Handler,
},
{
MethodName: "EnableJob",
Handler: _TSGRain_EnableJob_Handler,
},
{
MethodName: "DisableJob",
Handler: _TSGRain_DisableJob_Handler,
},
{
MethodName: "GetAutoMode",
Handler: _TSGRain_GetAutoMode_Handler,
@ -642,12 +828,16 @@ var TSGRain_ServiceDesc = grpc.ServiceDesc{
Handler: _TSGRain_SetAutoMode_Handler,
},
{
MethodName: "GetConfigTime",
Handler: _TSGRain_GetConfigTime_Handler,
MethodName: "GetSystemTime",
Handler: _TSGRain_GetSystemTime_Handler,
},
{
MethodName: "SetConfigTime",
Handler: _TSGRain_SetConfigTime_Handler,
MethodName: "SetSystemTime",
Handler: _TSGRain_SetSystemTime_Handler,
},
{
MethodName: "SetSystemTimezone",
Handler: _TSGRain_SetSystemTimezone_Handler,
},
{
MethodName: "GetDefaultIrrigationTime",

View file

@ -83,24 +83,30 @@ func mapTaskList(pbTaskList *tsgrain_grpc.TaskList) model.TaskList {
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,
func (c *RPCClient) StartManualTask(zone_id int32, duration int32) (bool, error) {
res, err := c.tsgrain.StartTask(c.ctx, &tsgrain_grpc.TaskStart{
Source: tsgrain_grpc.TaskSource_MANUAL,
ZoneId: zone_id,
Duration: duration,
Queuing: true,
})
if err != nil {
return model.TaskRequestResult{}, err
return false, err
}
return model.TaskRequestResult{
Started: res.Started,
Stopped: res.Stopped,
}, nil
return res.Value, nil
}
func (c *RPCClient) StopManualTask(zone_id int32) (bool, error) {
res, err := c.tsgrain.StopTask(c.ctx, &tsgrain_grpc.TaskStop{
Source: tsgrain_grpc.TaskSource_MANUAL,
ZoneId: zone_id,
})
if err != nil {
return false, err
}
return res.Value, nil
}
func (c *RPCClient) GetTasks() (model.TaskList, error) {
@ -255,30 +261,27 @@ func (c *RPCClient) SetAutoMode(state bool) error {
return err
}
func (c *RPCClient) GetConfigTime() (model.ConfigTime, error) {
configTime, err := c.tsgrain.GetConfigTime(c.ctx, &emptypb.Empty{})
func (c *RPCClient) GetSystemTime() (model.SystemTime, error) {
configTime, err := c.tsgrain.GetSystemTime(c.ctx, &emptypb.Empty{})
if err != nil {
return model.ConfigTime{}, err
return model.SystemTime{}, err
}
return model.ConfigTime{
return model.SystemTime{
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,
})
func (c *RPCClient) SetSystemTime(timestamp int64) error {
_, err := c.tsgrain.SetSystemTime(c.ctx,
&tsgrain_grpc.Timestamp{Seconds: timestamp})
return err
}
func (c *RPCClient) SetSystemTimezone(timezone string) error {
_, err := c.tsgrain.SetConfigTime(c.ctx, &tsgrain_grpc.ConfigTime{
Timezone: timezone,
})
_, err := c.tsgrain.SetSystemTimezone(c.ctx,
&wrapperspb.StringValue{Value: timezone})
return err
}

View file

@ -9,16 +9,22 @@
"format": "prettier --write ../"
},
"dependencies": {
"@mdi/js": "^6.5.95",
"axios": "^0.24.0",
"preact": "^10.5.15"
"@emotion/react": "^11.7.1",
"@emotion/styled": "^11.6.0",
"@mui/icons-material": "^5.3.1",
"@mui/lab": "^5.0.0-alpha.67",
"@mui/material": "^5.4.0",
"axios": "^0.25.0",
"date-fns": "^2.28.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"wouter": "^2.8.0-alpha.2"
},
"devDependencies": {
"@preact/preset-vite": "^2.1.5",
"prettier": "^2.4.1",
"sass": "^1.43.4",
"@babel/core": "^7.17.0",
"@types/react-dom": "^17.0.11",
"@vitejs/plugin-react-refresh": "latest",
"typescript": "^4.5.2",
"vite": "^2.6.14",
"@babel/core": ">=7.12.10 <8.0.0"
"vite": "latest"
}
}

File diff suppressed because it is too large Load diff

36
ui/src/App.tsx Normal file
View file

@ -0,0 +1,36 @@
import * as React from "react"
import {Box, Typography} from "@mui/material"
import {Route} from "wouter"
import Navbar from "./components/Navbar"
import Controls from "./components/Controls"
import Jobs from "./components/Jobs"
import Settings from "./components/Settings"
import NoConnectionBar from "./components/NoConnectionBar"
export default function App() {
return (
<Box
sx={{
display: "flex",
flexDirection: "column",
minHeight: "100vh",
}}
>
<Navbar />
<NoConnectionBar />
<Box sx={{m: 2}}>
<Route path="">
<Controls />
</Route>
<Route path="/jobs">
<Jobs />
</Route>
<Route path="/settings">
<Settings />
</Route>
</Box>
</Box>
)
}

View file

@ -0,0 +1,65 @@
import * as React from "react"
import {Switch, FormControlLabel} from "@mui/material"
import {WebsocketClient} from "../util/websocket"
import {TaskList} from "../tsgrain-client"
import {tsgrainApi} from "../util/apiUrls"
import {handleApiError} from "../util/functions"
type Props = {}
type State = {
wsConnected: boolean
auto: boolean
}
export default class AutoModeSwitch extends React.Component<Props, State> {
private ws?: WebsocketClient
constructor(props: Props) {
super(props)
this.state = {
wsConnected: false,
auto: false,
}
}
private onWsStatusUpdate = (wsConnected: boolean) => {
this.setState({wsConnected: wsConnected})
}
private onWsMessage = (msg: TaskList, date: Date) => {
this.setState({auto: msg.auto_mode})
}
componentDidMount() {
this.ws = new WebsocketClient(this.onWsStatusUpdate, this.onWsMessage)
this.setState({wsConnected: this.ws.api().isConnected()})
}
componentWillUnmount() {
if (this.ws !== undefined) {
this.ws.destroy()
}
}
private handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newState = !this.state.auto
tsgrainApi.setAutoMode({state: newState}).catch(handleApiError)
}
render() {
return (
<FormControlLabel
control={
<Switch
color="secondary"
checked={this.state.auto}
onChange={this.handleChange}
/>
}
label="Auto"
/>
)
}
}

View file

@ -0,0 +1,344 @@
import * as React from "react"
import {
Container,
Card,
CardContent,
Typography,
CardActions,
Button,
Box,
Grid,
LinearProgress,
LinearProgressProps,
} from "@mui/material"
import {
TimerOutlined as IconSchedule,
PowerSettingsNew as IconManual,
} from "@mui/icons-material"
import {Config, getConfig} from "../util/config"
import {WebsocketClient, WebsocketMessage} from "../util/websocket"
import {TaskList, Task} from "../tsgrain-client"
import {tsgrainApi} from "../util/apiUrls"
import {
dateToTimestamp,
getTimestamp,
handleApiError,
secondsToString,
} from "../util/functions"
function startZone(id: number) {
tsgrainApi.startTask({id: id}).catch(handleApiError)
}
function stopZone(id: number) {
tsgrainApi.stopTask({id: id}).catch(handleApiError)
}
type LinearProgressWithLabelProps = LinearProgressProps & {
label?: string
icon?: React.ReactElement
}
function LinearProgressWithLabel(props: LinearProgressWithLabelProps) {
return (
<Box sx={{display: "flex", alignItems: "center", width: "100%", my: 1}}>
<Box sx={{minWidth: 35, maxWidth: 120, overflow: "hidden"}}>
<div
style={{
display: "flex",
alignItems: "center",
flexWrap: "wrap",
}}
>
{props.icon}
<Typography variant="body2" color="text.secondary">
{props.label}
</Typography>
</div>
</Box>
<Box sx={{flexGrow: "1", ml: 1}}>
<LinearProgress variant="determinate" {...props} />
</Box>
</Box>
)
}
type TaskProgressProps = {
task: Task
taskListCliTimestamp: number
taskListSrvTimestamp: number
}
type TaskProgressState = {
timestamp: number
}
class TaskProgress extends React.Component<TaskProgressProps, TaskProgressState> {
private timerID?: number
constructor(props: TaskProgressProps) {
super(props)
this.state = {timestamp: getTimestamp()}
}
componentDidMount() {
this.timerID = setInterval(() => this.tick(), 1000)
}
componentWillUnmount() {
clearInterval(this.timerID)
}
tick() {
this.setState({
timestamp: getTimestamp(),
})
}
render() {
let icon
switch (this.props.task.source) {
case 0:
icon = <IconManual />
break
case 1:
icon = <IconSchedule />
break
default:
break
}
let progSeconds = 0,
progPercent = 0
if (this.props.task.datetime_started !== null) {
// Calculate the current server time based on current client time,
// client time at last TaskList update and server time at last TaskList update
const srvTimestamp =
this.state.timestamp -
this.props.taskListCliTimestamp +
this.props.taskListSrvTimestamp
const srvDuration =
this.props.task.datetime_finished - this.props.task.datetime_started
// If a task is restored from the database (after a server restart),
// it will continue at the left-off position.
// The 'Headstart' is the amount of irrigation time already completed
// in the previous server run. It needs to be added to the progress time.
const headstart = this.props.task.duration - srvDuration
progSeconds = srvTimestamp - this.props.task.datetime_started + headstart
// Cap progress in case the client is out of sync
progSeconds = Math.min(Math.max(progSeconds, 0), this.props.task.duration)
progPercent = (progSeconds / srvDuration) * 100
}
return (
<LinearProgressWithLabel
variant="determinate"
value={progPercent}
label={`${secondsToString(progSeconds)}/${secondsToString(
this.props.task.duration
)}`}
icon={icon}
/>
)
}
}
type TaskProgressListProps = {
tasks: Task[]
taskListCliTimestamp: number
taskListSrvTimestamp: number
}
const TaskProgressList = (props: TaskProgressListProps) => {
let items: React.ReactElement[] = []
props.tasks.forEach((task, i) => {
let icon
switch (task.source) {
case 0:
icon = <IconManual />
break
case 1:
icon = <IconSchedule />
break
default:
break
}
if (task.datetime_started !== null) {
}
items.push(
<TaskProgress
key={i}
task={task}
taskListCliTimestamp={props.taskListCliTimestamp}
taskListSrvTimestamp={props.taskListSrvTimestamp}
/>
)
})
return <Box>{items}</Box>
}
type ZoneButtonProps = {
start: boolean
disabled: boolean
zoneId: number
}
const ZoneButton = (props: ZoneButtonProps) => {
const clickFun = props.start ? startZone : stopZone
return (
<Button
size="small"
onClick={() => clickFun(props.zoneId)}
disabled={props.disabled}
>
{props.start ? "Start" : "Stop"}
</Button>
)
}
type ZoneCardProps = {
zoneId: number
tasks?: Task[]
taskListCliTimestamp: number
taskListSrvTimestamp: number
}
const ZoneCard = (props: ZoneCardProps) => {
const tasks = props.tasks === undefined ? [] : props.tasks
let runningTask
let hasManualTask = false
if (tasks.length > 0) {
if (tasks[0].datetime_started !== null) {
runningTask = tasks[0]
}
tasks.forEach((task) => {
if (task.source === 0) {
hasManualTask = true
}
})
}
return (
<Card sx={{width: 250}}>
<CardContent sx={{py: 1.5}}>
<Typography variant="h5" component="div">
Platz {props.zoneId}
</Typography>
</CardContent>
<CardContent sx={{py: 0}}>
<TaskProgressList
tasks={tasks}
taskListCliTimestamp={props.taskListCliTimestamp}
taskListSrvTimestamp={props.taskListSrvTimestamp}
/>
</CardContent>
<CardActions>
<ZoneButton
start={!hasManualTask}
disabled={false}
zoneId={props.zoneId}
/>
</CardActions>
</Card>
)
}
type Props = {}
type State = {
wsConnected: boolean
taskList: TaskList
tasksByZone: {[key: number]: Task[]}
taskListTimestamp: number
}
export default class Controls extends React.Component<Props, State> {
private cfg: Config
private ws?: WebsocketClient
constructor(props: Props) {
super(props)
this.cfg = getConfig()
this.state = {
wsConnected: false,
taskList: new WebsocketMessage(),
tasksByZone: {},
taskListTimestamp: getTimestamp(),
}
}
private onWsStatusUpdate = (wsConnected: boolean) => {
this.setState({wsConnected: wsConnected})
}
private onWsMessage = (msg: TaskList, date: Date) => {
const tbz: {[key: number]: Task[]} = {}
msg.tasks.forEach((task) => {
if (tbz[task.zone_id] === undefined) {
tbz[task.zone_id] = []
}
tbz[task.zone_id].push(task)
})
this.setState({
taskList: msg,
tasksByZone: tbz,
taskListTimestamp: dateToTimestamp(date),
})
}
componentDidMount() {
this.ws = new WebsocketClient(this.onWsStatusUpdate, this.onWsMessage)
this.setState({wsConnected: this.ws.api().isConnected()})
}
componentWillUnmount() {
if (this.ws !== undefined) {
this.ws.destroy()
}
}
render() {
let cards = []
for (let i = 1; i <= this.cfg.n_zones; i++) {
cards.push(
<Grid item key={i}>
<ZoneCard
zoneId={i}
tasks={this.state.tasksByZone[i]}
taskListCliTimestamp={this.state.taskListTimestamp}
taskListSrvTimestamp={this.state.taskList.now}
/>
</Grid>
)
}
return (
<Container maxWidth="md">
<Grid container spacing={2}>
{cards}
</Grid>
</Container>
)
}
}

View file

@ -0,0 +1,35 @@
import * as React from "react"
import {TextField, TextFieldProps} from "@mui/material"
import {parseTimeString, secondsToString} from "../util/functions"
type Props = {
value: number
onChange: (newValue: number) => void
textFieldProps: TextFieldProps
}
const DurationInput = (props: Props) => {
const [textValue, setTextValue] = React.useState(secondsToString(props.value))
const handleDurationUpdate = (e: React.ChangeEvent<HTMLInputElement>) => {
setTextValue(e.target.value)
props.onChange(parseTimeString(e.target.value))
}
const handleDurationBlur = (e: React.FocusEvent<HTMLInputElement>) => {
setTextValue(secondsToString(props.value))
}
return (
<TextField
{...props.textFieldProps}
value={textValue}
onChange={handleDurationUpdate}
onBlur={handleDurationBlur}
error={!(props.value > 0)}
/>
)
}
export default DurationInput

409
ui/src/components/Jobs.tsx Normal file
View file

@ -0,0 +1,409 @@
import * as React from "react"
import {
Box,
Card,
Chip,
CircularProgress,
Container,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
IconButton,
Fab,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
TextField,
FormControlLabel,
Checkbox,
Typography,
} from "@mui/material"
import {
Add as AddIcon,
Edit as EditIcon,
Delete as DeleteIcon,
Timer as PowerOnIcon,
TimerOff as PowerOffIcon,
} from "@mui/icons-material"
import {DateTimePicker, LocalizationProvider} from "@mui/lab"
import AdapterDateFns from "@mui/lab/AdapterDateFns"
import {de} from "date-fns/locale"
import {Job} from "../tsgrain-client"
import {tsgrainApi} from "../util/apiUrls"
import {
dateToTimestamp,
getTimestamp,
handleApiError,
secondsToString,
timestampToString,
} from "../util/functions"
import {getConfig} from "../util/config"
import DurationInput from "./DurationInput"
type ZoneSelectorProps = {
defaultSelection?: Set<number>
onChange?: (selection: Set<number>) => void
}
function ZoneSelector(props: ZoneSelectorProps) {
const [selection, setSelection] = React.useState(
props.defaultSelection || new Set([])
)
const handleChange = (zoneId: number) => {
const newSelection = new Set(selection)
if (newSelection.has(zoneId)) {
newSelection.delete(zoneId)
} else {
newSelection.add(zoneId)
}
setSelection(newSelection)
if (props.onChange) {
props.onChange(newSelection)
}
}
const nZones = getConfig().n_zones
let items: React.ReactElement[] = []
for (let i = 1; i <= nZones; i++) {
items.push(
<Checkbox
key={i}
sx={{p: 0, m: 1}}
icon={<Chip label={i} color="default" />}
checkedIcon={<Chip label={i} color="primary" />}
checked={selection.has(i)}
onChange={() => handleChange(i)}
/>
)
}
return (
<Box>
<Typography>Plätze</Typography>
{items}
</Box>
)
}
type JobEditorProps = {
job: Job
jobEdit: (job: Job) => void
getBtn: (handleClickOpen: () => void) => React.ReactElement
}
function JobEditor(props: JobEditorProps) {
const [open, setOpen] = React.useState(false)
const [zones, setZones] = React.useState(new Set(props.job.zones))
const [timestamp, setTimestamp] = React.useState<number | null>(props.job.date)
const [datetimeInput, setDatetimeInput] = React.useState<Date | null>(
new Date((timestamp || 0) * 1000)
)
const [duration, setDuration] = React.useState(props.job.duration)
const [repeat, setRepeat] = React.useState(props.job.repeat)
const handleClickOpen = () => {
setOpen(true)
}
const handleClose = () => {
setOpen(false)
}
const handleOK = () => {
// Validate
if (zones.size === 0) {
return
}
if (timestamp === null) {
return
}
if (!(duration > 0)) {
return
}
const job = props.job
job.zones = Array.from(zones)
job.date = timestamp
job.duration = duration
job.repeat = repeat
props.jobEdit(job)
handleClose()
}
const handleDateUpdate = (newValue: Date | null) => {
setDatetimeInput(newValue)
if (newValue === null || isNaN(newValue.getTime())) {
setTimestamp(null)
} else {
setTimestamp(dateToTimestamp(newValue))
}
}
return (
<Box sx={{display: "inline"}}>
{props.getBtn(handleClickOpen)}
<Dialog open={open} onClose={handleClose}>
<DialogTitle>
Zeitplan {props.job.id > 0 ? "bearbeiten" : "erstellen"}
</DialogTitle>
<DialogContent>
<ZoneSelector
defaultSelection={zones}
onChange={(selection) => setZones(selection)}
/>
<LocalizationProvider dateAdapter={AdapterDateFns} locale={de}>
<DateTimePicker
renderInput={(props) => (
<TextField
variant="standard"
fullWidth
margin="dense"
{...props}
/>
)}
label="Datum/Zeit"
value={datetimeInput}
onChange={handleDateUpdate}
mask="__.__.____ __:__"
/>
</LocalizationProvider>
<DurationInput
textFieldProps={{
label: "Dauer",
fullWidth: true,
margin: "dense",
variant: "standard",
}}
value={duration}
onChange={(v) => setDuration(v)}
/>
<FormControlLabel
control={
<Checkbox
checked={repeat}
onChange={() => setRepeat(!repeat)}
/>
}
label="Täglich wiederholen"
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Abbrechen</Button>
<Button onClick={handleOK}>OK</Button>
</DialogActions>
</Dialog>
</Box>
)
}
type JobTableProps = {
jobs: Job[]
jobEdit: (job: Job) => void
jobDelete: (jobId: number) => void
}
const JobTable = (props: JobTableProps) => {
return (
<TableContainer component={Paper}>
<Table sx={{minWidth: 650}} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Ein/Aus</TableCell>
<TableCell>Plätze</TableCell>
<TableCell>Datum/Zeit</TableCell>
<TableCell>Dauer</TableCell>
<TableCell>Wiederholen</TableCell>
<TableCell>Bearbeiten</TableCell>
</TableRow>
</TableHead>
<TableBody>
{props.jobs.map((job) => {
return (
<TableRow key={job.id}>
<TableCell>
<IconButton
onClick={() => {
job.enable = !job.enable
props.jobEdit(job)
}}
>
{job.enable ? (
<PowerOnIcon color="primary" />
) : (
<PowerOffIcon />
)}
</IconButton>
</TableCell>
<TableCell>{job.zones.join(", ")}</TableCell>
<TableCell>{timestampToString(job.date)}</TableCell>
<TableCell>{secondsToString(job.duration)}</TableCell>
<TableCell>
{job.repeat ? (
<Chip label="Ja" color="primary" />
) : (
<Chip label="Nein" color="error" />
)}
</TableCell>
<TableCell>
<JobEditor
job={job}
jobEdit={props.jobEdit}
getBtn={(handleClickOpen) => (
<IconButton
aria-label="edit"
onClick={handleClickOpen}
>
<EditIcon />
</IconButton>
)}
/>
<IconButton
aria-label="delete"
onClick={() => props.jobDelete(job.id)}
>
<DeleteIcon />
</IconButton>
</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
</TableContainer>
)
}
type JobsProps = {}
type JobsState = {
loaded: boolean
jobs: Job[]
}
export default class Jobs extends React.Component<JobsProps, JobsState> {
private updateTimeout: number | undefined
constructor(props: JobsProps) {
super(props)
this.state = {
loaded: false,
jobs: [],
}
}
private clearUpdateTimeout = () => {
if (this.updateTimeout !== undefined) {
window.clearTimeout(this.updateTimeout)
}
}
private updateJobs = () => {
this.clearUpdateTimeout()
this.setState({loaded: false})
tsgrainApi
.getJobs()
.then((response) => {
this.setState({loaded: true, jobs: response.data})
})
.catch((reason) => {
console.log("error fetching info", reason)
this.updateTimeout = window.setTimeout(this.updateJobs, 3000)
})
}
private jobEdit = (job: Job) => {
tsgrainApi
.updateJob(job)
.then((response) => {
this.updateJobs()
})
.catch(handleApiError)
}
private jobCreate = (job: Job) => {
tsgrainApi
.createJob(job)
.then((response) => {
this.updateJobs()
})
.catch(handleApiError)
}
private jobDelete = (jobId: number) => {
tsgrainApi
.deleteJob({id: jobId})
.then((response) => {
this.updateJobs()
})
.catch(handleApiError)
}
componentDidMount() {
this.updateJobs()
}
componentWillUnmount() {
this.clearUpdateTimeout()
}
render() {
return (
<Container maxWidth="md" sx={{px: 0}}>
{this.state.loaded ? (
<JobTable
jobs={this.state.jobs}
jobEdit={this.jobEdit}
jobDelete={this.jobDelete}
/>
) : (
<Card sx={{p: 1}}>
<Box sx={{display: "flex", justifyContent: "center"}}>
<CircularProgress />
</Box>
</Card>
)}
<JobEditor
job={{
id: 0,
date: getTimestamp() + 300,
duration: 300,
enable: true,
repeat: false,
zones: [],
}}
jobEdit={this.jobCreate}
getBtn={(handleClickOpen) => (
<Fab
sx={{
position: "absolute",
bottom: 32,
right: 32,
}}
color="primary"
aria-label="add"
onClick={handleClickOpen}
>
<AddIcon />
</Fab>
)}
/>
</Container>
)
}
}

View file

@ -0,0 +1,124 @@
import * as React from "react"
import {
AppBar,
Box,
Toolbar,
IconButton,
Typography,
Menu,
Container,
Button,
MenuItem,
Switch,
FormControlLabel,
} from "@mui/material"
import MenuIcon from "@mui/icons-material/Menu"
import {useLocation} from "wouter"
import {getConfig} from "../util/config"
import AutoModeSwitch from "./AutoMode"
const pages: {[key: string]: string} = {
Steuerung: "/",
Zeitpläne: "/jobs",
Einstellungen: "/settings",
}
const Navbar = () => {
const [anchorElNav, setAnchorElNav] = React.useState<Element | null>(null)
const [location, setLocation] = useLocation()
const handleOpenNavMenu = (event: React.MouseEvent) => {
setAnchorElNav(event.currentTarget)
}
const handleCloseNavMenu = () => {
setAnchorElNav(null)
}
const handleTabClick = (page: string) => {
setLocation(pages[page])
handleCloseNavMenu()
}
return (
<AppBar position="static">
<Container maxWidth="xl">
<Toolbar disableGutters>
{/* Desktop title */}
<Typography
variant="h6"
noWrap
component="div"
sx={{mr: 2, display: {xs: "none", md: "flex"}}}
>
TSGRain
</Typography>
<Box sx={{flexGrow: 1, display: {xs: "flex", md: "none"}}}>
<IconButton
size="large"
aria-label="account of current user"
aria-controls="menu-appbar"
aria-haspopup="true"
onClick={handleOpenNavMenu}
color="inherit"
>
<MenuIcon />
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorElNav}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
keepMounted
transformOrigin={{
vertical: "top",
horizontal: "left",
}}
open={Boolean(anchorElNav)}
onClose={handleCloseNavMenu}
sx={{
display: {xs: "block", md: "none"},
}}
>
{Object.keys(pages).map((page) => (
<MenuItem
key={page}
onClick={() => handleTabClick(page)}
>
<Typography textAlign="center">{page}</Typography>
</MenuItem>
))}
</Menu>
</Box>
{/* Mobile title */}
<Typography
variant="h6"
noWrap
component="div"
sx={{flexGrow: 1, display: {xs: "flex", md: "none"}}}
>
TSGRain
</Typography>
<Box sx={{flexGrow: 1, display: {xs: "none", md: "flex"}}}>
{Object.keys(pages).map((page) => (
<Button
key={page}
onClick={() => handleTabClick(page)}
sx={{my: 2, color: "white", display: "block"}}
>
{page}
</Button>
))}
</Box>
<Box>
<AutoModeSwitch />
</Box>
</Toolbar>
</Container>
</AppBar>
)
}
export default Navbar

View file

@ -0,0 +1,47 @@
import * as React from "react"
import {Box, Typography} from "@mui/material"
import {WebsocketClient} from "../util/websocket"
type Props = {}
type State = {
wsConnected: boolean
}
export default class NoConnectionBar extends React.Component<Props, State> {
private ws?: WebsocketClient
constructor(props: Props) {
super(props)
this.state = {
wsConnected: false,
}
}
private onWsStatusUpdate = (wsConnected: boolean) => {
this.setState({wsConnected: wsConnected})
}
componentDidMount() {
this.ws = new WebsocketClient(this.onWsStatusUpdate)
this.setState({wsConnected: this.ws.api().isConnected()})
}
componentWillUnmount() {
if (this.ws !== undefined) {
this.ws.destroy()
}
}
render() {
if (!this.state.wsConnected) {
return (
<Box sx={{bgcolor: "warning.main"}}>
<Typography align="center">Keine Verbindung</Typography>
</Box>
)
}
return null
}
}

View file

@ -0,0 +1,315 @@
import * as React from "react"
import {
Box,
Button,
Card,
CardActions,
CardContent,
CircularProgress,
Container,
TextField,
Typography,
} from "@mui/material"
import {tsgrainApi} from "../util/apiUrls"
import {
dateToInputString,
dateToTimestamp,
handleApiError,
inputStringToDate,
timestampToDate,
} from "../util/functions"
import {getConfig} from "../util/config"
import DurationInput from "./DurationInput"
type IrrigationSettingsProps = {}
type IrrigationSettingsState = {
loaded: boolean
irrigationTime: number
}
class IrrigationSettings extends React.Component<
IrrigationSettingsProps,
IrrigationSettingsState
> {
private updateTimeout?: number
private initialIrrigationTime?: number
constructor(props: IrrigationSettingsProps) {
super(props)
this.state = {
loaded: false,
irrigationTime: 0,
}
}
private clearUpdateTimeout = () => {
if (this.updateTimeout !== undefined) {
window.clearTimeout(this.updateTimeout)
}
}
private updateSettings = () => {
this.clearUpdateTimeout()
this.setState({loaded: false})
tsgrainApi
.getDefaultIrrigationTime()
.then((response) => {
this.initialIrrigationTime = response.data.time
this.setState({
loaded: true,
irrigationTime: this.initialIrrigationTime,
})
})
.catch((reason) => {
console.log("error fetching irrigation settings", reason)
this.updateTimeout = window.setTimeout(this.updateSettings, 3000)
})
}
private showSubmit(): boolean {
return (
this.state.irrigationTime !== this.initialIrrigationTime &&
this.state.irrigationTime > 0
)
}
private submitSettings = () => {
const irrigationTime = this.state.irrigationTime
tsgrainApi
.setDefaultIrrigationTime({time: irrigationTime})
.then((response) => {
this.setState({irrigationTime: irrigationTime})
})
.catch(handleApiError)
}
componentDidMount() {
this.updateSettings()
}
componentWillUnmount() {
this.clearUpdateTimeout()
}
renderSettings() {
return (
<Box>
<Card sx={{mb: 2}}>
<CardContent>
<Typography variant="h5">Bewässerungssteuerung</Typography>
<DurationInput
textFieldProps={{
label: "Manuelle Bewässerungszeit",
fullWidth: true,
margin: "dense",
variant: "standard",
}}
value={this.state.irrigationTime}
onChange={(v) => this.setState({irrigationTime: v})}
/>
</CardContent>
{this.showSubmit() ? (
<CardActions>
<Button size="small" onClick={this.submitSettings}>
OK
</Button>
</CardActions>
) : null}
</Card>
</Box>
)
}
render() {
if (this.state.loaded) return this.renderSettings()
return (
<Card sx={{p: 1}}>
<Box sx={{display: "flex", justifyContent: "center"}}>
<CircularProgress />
</Box>
</Card>
)
}
}
type TimeSettingsProps = {}
type TimeSettingsState = {
loaded: boolean
date: Date
dateStr: string
timezone: string
}
class TimeSettings extends React.Component<TimeSettingsProps, TimeSettingsState> {
private updateTimeout?: number
private timerID?: number
private initialDate?: Date
private initialTimezone?: string
constructor(props: TimeSettingsProps) {
super(props)
const now = new Date()
this.state = {
loaded: false,
date: now,
dateStr: dateToInputString(now),
timezone: "",
}
}
private clearUpdateTimeout = () => {
if (this.updateTimeout !== undefined) {
window.clearTimeout(this.updateTimeout)
}
}
private updateSettings = () => {
this.clearUpdateTimeout()
this.setState({loaded: false})
tsgrainApi
.getSystemTime()
.then((response) => {
this.initialDate = timestampToDate(response.data.time)
this.initialTimezone = response.data.timezone
this.setState({
loaded: true,
dateStr: dateToInputString(this.initialDate),
timezone: this.initialTimezone,
})
this.timerID = setInterval(this.tick, 1000)
})
.catch((reason) => {
console.log("error fetching time settings", reason)
this.updateTimeout = window.setTimeout(this.updateSettings, 3000)
})
}
private tick = () => {
if (this.state.date !== null) {
const newDate = new Date(this.state.date.getTime() + 1000)
this.setState({
date: newDate,
dateStr: dateToInputString(newDate),
})
}
}
private showSubmit(): boolean {
return (
this.state.date !== this.initialDate ||
this.state.timezone !== this.initialTimezone
)
}
private submitSettings = () => {
const date = this.state.date
if (date !== this.initialDate) {
tsgrainApi
.setSystemTime({time: dateToTimestamp(date)})
.then((response) => {
this.updateSettings()
})
.catch(handleApiError)
}
const tz = this.state.timezone
if (tz !== this.initialTimezone) {
tsgrainApi
.setSystemTimezone({timezone: this.state.timezone})
.then((response) => {
this.initialTimezone = tz
this.setState({timezone: tz})
})
.catch(handleApiError)
}
}
componentDidMount() {
this.updateSettings()
}
componentWillUnmount() {
this.clearUpdateTimeout()
clearInterval(this.timerID)
}
renderSettings() {
return (
<Card sx={{mb: 2}}>
<CardContent>
<Typography variant="h5">Datum/Uhrzeit</Typography>
<TextField
label="Datum/Zeit"
fullWidth
margin="dense"
variant="standard"
type="datetime-local"
value={this.state.dateStr}
onChange={(e) => {
clearInterval(this.timerID)
this.setState({
dateStr: e.target.value,
date: inputStringToDate(e.target.value),
})
}}
/>
<TextField
label="Zeitzone"
fullWidth
margin="dense"
variant="standard"
value={this.state.timezone}
onChange={(e) => this.setState({timezone: e.target.value})}
/>
</CardContent>
{this.showSubmit() ? (
<CardActions>
<Button size="small" onClick={this.submitSettings}>
OK
</Button>
</CardActions>
) : null}
</Card>
)
}
render() {
if (this.state.loaded) return this.renderSettings()
return (
<Card sx={{p: 1}}>
<Box sx={{display: "flex", justifyContent: "center"}}>
<CircularProgress />
</Box>
</Card>
)
}
}
const Settings = () => {
return (
<Container maxWidth="md" sx={{px: 0}}>
<IrrigationSettings />
<TimeSettings />
<Card sx={{mb: 2, p: 1, textAlign: "center"}}>
TSGRain WebUI Version: {getConfig().version}
</Card>
</Container>
)
}
export default Settings

View file

@ -1,10 +0,0 @@
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>
}
}

View file

@ -1,5 +1,17 @@
import {render} from "preact"
import App from "./components/app"
import "./style/index.scss"
import * as React from "react"
import ReactDOM from "react-dom"
import {CssBaseline} from "@mui/material"
import {ThemeProvider} from "@mui/material/styles"
import App from "./App"
import theme from "./theme"
render(<App />, document.getElementById("app")!)
ReactDOM.render(
<React.StrictMode>
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<App />
</ThemeProvider>
</React.StrictMode>,
document.getElementById("app")
)

1
ui/src/preact.d.ts vendored
View file

@ -1 +0,0 @@
import JSX = preact.JSX

19
ui/src/theme.ts Normal file
View file

@ -0,0 +1,19 @@
import {createTheme} from "@mui/material/styles"
import {red} from "@mui/material/colors"
// Create a theme instance.
const theme = createTheme({
palette: {
primary: {
main: "#357a38",
},
secondary: {
main: "#ff3d00",
},
error: {
main: red.A400,
},
},
})
export default theme

View file

@ -1,6 +1,5 @@
.gitignore
.npmignore
.openapi-generator-ignore
api.ts
base.ts
common.ts

View file

@ -1 +1 @@
5.3.1
5.4.0

View file

@ -50,25 +50,6 @@ export interface AutoMode {
*/
state: boolean
}
/**
* Aktuelle Systemzeit/Zeitzone
* @export
* @interface ConfigTime
*/
export interface ConfigTime {
/**
* Aktuelle Systemzeit
* @type {number}
* @memberof ConfigTime
*/
time: number
/**
* Aktuelle Zeitzone
* @type {string}
* @memberof ConfigTime
*/
timezone: string
}
/**
* Manuelle Bewässerungszeit in Sekunden
* @export
@ -82,50 +63,6 @@ export interface DefaultIrrigationTime {
*/
time: number
}
/**
*
* @export
* @interface InlineObject
*/
export interface InlineObject {
/**
*
* @type {boolean}
* @memberof InlineObject
*/
state: boolean
}
/**
*
* @export
* @interface InlineObject1
*/
export interface InlineObject1 {
/**
* Aufgabe stoppen/aus der Warteschlange entfernen, wenn sie bereits läuft oder sich in der Warteschlange befindet.
* @type {boolean}
* @memberof InlineObject1
*/
cancelling: boolean
/**
* Bewässerungsdauer in Sekunden (0: Standarddauer)
* @type {number}
* @memberof InlineObject1
*/
duration: number
/**
* Aufgabe in die Warteschlange einreihen, wenn sie nicht sofort ausgeführt werden kann.
* @type {boolean}
* @memberof InlineObject1
*/
queuing: boolean
/**
* Nummer der Bewässerungszone
* @type {number}
* @memberof InlineObject1
*/
zone_id: number
}
/**
* Job stellt einen Bewässerungszeitplan dar.
* @export
@ -263,6 +200,25 @@ export interface StatusMessage {
*/
success: boolean
}
/**
* Aktuelle Systemzeit/Zeitzone
* @export
* @interface SystemTime
*/
export interface SystemTime {
/**
* Aktuelle Systemzeit
* @type {number}
* @memberof SystemTime
*/
time: number
/**
* Aktuelle Zeitzone
* @type {string}
* @memberof SystemTime
*/
timezone: string
}
/**
* Task stellt eine Bewässerungsaufgabe dar.
* @export
@ -326,23 +282,43 @@ export interface TaskList {
tasks: Array<Task>
}
/**
* TaskRequestResult wird beim Starten eines Tasks zurückgegeben
* Zeitstempel
* @export
* @interface TaskRequestResult
* @interface Timestamp
*/
export interface TaskRequestResult {
export interface Timestamp {
/**
*
* @type {boolean}
* @memberof TaskRequestResult
* @type {number}
* @memberof Timestamp
*/
Started?: boolean
time: number
}
/**
* Systemzeitzone
* @export
* @interface Timezone
*/
export interface Timezone {
/**
*
* @type {boolean}
* @memberof TaskRequestResult
* @type {string}
* @memberof Timezone
*/
Stopped?: boolean
timezone: string
}
/**
*
* @export
* @interface ZoneID
*/
export interface ZoneID {
/**
*
* @type {number}
* @memberof ZoneID
*/
id?: number
}
/**
@ -400,12 +376,12 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati
/**
*
* @summary Lösche einen gespeicherten Zeitplan.
* @param {number} [id]
* @param {JobID} [jobId] JobID
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
deleteJob: async (
id?: number,
jobId?: JobID,
options: AxiosRequestConfig = {}
): Promise<RequestArgs> => {
const localVarPath = `/job`
@ -435,7 +411,7 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati
...options.headers,
}
localVarRequestOptions.data = serializeDataIfNeeded(
id,
jobId,
localVarRequestOptions,
configuration
)
@ -478,40 +454,6 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati
options: localVarRequestOptions,
}
},
/**
* Rufe die aktuelle Systemzeit/Zeitzone ab
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getConfigTime: async (
options: AxiosRequestConfig = {}
): Promise<RequestArgs> => {
const localVarPath = `/config/time`
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL)
let baseOptions
if (configuration) {
baseOptions = configuration.baseOptions
}
const localVarRequestOptions = {method: "GET", ...baseOptions, ...options}
const localVarHeaderParameter = {} as any
const localVarQueryParameter = {} as any
setSearchParams(localVarUrlObj, localVarQueryParameter)
let headersFromBaseOptions =
baseOptions && baseOptions.headers ? baseOptions.headers : {}
localVarRequestOptions.headers = {
...localVarHeaderParameter,
...headersFromBaseOptions,
...options.headers,
}
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
}
},
/**
*
* @summary Rufe die Standardzeit bei manueller Bewässerung ab.
@ -623,14 +565,81 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati
options: localVarRequestOptions,
}
},
/**
* Rufe die aktuelle Systemzeit/Zeitzone ab
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getSystemTime: async (
options: AxiosRequestConfig = {}
): Promise<RequestArgs> => {
const localVarPath = `/config/time`
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL)
let baseOptions
if (configuration) {
baseOptions = configuration.baseOptions
}
const localVarRequestOptions = {method: "GET", ...baseOptions, ...options}
const localVarHeaderParameter = {} as any
const localVarQueryParameter = {} as any
setSearchParams(localVarUrlObj, localVarQueryParameter)
let headersFromBaseOptions =
baseOptions && baseOptions.headers ? baseOptions.headers : {}
localVarRequestOptions.headers = {
...localVarHeaderParameter,
...headersFromBaseOptions,
...options.headers,
}
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
}
},
/**
*
* @summary Rufe alle momentan laufenden Bewässerungsaufgaben ab.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getTasks: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/tasks`
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL)
let baseOptions
if (configuration) {
baseOptions = configuration.baseOptions
}
const localVarRequestOptions = {method: "GET", ...baseOptions, ...options}
const localVarHeaderParameter = {} as any
const localVarQueryParameter = {} as any
setSearchParams(localVarUrlObj, localVarQueryParameter)
let headersFromBaseOptions =
baseOptions && baseOptions.headers ? baseOptions.headers : {}
localVarRequestOptions.headers = {
...localVarHeaderParameter,
...headersFromBaseOptions,
...options.headers,
}
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
}
},
/**
* Automatikmodus aktivieren/deaktivieren
* @param {InlineObject} [state]
* @param {AutoMode} [state] Zustand des Automatikmodus
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
setAutoMode: async (
state?: InlineObject,
state?: AutoMode,
options: AxiosRequestConfig = {}
): Promise<RequestArgs> => {
const localVarPath = `/config/auto`
@ -666,49 +675,6 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati
options: localVarRequestOptions,
}
},
/**
* Automatikmodus aktivieren/deaktivieren
* @param {ConfigTime} [configTime]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
setConfigTime: async (
configTime?: ConfigTime,
options: AxiosRequestConfig = {}
): Promise<RequestArgs> => {
const localVarPath = `/config/time`
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL)
let baseOptions
if (configuration) {
baseOptions = configuration.baseOptions
}
const localVarRequestOptions = {method: "POST", ...baseOptions, ...options}
const localVarHeaderParameter = {} as any
const localVarQueryParameter = {} as any
localVarHeaderParameter["Content-Type"] = "application/json"
setSearchParams(localVarUrlObj, localVarQueryParameter)
let headersFromBaseOptions =
baseOptions && baseOptions.headers ? baseOptions.headers : {}
localVarRequestOptions.headers = {
...localVarHeaderParameter,
...headersFromBaseOptions,
...options.headers,
}
localVarRequestOptions.data = serializeDataIfNeeded(
configTime,
localVarRequestOptions,
configuration
)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
}
},
/**
*
* @summary Setze die die Standardzeit bei manueller Bewässerung.
@ -754,16 +720,16 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati
}
},
/**
* Starte/stoppe manuell eine neue Bewässerungsaufgabe
* @param {InlineObject1} [taskRequest]
* Systemzeit einstellen
* @param {Timestamp} [timestamp]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
startManualTask: async (
taskRequest?: InlineObject1,
setSystemTime: async (
timestamp?: Timestamp,
options: AxiosRequestConfig = {}
): Promise<RequestArgs> => {
const localVarPath = `/task/manual`
const localVarPath = `/config/time`
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL)
let baseOptions
@ -786,7 +752,137 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati
...options.headers,
}
localVarRequestOptions.data = serializeDataIfNeeded(
taskRequest,
timestamp,
localVarRequestOptions,
configuration
)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
}
},
/**
* Systemzeitzone einstellen
* @param {Timezone} [timezone]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
setSystemTimezone: async (
timezone?: Timezone,
options: AxiosRequestConfig = {}
): Promise<RequestArgs> => {
const localVarPath = `/config/timezone`
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL)
let baseOptions
if (configuration) {
baseOptions = configuration.baseOptions
}
const localVarRequestOptions = {method: "POST", ...baseOptions, ...options}
const localVarHeaderParameter = {} as any
const localVarQueryParameter = {} as any
localVarHeaderParameter["Content-Type"] = "application/json"
setSearchParams(localVarUrlObj, localVarQueryParameter)
let headersFromBaseOptions =
baseOptions && baseOptions.headers ? baseOptions.headers : {}
localVarRequestOptions.headers = {
...localVarHeaderParameter,
...headersFromBaseOptions,
...options.headers,
}
localVarRequestOptions.data = serializeDataIfNeeded(
timezone,
localVarRequestOptions,
configuration
)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
}
},
/**
* Starte eine neue manuelle Bewässerungsaufgabe mit der eingestellten Standardzeit.
* @param {ZoneID} [zoneId] ZoneID
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
startTask: async (
zoneId?: ZoneID,
options: AxiosRequestConfig = {}
): Promise<RequestArgs> => {
const localVarPath = `/task/start`
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL)
let baseOptions
if (configuration) {
baseOptions = configuration.baseOptions
}
const localVarRequestOptions = {method: "POST", ...baseOptions, ...options}
const localVarHeaderParameter = {} as any
const localVarQueryParameter = {} as any
localVarHeaderParameter["Content-Type"] = "application/json"
setSearchParams(localVarUrlObj, localVarQueryParameter)
let headersFromBaseOptions =
baseOptions && baseOptions.headers ? baseOptions.headers : {}
localVarRequestOptions.headers = {
...localVarHeaderParameter,
...headersFromBaseOptions,
...options.headers,
}
localVarRequestOptions.data = serializeDataIfNeeded(
zoneId,
localVarRequestOptions,
configuration
)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
}
},
/**
*
* @summary Stoppe eine manuelle Bewässerungsaufgabe.
* @param {ZoneID} [zoneId] ZoneID
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
stopTask: async (
zoneId?: ZoneID,
options: AxiosRequestConfig = {}
): Promise<RequestArgs> => {
const localVarPath = `/task/stop`
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL)
let baseOptions
if (configuration) {
baseOptions = configuration.baseOptions
}
const localVarRequestOptions = {method: "POST", ...baseOptions, ...options}
const localVarHeaderParameter = {} as any
const localVarQueryParameter = {} as any
localVarHeaderParameter["Content-Type"] = "application/json"
setSearchParams(localVarUrlObj, localVarQueryParameter)
let headersFromBaseOptions =
baseOptions && baseOptions.headers ? baseOptions.headers : {}
localVarRequestOptions.headers = {
...localVarHeaderParameter,
...headersFromBaseOptions,
...options.headers,
}
localVarRequestOptions.data = serializeDataIfNeeded(
zoneId,
localVarRequestOptions,
configuration
)
@ -877,18 +973,18 @@ export const DefaultApiFp = function (configuration?: Configuration) {
/**
*
* @summary Lösche einen gespeicherten Zeitplan.
* @param {number} [id]
* @param {JobID} [jobId] JobID
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async deleteJob(
id?: number,
jobId?: JobID,
options?: AxiosRequestConfig
): Promise<
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<StatusMessage>
> {
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteJob(
id,
jobId,
options
)
return createRequestFunction(
@ -919,26 +1015,6 @@ export const DefaultApiFp = function (configuration?: Configuration) {
configuration
)
},
/**
* Rufe die aktuelle Systemzeit/Zeitzone ab
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getConfigTime(
options?: AxiosRequestConfig
): Promise<
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ConfigTime>
> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getConfigTime(
options
)
return createRequestFunction(
localVarAxiosArgs,
globalAxios,
BASE_PATH,
configuration
)
},
/**
*
* @summary Rufe die Standardzeit bei manueller Bewässerung ab.
@ -1004,19 +1080,16 @@ export const DefaultApiFp = function (configuration?: Configuration) {
)
},
/**
* Automatikmodus aktivieren/deaktivieren
* @param {InlineObject} [state]
* Rufe die aktuelle Systemzeit/Zeitzone ab
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async setAutoMode(
state?: InlineObject,
async getSystemTime(
options?: AxiosRequestConfig
): Promise<
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<StatusMessage>
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SystemTime>
> {
const localVarAxiosArgs = await localVarAxiosParamCreator.setAutoMode(
state,
const localVarAxiosArgs = await localVarAxiosParamCreator.getSystemTime(
options
)
return createRequestFunction(
@ -1027,19 +1100,38 @@ export const DefaultApiFp = function (configuration?: Configuration) {
)
},
/**
* Automatikmodus aktivieren/deaktivieren
* @param {ConfigTime} [configTime]
*
* @summary Rufe alle momentan laufenden Bewässerungsaufgaben ab.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async setConfigTime(
configTime?: ConfigTime,
async getTasks(
options?: AxiosRequestConfig
): Promise<
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<TaskList>
> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getTasks(options)
return createRequestFunction(
localVarAxiosArgs,
globalAxios,
BASE_PATH,
configuration
)
},
/**
* Automatikmodus aktivieren/deaktivieren
* @param {AutoMode} [state] Zustand des Automatikmodus
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async setAutoMode(
state?: AutoMode,
options?: AxiosRequestConfig
): Promise<
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<StatusMessage>
> {
const localVarAxiosArgs = await localVarAxiosParamCreator.setConfigTime(
configTime,
const localVarAxiosArgs = await localVarAxiosParamCreator.setAutoMode(
state,
options
)
return createRequestFunction(
@ -1075,19 +1167,89 @@ export const DefaultApiFp = function (configuration?: Configuration) {
)
},
/**
* Starte/stoppe manuell eine neue Bewässerungsaufgabe
* @param {InlineObject1} [taskRequest]
* Systemzeit einstellen
* @param {Timestamp} [timestamp]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async startManualTask(
taskRequest?: InlineObject1,
async setSystemTime(
timestamp?: Timestamp,
options?: AxiosRequestConfig
): Promise<
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<StatusMessage>
> {
const localVarAxiosArgs = await localVarAxiosParamCreator.startManualTask(
taskRequest,
const localVarAxiosArgs = await localVarAxiosParamCreator.setSystemTime(
timestamp,
options
)
return createRequestFunction(
localVarAxiosArgs,
globalAxios,
BASE_PATH,
configuration
)
},
/**
* Systemzeitzone einstellen
* @param {Timezone} [timezone]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async setSystemTimezone(
timezone?: Timezone,
options?: AxiosRequestConfig
): Promise<
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<StatusMessage>
> {
const localVarAxiosArgs = await localVarAxiosParamCreator.setSystemTimezone(
timezone,
options
)
return createRequestFunction(
localVarAxiosArgs,
globalAxios,
BASE_PATH,
configuration
)
},
/**
* Starte eine neue manuelle Bewässerungsaufgabe mit der eingestellten Standardzeit.
* @param {ZoneID} [zoneId] ZoneID
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async startTask(
zoneId?: ZoneID,
options?: AxiosRequestConfig
): Promise<
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<StatusMessage>
> {
const localVarAxiosArgs = await localVarAxiosParamCreator.startTask(
zoneId,
options
)
return createRequestFunction(
localVarAxiosArgs,
globalAxios,
BASE_PATH,
configuration
)
},
/**
*
* @summary Stoppe eine manuelle Bewässerungsaufgabe.
* @param {ZoneID} [zoneId] ZoneID
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async stopTask(
zoneId?: ZoneID,
options?: AxiosRequestConfig
): Promise<
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<StatusMessage>
> {
const localVarAxiosArgs = await localVarAxiosParamCreator.stopTask(
zoneId,
options
)
return createRequestFunction(
@ -1150,13 +1312,13 @@ export const DefaultApiFactory = function (
/**
*
* @summary Lösche einen gespeicherten Zeitplan.
* @param {number} [id]
* @param {JobID} [jobId] JobID
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
deleteJob(id?: number, options?: any): AxiosPromise<StatusMessage> {
deleteJob(jobId?: JobID, options?: any): AxiosPromise<StatusMessage> {
return localVarFp
.deleteJob(id, options)
.deleteJob(jobId, options)
.then((request) => request(axios, basePath))
},
/**
@ -1170,16 +1332,6 @@ export const DefaultApiFactory = function (
.getAutoMode(options)
.then((request) => request(axios, basePath))
},
/**
* Rufe die aktuelle Systemzeit/Zeitzone ab
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getConfigTime(options?: any): AxiosPromise<ConfigTime> {
return localVarFp
.getConfigTime(options)
.then((request) => request(axios, basePath))
},
/**
*
* @summary Rufe die Standardzeit bei manueller Bewässerung ab.
@ -1215,28 +1367,35 @@ export const DefaultApiFactory = function (
.then((request) => request(axios, basePath))
},
/**
* Automatikmodus aktivieren/deaktivieren
* @param {InlineObject} [state]
* Rufe die aktuelle Systemzeit/Zeitzone ab
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
setAutoMode(state?: InlineObject, options?: any): AxiosPromise<StatusMessage> {
getSystemTime(options?: any): AxiosPromise<SystemTime> {
return localVarFp
.setAutoMode(state, options)
.getSystemTime(options)
.then((request) => request(axios, basePath))
},
/**
*
* @summary Rufe alle momentan laufenden Bewässerungsaufgaben ab.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getTasks(options?: any): AxiosPromise<TaskList> {
return localVarFp
.getTasks(options)
.then((request) => request(axios, basePath))
},
/**
* Automatikmodus aktivieren/deaktivieren
* @param {ConfigTime} [configTime]
* @param {AutoMode} [state] Zustand des Automatikmodus
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
setConfigTime(
configTime?: ConfigTime,
options?: any
): AxiosPromise<StatusMessage> {
setAutoMode(state?: AutoMode, options?: any): AxiosPromise<StatusMessage> {
return localVarFp
.setConfigTime(configTime, options)
.setAutoMode(state, options)
.then((request) => request(axios, basePath))
},
/**
@ -1255,17 +1414,54 @@ export const DefaultApiFactory = function (
.then((request) => request(axios, basePath))
},
/**
* Starte/stoppe manuell eine neue Bewässerungsaufgabe
* @param {InlineObject1} [taskRequest]
* Systemzeit einstellen
* @param {Timestamp} [timestamp]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
startManualTask(
taskRequest?: InlineObject1,
setSystemTime(
timestamp?: Timestamp,
options?: any
): AxiosPromise<StatusMessage> {
return localVarFp
.startManualTask(taskRequest, options)
.setSystemTime(timestamp, options)
.then((request) => request(axios, basePath))
},
/**
* Systemzeitzone einstellen
* @param {Timezone} [timezone]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
setSystemTimezone(
timezone?: Timezone,
options?: any
): AxiosPromise<StatusMessage> {
return localVarFp
.setSystemTimezone(timezone, options)
.then((request) => request(axios, basePath))
},
/**
* Starte eine neue manuelle Bewässerungsaufgabe mit der eingestellten Standardzeit.
* @param {ZoneID} [zoneId] ZoneID
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
startTask(zoneId?: ZoneID, options?: any): AxiosPromise<StatusMessage> {
return localVarFp
.startTask(zoneId, options)
.then((request) => request(axios, basePath))
},
/**
*
* @summary Stoppe eine manuelle Bewässerungsaufgabe.
* @param {ZoneID} [zoneId] ZoneID
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
stopTask(zoneId?: ZoneID, options?: any): AxiosPromise<StatusMessage> {
return localVarFp
.stopTask(zoneId, options)
.then((request) => request(axios, basePath))
},
/**
@ -1307,14 +1503,14 @@ export class DefaultApi extends BaseAPI {
/**
*
* @summary Lösche einen gespeicherten Zeitplan.
* @param {number} [id]
* @param {JobID} [jobId] JobID
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof DefaultApi
*/
public deleteJob(id?: number, options?: AxiosRequestConfig) {
public deleteJob(jobId?: JobID, options?: AxiosRequestConfig) {
return DefaultApiFp(this.configuration)
.deleteJob(id, options)
.deleteJob(jobId, options)
.then((request) => request(this.axios, this.basePath))
}
@ -1331,18 +1527,6 @@ export class DefaultApi extends BaseAPI {
.then((request) => request(this.axios, this.basePath))
}
/**
* Rufe die aktuelle Systemzeit/Zeitzone ab
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof DefaultApi
*/
public getConfigTime(options?: AxiosRequestConfig) {
return DefaultApiFp(this.configuration)
.getConfigTime(options)
.then((request) => request(this.axios, this.basePath))
}
/**
*
* @summary Rufe die Standardzeit bei manueller Bewässerung ab.
@ -1384,28 +1568,40 @@ export class DefaultApi extends BaseAPI {
}
/**
* Automatikmodus aktivieren/deaktivieren
* @param {InlineObject} [state]
* Rufe die aktuelle Systemzeit/Zeitzone ab
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof DefaultApi
*/
public setAutoMode(state?: InlineObject, options?: AxiosRequestConfig) {
public getSystemTime(options?: AxiosRequestConfig) {
return DefaultApiFp(this.configuration)
.setAutoMode(state, options)
.getSystemTime(options)
.then((request) => request(this.axios, this.basePath))
}
/**
*
* @summary Rufe alle momentan laufenden Bewässerungsaufgaben ab.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof DefaultApi
*/
public getTasks(options?: AxiosRequestConfig) {
return DefaultApiFp(this.configuration)
.getTasks(options)
.then((request) => request(this.axios, this.basePath))
}
/**
* Automatikmodus aktivieren/deaktivieren
* @param {ConfigTime} [configTime]
* @param {AutoMode} [state] Zustand des Automatikmodus
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof DefaultApi
*/
public setConfigTime(configTime?: ConfigTime, options?: AxiosRequestConfig) {
public setAutoMode(state?: AutoMode, options?: AxiosRequestConfig) {
return DefaultApiFp(this.configuration)
.setConfigTime(configTime, options)
.setAutoMode(state, options)
.then((request) => request(this.axios, this.basePath))
}
@ -1427,15 +1623,55 @@ export class DefaultApi extends BaseAPI {
}
/**
* Starte/stoppe manuell eine neue Bewässerungsaufgabe
* @param {InlineObject1} [taskRequest]
* Systemzeit einstellen
* @param {Timestamp} [timestamp]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof DefaultApi
*/
public startManualTask(taskRequest?: InlineObject1, options?: AxiosRequestConfig) {
public setSystemTime(timestamp?: Timestamp, options?: AxiosRequestConfig) {
return DefaultApiFp(this.configuration)
.startManualTask(taskRequest, options)
.setSystemTime(timestamp, options)
.then((request) => request(this.axios, this.basePath))
}
/**
* Systemzeitzone einstellen
* @param {Timezone} [timezone]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof DefaultApi
*/
public setSystemTimezone(timezone?: Timezone, options?: AxiosRequestConfig) {
return DefaultApiFp(this.configuration)
.setSystemTimezone(timezone, options)
.then((request) => request(this.axios, this.basePath))
}
/**
* Starte eine neue manuelle Bewässerungsaufgabe mit der eingestellten Standardzeit.
* @param {ZoneID} [zoneId] ZoneID
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof DefaultApi
*/
public startTask(zoneId?: ZoneID, options?: AxiosRequestConfig) {
return DefaultApiFp(this.configuration)
.startTask(zoneId, options)
.then((request) => request(this.axios, this.basePath))
}
/**
*
* @summary Stoppe eine manuelle Bewässerungsaufgabe.
* @param {ZoneID} [zoneId] ZoneID
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof DefaultApi
*/
public stopTask(zoneId?: ZoneID, options?: AxiosRequestConfig) {
return DefaultApiFp(this.configuration)
.stopTask(zoneId, options)
.then((request) => request(this.axios, this.basePath))
}

View file

@ -15,6 +15,6 @@ let apicfg = new Configuration({
basePath: apiUrl,
})
const sebraucApi = new DefaultApi(apicfg)
const tsgrainApi = new DefaultApi(apicfg)
export {apiUrl, wsUrl, sebraucApi}
export {apiUrl, wsUrl, tsgrainApi}

View file

@ -19,9 +19,8 @@ export function getConfig(): Config {
return window.config
}
console.error("App config not found")
return {
version: "dev",
n_zones: 0,
n_zones: 7,
}
}

View file

@ -1,18 +1,120 @@
import {format, parse, parseISO} from "date-fns"
const S_YEAR = 31536000
const S_DAY = 86400
const S_HOUR = 3600
const S_MIN = 60
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
const numyears = Math.floor(seconds / S_YEAR)
const numdays = Math.floor((seconds % S_YEAR) / S_DAY)
const numhours = Math.floor(((seconds % S_YEAR) % S_DAY) / S_HOUR)
const numminutes = Math.floor((((seconds % S_YEAR) % S_DAY) % S_HOUR) / S_MIN)
const numseconds = (((seconds % S_YEAR) % S_DAY) % S_HOUR) % S_MIN
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")
if (numseconds > 0 || res.length === 0) res.push(numseconds + "s")
return res.join(" ")
}
export {secondsToString}
function parseTimeString(timeString: string): number {
const parts = timeString.split(/[ ,;]/)
let seconds = 0
const regexpPart = /^(\d+)\s*(?:([yjdthms])|$)/i
parts.forEach((part) => {
const match = part.match(regexpPart)
if (match) {
const n = parseInt(match[1])
const unit = match[2] === undefined ? "" : match[2].toLowerCase()
switch (unit) {
case "y":
case "j":
seconds += n * S_YEAR
break
case "d":
case "t":
seconds += n * S_DAY
break
case "h":
seconds += n * S_HOUR
break
case "m":
seconds += n * S_MIN
break
case "s":
seconds += n
break
default:
if (seconds === 0) seconds += n * S_MIN
else seconds += n
break
}
}
})
return seconds
}
function getTimestamp(): number {
return Math.round(Date.now() / 1000)
}
function timestampToDate(timestamp: number): Date {
return new Date(timestamp * 1000)
}
function dateToTimestamp(date: Date): number {
return Math.round(date.getTime() / 1000)
}
function timestampToString(timestamp: number): string {
return dateToString(timestampToDate(timestamp))
}
function dateToString(date: Date): string {
return date.toLocaleString("de-DE")
}
const INPUT_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"
function dateToInputString(date: Date): string {
return format(date, INPUT_DATE_FORMAT)
}
function inputStringToDate(inputString: string): Date {
return parseISO(inputString)
}
function handleApiError(error: any) {
if (error.response) {
const msg = error.response.data.msg
if (msg !== undefined) {
alert("Error: " + msg)
} else {
alert("Error: no response")
}
} else {
alert(String(error))
}
}
export {
secondsToString,
parseTimeString,
getTimestamp,
timestampToDate,
dateToTimestamp,
timestampToString,
dateToString,
dateToInputString,
inputStringToDate,
handleApiError,
}

View file

@ -1,10 +1,19 @@
import {wsUrl} from "./apiUrls"
import {Task, TaskList} from "../tsgrain-client"
import {tsgrainApi, wsUrl} from "./apiUrls"
class WebsocketMessage implements TaskList {
auto_mode = false
now = 0
tasks: Task[] = []
}
class WebsocketAPI {
private static ws: WebsocketAPI | undefined
private static ws?: WebsocketAPI
private conn: WebSocket | undefined
private conn?: WebSocket
private wsConnected: boolean
private lastMessage?: WebsocketMessage
private lastMessageDate?: Date
private clients: Set<WebsocketClient>
@ -40,10 +49,24 @@ class WebsocketAPI {
window.setTimeout(() => this.connect(), 3000)
}
this.conn.onmessage = (evt) => {
this.clients.forEach((client) => {
client.msgCallback(evt)
})
const msg = Object.assign(new WebsocketMessage(), JSON.parse(evt.data))
this.broadcastMsg(msg)
}
// Get initial state via REST api
tsgrainApi.getTasks().then((response) => {
this.broadcastMsg(response.data)
})
}
private broadcastMsg(msg: WebsocketMessage) {
const date = new Date()
this.lastMessage = msg
this.lastMessageDate = date
this.clients.forEach((client) => {
client.msgCallback(msg, date)
})
}
static Get(): WebsocketAPI {
@ -58,26 +81,28 @@ class WebsocketAPI {
}
addClient(client: WebsocketClient) {
console.log("added client", client)
this.clients.add(client)
if (this.lastMessage !== undefined && this.lastMessageDate !== undefined) {
client.msgCallback(this.lastMessage, this.lastMessageDate)
}
}
removeClient(client: WebsocketClient) {
console.log("removed client", client)
this.clients.delete(client)
}
}
export default class WebsocketClient {
class WebsocketClient {
statusCallback: (wsConnected: boolean) => void
msgCallback: (evt: MessageEvent) => void
msgCallback: (msg: WebsocketMessage, date: Date) => void
constructor(
statusCallback: (wsConnected: boolean) => void,
msgCallback: (evt: MessageEvent) => void
statusCallback?: (wsConnected: boolean) => void,
msgCallback?: (msg: WebsocketMessage, date: Date) => void
) {
this.statusCallback = statusCallback
this.msgCallback = msgCallback
this.statusCallback = statusCallback || ((con) => {})
this.msgCallback = msgCallback || ((msg, date) => {})
this.api().addClient(this)
}
@ -90,3 +115,5 @@ export default class WebsocketClient {
this.api().removeClient(this)
}
}
export {WebsocketMessage, WebsocketClient}

View file

@ -1 +1,3 @@
/// <reference types="vite/client" />
export {}

View file

@ -14,8 +14,6 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment"
"jsx": "preserve"
}
}

View file

@ -1,7 +1,7 @@
import {defineConfig} from "vite"
import preact from "@preact/preset-vite"
import reactRefresh from "@vitejs/plugin-react-refresh"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [preact()],
plugins: [reactRefresh()],
})