diff --git a/.editorconfig b/.editorconfig index 23d0200..95f10d1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -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 diff --git a/Makefile b/Makefile index 6557106..a9d473f 100644 --- a/Makefile +++ b/Makefile @@ -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} diff --git a/proto/tsgrain.proto b/proto/tsgrain.proto index f9c7fdf..a7bc75b 100644 --- a/proto/tsgrain.proto +++ b/proto/tsgrain.proto @@ -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; } diff --git a/src/model/options.go b/src/model/options.go index a2fb2db..0d931ac 100644 --- a/src/model/options.go +++ b/src/model/options.go @@ -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 diff --git a/src/model/task.go b/src/model/task.go index bb398e5..a2a272f 100644 --- a/src/model/task.go +++ b/src/model/task.go @@ -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"` } diff --git a/src/server/server.go b/src/server/server.go index bec8cf5..0ac84a8 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -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(¶ms); 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(¶ms); 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 diff --git a/src/server/swagger/swagger.yaml b/src/server/swagger/swagger.yaml index a5d2036..a0bd762 100644 --- a/src/server/swagger/swagger.yaml +++ b/src/server/swagger/swagger.yaml @@ -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 diff --git a/src/tsgrain_rpc/proto/tsgrain.pb.go b/src/tsgrain_rpc/proto/tsgrain.pb.go index 923581a..8d9459f 100644 --- a/src/tsgrain_rpc/proto/tsgrain.pb.go +++ b/src/tsgrain_rpc/proto/tsgrain.pb.go @@ -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, }, diff --git a/src/tsgrain_rpc/proto/tsgrain_grpc.pb.go b/src/tsgrain_rpc/proto/tsgrain_grpc.pb.go index 294d3bc..a429ef6 100644 --- a/src/tsgrain_rpc/proto/tsgrain_grpc.pb.go +++ b/src/tsgrain_rpc/proto/tsgrain_grpc.pb.go @@ -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", diff --git a/src/tsgrain_rpc/tsgrain_rpc.go b/src/tsgrain_rpc/tsgrain_rpc.go index 46202e3..69a93d5 100644 --- a/src/tsgrain_rpc/tsgrain_rpc.go +++ b/src/tsgrain_rpc/tsgrain_rpc.go @@ -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 } diff --git a/ui/package.json b/ui/package.json index c27f082..f7be729 100644 --- a/ui/package.json +++ b/ui/package.json @@ -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" } } diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml index a0e06fe..0e57569 100644 --- a/ui/pnpm-lock.yaml +++ b/ui/pnpm-lock.yaml @@ -1,28 +1,40 @@ lockfileVersion: 5.3 specifiers: - "@babel/core": ">=7.12.10 <8.0.0" - "@mdi/js": ^6.5.95 - "@preact/preset-vite": ^2.1.5 - axios: ^0.24.0 - preact: ^10.5.15 - prettier: ^2.4.1 - sass: ^1.43.4 + "@babel/core": ^7.17.0 + "@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 + "@types/react-dom": ^17.0.11 + "@vitejs/plugin-react-refresh": latest + axios: ^0.25.0 + date-fns: ^2.28.0 + react: ^17.0.2 + react-dom: ^17.0.2 typescript: ^4.5.2 - vite: ^2.6.14 + vite: latest + wouter: ^2.8.0-alpha.2 dependencies: - "@mdi/js": 6.5.95 - axios: 0.24.0 - preact: 10.6.5 + "@emotion/react": 11.7.1_@babel+core@7.17.0+react@17.0.2 + "@emotion/styled": 11.6.0_f7d5c6639f7c2cb185a33953e29a971a + "@mui/icons-material": 5.3.1_@mui+material@5.4.0+react@17.0.2 + "@mui/lab": 5.0.0-alpha.67_7b8cfd313d74777d565ad5f64226489b + "@mui/material": 5.4.0_c84e354a724f4213aa6539fdea9d08aa + axios: 0.25.0 + date-fns: 2.28.0 + react: 17.0.2 + react-dom: 17.0.2_react@17.0.2 + wouter: 2.8.0-alpha.2_react@17.0.2 devDependencies: "@babel/core": 7.17.0 - "@preact/preset-vite": 2.1.7_8bad03c14e079d36b71a4a5e3cd3eb6f - prettier: 2.5.1 - sass: 1.49.7 + "@types/react-dom": 17.0.11 + "@vitejs/plugin-react-refresh": 1.3.6 typescript: 4.5.5 - vite: 2.7.13_sass@1.49.7 + vite: 2.7.13 packages: /@ampproject/remapping/2.0.2: @@ -44,7 +56,6 @@ packages: engines: {node: ">=6.9.0"} dependencies: "@babel/highlight": 7.16.10 - dev: true /@babel/compat-data/7.17.0: resolution: @@ -92,16 +103,6 @@ packages: source-map: 0.5.7 dev: true - /@babel/helper-annotate-as-pure/7.16.7: - resolution: - { - integrity: sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==, - } - engines: {node: ">=6.9.0"} - dependencies: - "@babel/types": 7.17.0 - dev: true - /@babel/helper-compilation-targets/7.16.7_@babel+core@7.17.0: resolution: { @@ -168,7 +169,6 @@ packages: engines: {node: ">=6.9.0"} dependencies: "@babel/types": 7.17.0 - dev: true /@babel/helper-module-transforms/7.16.7: resolution: @@ -195,7 +195,6 @@ packages: integrity: sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==, } engines: {node: ">=6.9.0"} - dev: true /@babel/helper-simple-access/7.16.7: resolution: @@ -223,7 +222,6 @@ packages: integrity: sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==, } engines: {node: ">=6.9.0"} - dev: true /@babel/helper-validator-option/7.16.7: resolution: @@ -257,7 +255,6 @@ packages: "@babel/helper-validator-identifier": 7.16.7 chalk: 2.4.2 js-tokens: 4.0.0 - dev: true /@babel/parser/7.17.0: resolution: @@ -279,25 +276,44 @@ packages: dependencies: "@babel/core": 7.17.0 "@babel/helper-plugin-utils": 7.16.7 - dev: true + dev: false - /@babel/plugin-transform-react-jsx/7.16.7_@babel+core@7.17.0: + /@babel/plugin-transform-react-jsx-self/7.16.7_@babel+core@7.17.0: resolution: { - integrity: sha512-8D16ye66fxiE8m890w0BpPpngG9o9OVBBy0gH2E+2AR7qMR2ZpTYJEqLxAsoroenMId0p/wMW+Blc0meDgu0Ag==, + integrity: sha512-oe5VuWs7J9ilH3BCCApGoYjHoSO48vkjX2CbA5bFVhIuO2HKxA3vyF7rleA4o6/4rTDbk6r8hBW7Ul8E+UZrpA==, } engines: {node: ">=6.9.0"} peerDependencies: "@babel/core": ^7.0.0-0 dependencies: "@babel/core": 7.17.0 - "@babel/helper-annotate-as-pure": 7.16.7 - "@babel/helper-module-imports": 7.16.7 "@babel/helper-plugin-utils": 7.16.7 - "@babel/plugin-syntax-jsx": 7.16.7_@babel+core@7.17.0 - "@babel/types": 7.17.0 dev: true + /@babel/plugin-transform-react-jsx-source/7.16.7_@babel+core@7.17.0: + resolution: + { + integrity: sha512-rONFiQz9vgbsnaMtQlZCjIRwhJvlrPET8TabIUK2hzlXw9B9s2Ieaxte1SCOOXMbWRHodbKixNf3BLcWVOQ8Bw==, + } + engines: {node: ">=6.9.0"} + peerDependencies: + "@babel/core": ^7.0.0-0 + dependencies: + "@babel/core": 7.17.0 + "@babel/helper-plugin-utils": 7.16.7 + dev: true + + /@babel/runtime/7.17.0: + resolution: + { + integrity: sha512-etcO/ohMNaNA2UBdaXBBSX/3aEzFMRrVfaPv8Ptc0k+cWpWW0QFiGZ2XnVqQZI1Cf734LbPGmqBKWESfW4x/dQ==, + } + engines: {node: ">=6.9.0"} + dependencies: + regenerator-runtime: 0.13.9 + dev: false + /@babel/template/7.16.7: resolution: { @@ -340,7 +356,222 @@ packages: dependencies: "@babel/helper-validator-identifier": 7.16.7 to-fast-properties: 2.0.0 - dev: true + + /@date-io/core/2.13.1: + resolution: + { + integrity: sha512-pVI9nfkf2qClb2Cxdq0Q4zJhdawMG4ybWZUVGifT78FDwzRMX2SwXBb55s5NRJk0HcIicDuxktmCtemZqMH1Zg==, + } + dev: false + + /@date-io/date-fns/2.13.1_date-fns@2.28.0: + resolution: + { + integrity: sha512-8fmfwjiLMpFLD+t4NBwDx0eblWnNcgt4NgfT/uiiQTGI81fnPu9tpBMYdAcuWxaV7LLpXgzLBx1SYWAMDVUDQQ==, + } + peerDependencies: + date-fns: ^2.0.0 + peerDependenciesMeta: + date-fns: + optional: true + dependencies: + "@date-io/core": 2.13.1 + date-fns: 2.28.0 + dev: false + + /@date-io/dayjs/2.13.1: + resolution: + { + integrity: sha512-5bL4WWWmlI4uGZVScANhHJV7Mjp93ec2gNeUHDqqLaMZhp51S0NgD25oqj/k0LqBn1cdU2MvzNpk/ObMmVv5cQ==, + } + peerDependencies: + dayjs: ^1.8.17 + peerDependenciesMeta: + dayjs: + optional: true + dependencies: + "@date-io/core": 2.13.1 + dev: false + + /@date-io/luxon/2.13.1: + resolution: + { + integrity: sha512-yG+uM7lXfwLyKKEwjvP8oZ7qblpmfl9gxQYae55ifbwiTs0CoCTkYkxEaQHGkYtTqGTzLqcb0O9Pzx6vgWg+yg==, + } + peerDependencies: + luxon: ^1.21.3 || ^2.x + peerDependenciesMeta: + luxon: + optional: true + dependencies: + "@date-io/core": 2.13.1 + dev: false + + /@date-io/moment/2.13.1: + resolution: + { + integrity: sha512-XX1X/Tlvl3TdqQy2j0ZUtEJV6Rl8tOyc5WOS3ki52He28Uzme4Ro/JuPWTMBDH63weSWIZDlbR7zBgp3ZA2y1A==, + } + peerDependencies: + moment: ^2.24.0 + peerDependenciesMeta: + moment: + optional: true + dependencies: + "@date-io/core": 2.13.1 + dev: false + + /@emotion/babel-plugin/11.7.2_@babel+core@7.17.0: + resolution: + { + integrity: sha512-6mGSCWi9UzXut/ZAN6lGFu33wGR3SJisNl3c0tvlmb8XChH1b2SUvxvnOh7hvLpqyRdHHU9AiazV3Cwbk5SXKQ==, + } + peerDependencies: + "@babel/core": ^7.0.0 + dependencies: + "@babel/core": 7.17.0 + "@babel/helper-module-imports": 7.16.7 + "@babel/plugin-syntax-jsx": 7.16.7_@babel+core@7.17.0 + "@babel/runtime": 7.17.0 + "@emotion/hash": 0.8.0 + "@emotion/memoize": 0.7.5 + "@emotion/serialize": 1.0.2 + babel-plugin-macros: 2.8.0 + convert-source-map: 1.8.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.0.13 + dev: false + + /@emotion/cache/11.7.1: + resolution: + { + integrity: sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==, + } + dependencies: + "@emotion/memoize": 0.7.5 + "@emotion/sheet": 1.1.0 + "@emotion/utils": 1.0.0 + "@emotion/weak-memoize": 0.2.5 + stylis: 4.0.13 + dev: false + + /@emotion/hash/0.8.0: + resolution: + { + integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==, + } + dev: false + + /@emotion/is-prop-valid/1.1.1: + resolution: + { + integrity: sha512-bW1Tos67CZkOURLc0OalnfxtSXQJMrAMV0jZTVGJUPSOd4qgjF3+tTD5CwJM13PHA8cltGW1WGbbvV9NpvUZPw==, + } + dependencies: + "@emotion/memoize": 0.7.5 + dev: false + + /@emotion/memoize/0.7.5: + resolution: + { + integrity: sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==, + } + dev: false + + /@emotion/react/11.7.1_@babel+core@7.17.0+react@17.0.2: + resolution: + { + integrity: sha512-DV2Xe3yhkF1yT4uAUoJcYL1AmrnO5SVsdfvu+fBuS7IbByDeTVx9+wFmvx9Idzv7/78+9Mgx2Hcmr7Fex3tIyw==, + } + peerDependencies: + "@babel/core": ^7.0.0 + "@types/react": "*" + react: ">=16.8.0" + peerDependenciesMeta: + "@babel/core": + optional: true + "@types/react": + optional: true + dependencies: + "@babel/core": 7.17.0 + "@babel/runtime": 7.17.0 + "@emotion/cache": 11.7.1 + "@emotion/serialize": 1.0.2 + "@emotion/sheet": 1.1.0 + "@emotion/utils": 1.0.0 + "@emotion/weak-memoize": 0.2.5 + hoist-non-react-statics: 3.3.2 + react: 17.0.2 + dev: false + + /@emotion/serialize/1.0.2: + resolution: + { + integrity: sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==, + } + dependencies: + "@emotion/hash": 0.8.0 + "@emotion/memoize": 0.7.5 + "@emotion/unitless": 0.7.5 + "@emotion/utils": 1.0.0 + csstype: 3.0.10 + dev: false + + /@emotion/sheet/1.1.0: + resolution: + { + integrity: sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==, + } + dev: false + + /@emotion/styled/11.6.0_f7d5c6639f7c2cb185a33953e29a971a: + resolution: + { + integrity: sha512-mxVtVyIOTmCAkFbwIp+nCjTXJNgcz4VWkOYQro87jE2QBTydnkiYusMrRGFtzuruiGK4dDaNORk4gH049iiQuw==, + } + peerDependencies: + "@babel/core": ^7.0.0 + "@emotion/react": ^11.0.0-rc.0 + "@types/react": "*" + react: ">=16.8.0" + peerDependenciesMeta: + "@babel/core": + optional: true + "@types/react": + optional: true + dependencies: + "@babel/core": 7.17.0 + "@babel/runtime": 7.17.0 + "@emotion/babel-plugin": 11.7.2_@babel+core@7.17.0 + "@emotion/is-prop-valid": 1.1.1 + "@emotion/react": 11.7.1_@babel+core@7.17.0+react@17.0.2 + "@emotion/serialize": 1.0.2 + "@emotion/utils": 1.0.0 + react: 17.0.2 + dev: false + + /@emotion/unitless/0.7.5: + resolution: + { + integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==, + } + dev: false + + /@emotion/utils/1.0.0: + resolution: + { + integrity: sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA==, + } + dev: false + + /@emotion/weak-memoize/0.2.5: + resolution: + { + integrity: sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==, + } + dev: false /@jridgewell/resolve-uri/3.0.4: resolution: @@ -360,80 +591,247 @@ packages: sourcemap-codec: 1.4.8 dev: true - /@mdi/js/6.5.95: + /@mui/base/5.0.0-alpha.67_react-dom@17.0.2+react@17.0.2: resolution: { - integrity: sha512-x/bwEoAGP+Mo10Dfk5audNIPi7Yz8ZBrILcbXLW3ShOI/njpgodzpgpC2WYK3D2ZSC392peRRemIFb/JsyzzYQ==, + integrity: sha512-yK2++NivZUitAVpheMc5QVuwrVCphrTw85L6qjKcvnSpB8wmVYne58CY2vzMCNEuHkuHG2jccq9/JlRZFGAanw==, } + engines: {node: ">=12.0.0"} + peerDependencies: + "@types/react": ^16.8.6 || ^17.0.0 + react: ^17.0.0 + react-dom: ^17.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + dependencies: + "@babel/runtime": 7.17.0 + "@emotion/is-prop-valid": 1.1.1 + "@mui/utils": 5.3.0_react@17.0.2 + "@popperjs/core": 2.11.2 + clsx: 1.1.1 + prop-types: 15.8.1 + react: 17.0.2 + react-dom: 17.0.2_react@17.0.2 + react-is: 17.0.2 dev: false - /@preact/preset-vite/2.1.7_8bad03c14e079d36b71a4a5e3cd3eb6f: + /@mui/icons-material/5.3.1_@mui+material@5.4.0+react@17.0.2: resolution: { - integrity: sha512-/Ii+aN1Jm8TK5YzDVrj5UOVRZ91F2Nik1P3FbmZyQ5VJAg1ZCkOBBkmLrz9YhiMbbGl+U35yaZNUDMcO8Xlp2g==, + integrity: sha512-8zBWCaE8DHjIGZhGgMod92p6Rm38EhXrS+cZtaV0+jOTMeWh7z+mvswXzb/rVKc0ZYqw6mQYBcn2uEs2yclI9w==, } + engines: {node: ">=12.0.0"} peerDependencies: - "@babel/core": 7.x - vite: 2.x + "@mui/material": ^5.0.0 + "@types/react": ^16.8.6 || ^17.0.0 + react: ^17.0.0 + peerDependenciesMeta: + "@types/react": + optional: true dependencies: - "@babel/core": 7.17.0 - "@babel/plugin-transform-react-jsx": 7.16.7_@babel+core@7.17.0 - "@prefresh/vite": 2.2.6_preact@10.6.5+vite@2.7.13 - "@rollup/pluginutils": 4.1.2 - babel-plugin-transform-hook-names: 1.0.2_@babel+core@7.17.0 - debug: 4.3.3 - kolorist: 1.5.1 - resolve: 1.22.0 - vite: 2.7.13_sass@1.49.7 + "@babel/runtime": 7.17.0 + "@mui/material": 5.4.0_c84e354a724f4213aa6539fdea9d08aa + react: 17.0.2 + dev: false + + /@mui/lab/5.0.0-alpha.67_7b8cfd313d74777d565ad5f64226489b: + resolution: + { + integrity: sha512-pL6O2teso3tpAv38FXiFNWGcXjakgO66/Os5p2KX4QbkPKvzzeSxNU7hI02Ye8BIEYqTR5Oaz8njcdQgcNI7Cg==, + } + engines: {node: ">=12.0.0"} + peerDependencies: + "@mui/material": ^5.0.0 + "@types/react": ^16.8.6 || ^17.0.0 + date-fns: ^2.25.0 + dayjs: ^1.10.7 + luxon: ^1.28.0 || ^2.0.0 + moment: ^2.29.1 + react: ^17.0.0 + react-dom: ^17.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + date-fns: + optional: true + dayjs: + optional: true + luxon: + optional: true + moment: + optional: true + dependencies: + "@babel/runtime": 7.17.0 + "@date-io/date-fns": 2.13.1_date-fns@2.28.0 + "@date-io/dayjs": 2.13.1 + "@date-io/luxon": 2.13.1 + "@date-io/moment": 2.13.1 + "@mui/base": 5.0.0-alpha.67_react-dom@17.0.2+react@17.0.2 + "@mui/material": 5.4.0_c84e354a724f4213aa6539fdea9d08aa + "@mui/system": 5.4.0_922a85da57e3646a57465b7970b0de85 + "@mui/utils": 5.3.0_react@17.0.2 + clsx: 1.1.1 + date-fns: 2.28.0 + prop-types: 15.8.1 + react: 17.0.2 + react-dom: 17.0.2_react@17.0.2 + react-is: 17.0.2 + react-transition-group: 4.4.2_react-dom@17.0.2+react@17.0.2 + rifm: 0.12.1_react@17.0.2 transitivePeerDependencies: - - preact - - supports-color - dev: true + - "@emotion/react" + - "@emotion/styled" + dev: false - /@prefresh/babel-plugin/0.4.1: + /@mui/material/5.4.0_c84e354a724f4213aa6539fdea9d08aa: resolution: { - integrity: sha512-gj3ekiYtHlZNz0zFI1z6a9mcYX80Qacw84+2++7V1skvO7kQoV2ux56r8bJkTBbKMVxwAgaYrxxIdUCYlclE7Q==, + integrity: sha512-vfBIAMsRNWI/A4p/eP01MjqhSACwxRGYp/2Yi7WAU64PpI/TXR4b9SRl+XJMMJXVC7+abu4E3hTdF3oqwMCSYA==, } - dev: true + engines: {node: ">=12.0.0"} + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@types/react": ^16.8.6 || ^17.0.0 + react: ^17.0.0 + react-dom: ^17.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + dependencies: + "@babel/runtime": 7.17.0 + "@emotion/react": 11.7.1_@babel+core@7.17.0+react@17.0.2 + "@emotion/styled": 11.6.0_f7d5c6639f7c2cb185a33953e29a971a + "@mui/base": 5.0.0-alpha.67_react-dom@17.0.2+react@17.0.2 + "@mui/system": 5.4.0_922a85da57e3646a57465b7970b0de85 + "@mui/types": 7.1.0 + "@mui/utils": 5.3.0_react@17.0.2 + "@types/react-transition-group": 4.4.4 + clsx: 1.1.1 + csstype: 3.0.10 + hoist-non-react-statics: 3.3.2 + prop-types: 15.8.1 + react: 17.0.2 + react-dom: 17.0.2_react@17.0.2 + react-is: 17.0.2 + react-transition-group: 4.4.2_react-dom@17.0.2+react@17.0.2 + dev: false - /@prefresh/core/1.3.2_preact@10.6.5: + /@mui/private-theming/5.3.0_react@17.0.2: resolution: { - integrity: sha512-Iv+uI698KDgWsrKpLvOgN3hmAMyvhVgn09mcnhZ98BUNdg/qrxE7tcUf5yFCImkgqED5/Dcn8G5hFy4IikEDvg==, + integrity: sha512-EBobUEyM9fMnteKrVPp8pTMUh81xXakyfdpkoh7Y19q9JpD2eh7QGAQVJVj0JBFlcUJD60NIE4K8rdokrRmLwg==, + } + engines: {node: ">=12.0.0"} + peerDependencies: + "@types/react": ^16.8.6 || ^17.0.0 + react: ^17.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + dependencies: + "@babel/runtime": 7.17.0 + "@mui/utils": 5.3.0_react@17.0.2 + prop-types: 15.8.1 + react: 17.0.2 + dev: false + + /@mui/styled-engine/5.3.0_922a85da57e3646a57465b7970b0de85: + resolution: + { + integrity: sha512-I4YemFy9WnCLUdZ5T+6egpzc8e7Jq/uh9AJ3QInZHbyNu/9I2SWvNn7vHjWOT/D8Y8LMzIOhu5WwZbzjez7YRw==, + } + engines: {node: ">=12.0.0"} + peerDependencies: + "@emotion/react": ^11.4.1 + "@emotion/styled": ^11.3.0 + react: ^17.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + dependencies: + "@babel/runtime": 7.17.0 + "@emotion/cache": 11.7.1 + "@emotion/react": 11.7.1_@babel+core@7.17.0+react@17.0.2 + "@emotion/styled": 11.6.0_f7d5c6639f7c2cb185a33953e29a971a + prop-types: 15.8.1 + react: 17.0.2 + dev: false + + /@mui/system/5.4.0_922a85da57e3646a57465b7970b0de85: + resolution: + { + integrity: sha512-LX7g5gK5yCwiueSUVG73uVNc0yeHjsWUIFLrnPjP3m+J7O38RkPqyao5nZahhaSL1PGNbR9+zfkxljXthO9QqA==, + } + engines: {node: ">=12.0.0"} + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@types/react": ^16.8.6 || ^17.0.0 + react: ^17.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + dependencies: + "@babel/runtime": 7.17.0 + "@emotion/react": 11.7.1_@babel+core@7.17.0+react@17.0.2 + "@emotion/styled": 11.6.0_f7d5c6639f7c2cb185a33953e29a971a + "@mui/private-theming": 5.3.0_react@17.0.2 + "@mui/styled-engine": 5.3.0_922a85da57e3646a57465b7970b0de85 + "@mui/types": 7.1.0 + "@mui/utils": 5.3.0_react@17.0.2 + clsx: 1.1.1 + csstype: 3.0.10 + prop-types: 15.8.1 + react: 17.0.2 + dev: false + + /@mui/types/7.1.0: + resolution: + { + integrity: sha512-Hh7ALdq/GjfIwLvqH3XftuY3bcKhupktTm+S6qRIDGOtPtRuq2L21VWzOK4p7kblirK0XgGVH5BLwa6u8z/6QQ==, } peerDependencies: - preact: ^10.0.0 - dependencies: - preact: 10.6.5 - dev: true + "@types/react": "*" + peerDependenciesMeta: + "@types/react": + optional: true + dev: false - /@prefresh/utils/1.1.1: + /@mui/utils/5.3.0_react@17.0.2: resolution: { - integrity: sha512-MUhT5m2XNN5NsZl4GnpuvlzLo6VSTa/+wBfBd3fiWUvHGhv0GF9hnA1pd//v0uJaKwUnVRQ1hYElxCV7DtYsCQ==, - } - dev: true - - /@prefresh/vite/2.2.6_preact@10.6.5+vite@2.7.13: - resolution: - { - integrity: sha512-Q5i15C27NanIPzimkvBuzImebCxeQS6p8OyyH2QpNKS/HGsm7WpnyCzgMkIsidnmMHY59qllKwF01TWpq9IeGA==, + integrity: sha512-O/E9IQKPMg0OrN7+gkn7Ga5o5WA2iXQGdyqNBFPNrYzxOvwzsEtM5K7MtTCGGYKFe8mhTRM0ZOjh5OM0dglw+Q==, } + engines: {node: ">=12.0.0"} peerDependencies: - preact: ^10.4.0 - vite: ">=2.0.0-beta.3" + react: ^17.0.0 dependencies: - "@babel/core": 7.17.0 - "@prefresh/babel-plugin": 0.4.1 - "@prefresh/core": 1.3.2_preact@10.6.5 - "@prefresh/utils": 1.1.1 - "@rollup/pluginutils": 4.1.2 - preact: 10.6.5 - vite: 2.7.13_sass@1.49.7 - transitivePeerDependencies: - - supports-color - dev: true + "@babel/runtime": 7.17.0 + "@types/prop-types": 15.7.4 + "@types/react-is": 17.0.3 + prop-types: 15.8.1 + react: 17.0.2 + react-is: 17.0.2 + dev: false + + /@popperjs/core/2.11.2: + resolution: + { + integrity: sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==, + } + dev: false /@rollup/pluginutils/4.1.2: resolution: @@ -446,6 +844,78 @@ packages: picomatch: 2.3.1 dev: true + /@types/parse-json/4.0.0: + resolution: + { + integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==, + } + dev: false + + /@types/prop-types/15.7.4: + resolution: + { + integrity: sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==, + } + + /@types/react-dom/17.0.11: + resolution: + { + integrity: sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==, + } + dependencies: + "@types/react": 17.0.39 + dev: true + + /@types/react-is/17.0.3: + resolution: + { + integrity: sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==, + } + dependencies: + "@types/react": 17.0.39 + dev: false + + /@types/react-transition-group/4.4.4: + resolution: + { + integrity: sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==, + } + dependencies: + "@types/react": 17.0.39 + dev: false + + /@types/react/17.0.39: + resolution: + { + integrity: sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug==, + } + dependencies: + "@types/prop-types": 15.7.4 + "@types/scheduler": 0.16.2 + csstype: 3.0.10 + + /@types/scheduler/0.16.2: + resolution: + { + integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==, + } + + /@vitejs/plugin-react-refresh/1.3.6: + resolution: + { + integrity: sha512-iNR/UqhUOmFFxiezt0em9CgmiJBdWR+5jGxB2FihaoJfqGt76kiwaKoVOJVU5NYcDWMdN06LbyN2VIGIoYdsEA==, + } + engines: {node: ">=12.0.0"} + dependencies: + "@babel/core": 7.17.0 + "@babel/plugin-transform-react-jsx-self": 7.16.7_@babel+core@7.17.0 + "@babel/plugin-transform-react-jsx-source": 7.16.7_@babel+core@7.17.0 + "@rollup/pluginutils": 4.1.2 + react-refresh: 0.10.0 + transitivePeerDependencies: + - supports-color + dev: true + /ansi-styles/3.2.1: resolution: { @@ -454,23 +924,11 @@ packages: engines: {node: ">=4"} dependencies: color-convert: 1.9.3 - dev: true - /anymatch/3.1.2: + /axios/0.25.0: resolution: { - integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==, - } - engines: {node: ">= 8"} - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - dev: true - - /axios/0.24.0: - resolution: - { - integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==, + integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==, } dependencies: follow-redirects: 1.14.7 @@ -478,34 +936,16 @@ packages: - debug dev: false - /babel-plugin-transform-hook-names/1.0.2_@babel+core@7.17.0: + /babel-plugin-macros/2.8.0: resolution: { - integrity: sha512-5gafyjyyBTTdX/tQQ0hRgu4AhNHG/hqWi0ZZmg2xvs2FgRkJXzDNKBZCyoYqgFkovfDrgM8OoKg8karoUvWeCw==, + integrity: sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==, } - peerDependencies: - "@babel/core": ^7.12.10 dependencies: - "@babel/core": 7.17.0 - dev: true - - /binary-extensions/2.2.0: - resolution: - { - integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==, - } - engines: {node: ">=8"} - dev: true - - /braces/3.0.2: - resolution: - { - integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==, - } - engines: {node: ">=8"} - dependencies: - fill-range: 7.0.1 - dev: true + "@babel/runtime": 7.17.0 + cosmiconfig: 6.0.0 + resolve: 1.22.0 + dev: false /browserslist/4.19.1: resolution: @@ -516,12 +956,20 @@ packages: hasBin: true dependencies: caniuse-lite: 1.0.30001306 - electron-to-chromium: 1.4.63 + electron-to-chromium: 1.4.64 escalade: 3.1.1 node-releases: 2.0.1 picocolors: 1.0.0 dev: true + /callsites/3.1.0: + resolution: + { + integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, + } + engines: {node: ">=6"} + dev: false + /caniuse-lite/1.0.30001306: resolution: { @@ -539,25 +987,14 @@ packages: ansi-styles: 3.2.1 escape-string-regexp: 1.0.5 supports-color: 5.5.0 - dev: true - /chokidar/3.5.3: + /clsx/1.1.1: resolution: { - integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==, + integrity: sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==, } - engines: {node: ">= 8.10.0"} - dependencies: - anymatch: 3.1.2 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.2 - dev: true + engines: {node: ">=6"} + dev: false /color-convert/1.9.3: resolution: @@ -566,11 +1003,9 @@ packages: } dependencies: color-name: 1.1.3 - dev: true /color-name/1.1.3: resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=} - dev: true /convert-source-map/1.8.0: resolution: @@ -579,7 +1014,34 @@ packages: } dependencies: safe-buffer: 5.1.2 - dev: true + + /cosmiconfig/6.0.0: + resolution: + { + integrity: sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==, + } + engines: {node: ">=8"} + dependencies: + "@types/parse-json": 4.0.0 + import-fresh: 3.3.0 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + dev: false + + /csstype/3.0.10: + resolution: + { + integrity: sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==, + } + + /date-fns/2.28.0: + resolution: + { + integrity: sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==, + } + engines: {node: ">=0.11"} + dev: false /debug/4.3.3: resolution: @@ -596,13 +1058,32 @@ packages: ms: 2.1.2 dev: true - /electron-to-chromium/1.4.63: + /dom-helpers/5.2.1: resolution: { - integrity: sha512-e0PX/LRJPFRU4kzJKLvTobxyFdnANCvcoDCe8XcyTqP58nTWIwdsHvXLIl1RkB39X5yaosLaroMASWB0oIsgCA==, + integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==, + } + dependencies: + "@babel/runtime": 7.17.0 + csstype: 3.0.10 + dev: false + + /electron-to-chromium/1.4.64: + resolution: + { + integrity: sha512-8mec/99xgLUZCIZZq3wt61Tpxg55jnOSpxGYapE/1Ma9MpFEYYaz4QNYm0CM1rrnCo7i3FRHhbaWjeCLsveGjQ==, } dev: true + /error-ex/1.3.2: + resolution: + { + integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==, + } + dependencies: + is-arrayish: 0.2.1 + dev: false + /esbuild-android-arm64/0.13.15: resolution: { @@ -828,7 +1309,14 @@ packages: /escape-string-regexp/1.0.5: resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=} engines: {node: ">=0.8.0"} - dev: true + + /escape-string-regexp/4.0.0: + resolution: + { + integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==, + } + engines: {node: ">=10"} + dev: false /estree-walker/2.0.2: resolution: @@ -837,15 +1325,12 @@ packages: } dev: true - /fill-range/7.0.1: + /find-root/1.1.0: resolution: { - integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==, + integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==, } - engines: {node: ">=8"} - dependencies: - to-regex-range: 5.0.1 - dev: true + dev: false /follow-redirects/1.14.7: resolution: @@ -876,7 +1361,6 @@ packages: { integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==, } - dev: true /gensync/1.0.0-beta.2: resolution: @@ -886,16 +1370,6 @@ packages: engines: {node: ">=6.9.0"} dev: true - /glob-parent/5.1.2: - resolution: - { - integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==, - } - engines: {node: ">= 6"} - dependencies: - is-glob: 4.0.3 - dev: true - /globals/11.12.0: resolution: { @@ -907,7 +1381,6 @@ packages: /has-flag/3.0.0: resolution: {integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=} engines: {node: ">=4"} - dev: true /has/1.0.3: resolution: @@ -917,24 +1390,30 @@ packages: engines: {node: ">= 0.4.0"} dependencies: function-bind: 1.1.1 - dev: true - /immutable/4.0.0: + /hoist-non-react-statics/3.3.2: resolution: { - integrity: sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==, + integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==, } - dev: true - - /is-binary-path/2.1.0: - resolution: - { - integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==, - } - engines: {node: ">=8"} dependencies: - binary-extensions: 2.2.0 - dev: true + react-is: 16.13.1 + dev: false + + /import-fresh/3.3.0: + resolution: + { + integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==, + } + engines: {node: ">=6"} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: false + + /is-arrayish/0.2.1: + resolution: {integrity: sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=} + dev: false /is-core-module/2.8.1: resolution: @@ -943,37 +1422,12 @@ packages: } dependencies: has: 1.0.3 - dev: true - - /is-extglob/2.1.1: - resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=} - engines: {node: ">=0.10.0"} - dev: true - - /is-glob/4.0.3: - resolution: - { - integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==, - } - engines: {node: ">=0.10.0"} - dependencies: - is-extglob: 2.1.1 - dev: true - - /is-number/7.0.0: - resolution: - { - integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, - } - engines: {node: ">=0.12.0"} - dev: true /js-tokens/4.0.0: resolution: { integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, } - dev: true /jsesc/2.5.2: resolution: @@ -984,6 +1438,13 @@ packages: hasBin: true dev: true + /json-parse-even-better-errors/2.3.1: + resolution: + { + integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==, + } + dev: false + /json5/2.2.0: resolution: { @@ -995,12 +1456,22 @@ packages: minimist: 1.2.5 dev: true - /kolorist/1.5.1: + /lines-and-columns/1.2.4: resolution: { - integrity: sha512-lxpCM3HTvquGxKGzHeknB/sUjuVoUElLlfYnXZT73K8geR9jQbroGlSCFBax9/0mpGoD3kzcMLnOlGQPJJNyqQ==, + integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==, } - dev: true + dev: false + + /loose-envify/1.4.0: + resolution: + { + integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, + } + hasBin: true + dependencies: + js-tokens: 4.0.0 + dev: false /minimist/1.2.5: resolution: @@ -1032,20 +1503,47 @@ packages: } dev: true - /normalize-path/3.0.0: + /object-assign/4.1.1: + resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=} + engines: {node: ">=0.10.0"} + dev: false + + /parent-module/1.0.1: resolution: { - integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==, + integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, } - engines: {node: ">=0.10.0"} - dev: true + engines: {node: ">=6"} + dependencies: + callsites: 3.1.0 + dev: false + + /parse-json/5.2.0: + resolution: + { + integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==, + } + engines: {node: ">=8"} + dependencies: + "@babel/code-frame": 7.16.7 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + dev: false /path-parse/1.0.7: resolution: { integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, } - dev: true + + /path-type/4.0.0: + resolution: + { + integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==, + } + engines: {node: ">=8"} + dev: false /picocolors/1.0.0: resolution: @@ -1074,31 +1572,95 @@ packages: source-map-js: 1.0.2 dev: true - /preact/10.6.5: + /prop-types/15.8.1: resolution: { - integrity: sha512-i+LXM6JiVjQXSt2jG2vZZFapGpCuk1fl8o6ii3G84MA3xgj686FKjs4JFDkmUVhtxyq21+4ay74zqPykz9hU6w==, + integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==, + } + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + dev: false + + /react-dom/17.0.2_react@17.0.2: + resolution: + { + integrity: sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==, + } + peerDependencies: + react: 17.0.2 + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react: 17.0.2 + scheduler: 0.20.2 + dev: false + + /react-is/16.13.1: + resolution: + { + integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==, } dev: false - /prettier/2.5.1: + /react-is/17.0.2: resolution: { - integrity: sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==, + integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==, } - engines: {node: ">=10.13.0"} - hasBin: true + dev: false + + /react-refresh/0.10.0: + resolution: + { + integrity: sha512-PgidR3wST3dDYKr6b4pJoqQFpPGNKDSCDx4cZoshjXipw3LzO7mG1My2pwEzz2JVkF+inx3xRpDeQLFQGH/hsQ==, + } + engines: {node: ">=0.10.0"} dev: true - /readdirp/3.6.0: + /react-transition-group/4.4.2_react-dom@17.0.2+react@17.0.2: resolution: { - integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==, + integrity: sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==, } - engines: {node: ">=8.10.0"} + peerDependencies: + react: ">=16.6.0" + react-dom: ">=16.6.0" dependencies: - picomatch: 2.3.1 - dev: true + "@babel/runtime": 7.17.0 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 17.0.2 + react-dom: 17.0.2_react@17.0.2 + dev: false + + /react/17.0.2: + resolution: + { + integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==, + } + engines: {node: ">=0.10.0"} + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + dev: false + + /regenerator-runtime/0.13.9: + resolution: + { + integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==, + } + dev: false + + /resolve-from/4.0.0: + resolution: + { + integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, + } + engines: {node: ">=4"} + dev: false /resolve/1.22.0: resolution: @@ -1110,7 +1672,17 @@ packages: is-core-module: 2.8.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - dev: true + + /rifm/0.12.1_react@17.0.2: + resolution: + { + integrity: sha512-OGA1Bitg/dSJtI/c4dh90svzaUPt228kzFsUkJbtA2c964IqEAwWXeL9ZJi86xWv3j5SMqRvGULl7bA6cK0Bvg==, + } + peerDependencies: + react: ">=16.8" + dependencies: + react: 17.0.2 + dev: false /rollup/2.67.0: resolution: @@ -1128,20 +1700,16 @@ packages: { integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==, } - dev: true - /sass/1.49.7: + /scheduler/0.20.2: resolution: { - integrity: sha512-13dml55EMIR2rS4d/RDHHP0sXMY3+30e1TKsyXaSz3iLWVoDWEoboY8WzJd5JMnxrRHffKO3wq2mpJ0jxRJiEQ==, + integrity: sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==, } - engines: {node: ">=12.0.0"} - hasBin: true dependencies: - chokidar: 3.5.3 - immutable: 4.0.0 - source-map-js: 1.0.2 - dev: true + loose-envify: 1.4.0 + object-assign: 4.1.1 + dev: false /semver/6.3.0: resolution: @@ -1162,7 +1730,6 @@ packages: /source-map/0.5.7: resolution: {integrity: sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=} engines: {node: ">=0.10.0"} - dev: true /sourcemap-codec/1.4.8: resolution: @@ -1171,6 +1738,13 @@ packages: } dev: true + /stylis/4.0.13: + resolution: + { + integrity: sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==, + } + dev: false + /supports-color/5.5.0: resolution: { @@ -1179,7 +1753,6 @@ packages: engines: {node: ">=4"} dependencies: has-flag: 3.0.0 - dev: true /supports-preserve-symlinks-flag/1.0.0: resolution: @@ -1187,22 +1760,10 @@ packages: integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==, } engines: {node: ">= 0.4"} - dev: true /to-fast-properties/2.0.0: resolution: {integrity: sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=} engines: {node: ">=4"} - dev: true - - /to-regex-range/5.0.1: - resolution: - { - integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==, - } - engines: {node: ">=8.0"} - dependencies: - is-number: 7.0.0 - dev: true /typescript/4.5.5: resolution: @@ -1213,7 +1774,7 @@ packages: hasBin: true dev: true - /vite/2.7.13_sass@1.49.7: + /vite/2.7.13: resolution: { integrity: sha512-Mq8et7f3aK0SgSxjDNfOAimZGW9XryfHRa/uV0jseQSilg+KhYDSoNb9h1rknOy6SuMkvNDLKCYAYYUMCE+IgQ==, @@ -1236,7 +1797,25 @@ packages: postcss: 8.4.6 resolve: 1.22.0 rollup: 2.67.0 - sass: 1.49.7 optionalDependencies: fsevents: 2.3.2 dev: true + + /wouter/2.8.0-alpha.2_react@17.0.2: + resolution: + { + integrity: sha512-aPsL5m5rW9RiceClOmGj6t5gn9Ut2TJVr98UDi1u9MIRNYiYVflg6vFIjdDYJ4IAyH0JdnkSgGwfo0LQS3k2zg==, + } + peerDependencies: + react: ">=16.8.0" + dependencies: + react: 17.0.2 + dev: false + + /yaml/1.10.2: + resolution: + { + integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==, + } + engines: {node: ">= 6"} + dev: false diff --git a/ui/src/App.tsx b/ui/src/App.tsx new file mode 100644 index 0000000..2171ffc --- /dev/null +++ b/ui/src/App.tsx @@ -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 ( + + + + + + + + + + + + + + + + + ) +} diff --git a/ui/src/components/AutoMode.tsx b/ui/src/components/AutoMode.tsx new file mode 100644 index 0000000..78405a6 --- /dev/null +++ b/ui/src/components/AutoMode.tsx @@ -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 { + 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) => { + const newState = !this.state.auto + tsgrainApi.setAutoMode({state: newState}).catch(handleApiError) + } + + render() { + return ( + + } + label="Auto" + /> + ) + } +} diff --git a/ui/src/components/Controls.tsx b/ui/src/components/Controls.tsx new file mode 100644 index 0000000..e79a78e --- /dev/null +++ b/ui/src/components/Controls.tsx @@ -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 ( + + + + {props.icon} + + {props.label} + + + + + + + + ) +} + +type TaskProgressProps = { + task: Task + taskListCliTimestamp: number + taskListSrvTimestamp: number +} + +type TaskProgressState = { + timestamp: number +} + +class TaskProgress extends React.Component { + 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 = + break + + case 1: + icon = + 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 ( + + ) + } +} + +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 = + break + + case 1: + icon = + break + + default: + break + } + + if (task.datetime_started !== null) { + } + + items.push( + + ) + }) + + return {items} +} + +type ZoneButtonProps = { + start: boolean + disabled: boolean + zoneId: number +} + +const ZoneButton = (props: ZoneButtonProps) => { + const clickFun = props.start ? startZone : stopZone + + return ( + clickFun(props.zoneId)} + disabled={props.disabled} + > + {props.start ? "Start" : "Stop"} + + ) +} + +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 ( + + + + Platz {props.zoneId} + + + + + + + + + + ) +} + +type Props = {} + +type State = { + wsConnected: boolean + taskList: TaskList + tasksByZone: {[key: number]: Task[]} + taskListTimestamp: number +} + +export default class Controls extends React.Component { + 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( + + + + ) + } + + return ( + + + {cards} + + + ) + } +} diff --git a/ui/src/components/DurationInput.tsx b/ui/src/components/DurationInput.tsx new file mode 100644 index 0000000..928d771 --- /dev/null +++ b/ui/src/components/DurationInput.tsx @@ -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) => { + setTextValue(e.target.value) + props.onChange(parseTimeString(e.target.value)) + } + + const handleDurationBlur = (e: React.FocusEvent) => { + setTextValue(secondsToString(props.value)) + } + + return ( + 0)} + /> + ) +} + +export default DurationInput diff --git a/ui/src/components/Jobs.tsx b/ui/src/components/Jobs.tsx new file mode 100644 index 0000000..89955af --- /dev/null +++ b/ui/src/components/Jobs.tsx @@ -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 + onChange?: (selection: Set) => 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( + } + checkedIcon={} + checked={selection.has(i)} + onChange={() => handleChange(i)} + /> + ) + } + + return ( + + Plätze + {items} + + ) +} + +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(props.job.date) + const [datetimeInput, setDatetimeInput] = React.useState( + 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 ( + + {props.getBtn(handleClickOpen)} + + + Zeitplan {props.job.id > 0 ? "bearbeiten" : "erstellen"} + + + setZones(selection)} + /> + + ( + + )} + label="Datum/Zeit" + value={datetimeInput} + onChange={handleDateUpdate} + mask="__.__.____ __:__" + /> + + + setDuration(v)} + /> + + setRepeat(!repeat)} + /> + } + label="Täglich wiederholen" + /> + + + Abbrechen + OK + + + + ) +} + +type JobTableProps = { + jobs: Job[] + jobEdit: (job: Job) => void + jobDelete: (jobId: number) => void +} + +const JobTable = (props: JobTableProps) => { + return ( + + + + + Ein/Aus + Plätze + Datum/Zeit + Dauer + Wiederholen + Bearbeiten + + + + {props.jobs.map((job) => { + return ( + + + { + job.enable = !job.enable + props.jobEdit(job) + }} + > + {job.enable ? ( + + ) : ( + + )} + + + {job.zones.join(", ")} + {timestampToString(job.date)} + {secondsToString(job.duration)} + + {job.repeat ? ( + + ) : ( + + )} + + + ( + + + + )} + /> + props.jobDelete(job.id)} + > + + + + + ) + })} + + + + ) +} + +type JobsProps = {} + +type JobsState = { + loaded: boolean + jobs: Job[] +} + +export default class Jobs extends React.Component { + 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 ( + + {this.state.loaded ? ( + + ) : ( + + + + + + )} + ( + + + + )} + /> + + ) + } +} diff --git a/ui/src/components/Navbar.tsx b/ui/src/components/Navbar.tsx new file mode 100644 index 0000000..1423e03 --- /dev/null +++ b/ui/src/components/Navbar.tsx @@ -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(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 ( + + + + {/* Desktop title */} + + TSGRain + + + + + + + + {Object.keys(pages).map((page) => ( + handleTabClick(page)} + > + {page} + + ))} + + + {/* Mobile title */} + + TSGRain + + + {Object.keys(pages).map((page) => ( + handleTabClick(page)} + sx={{my: 2, color: "white", display: "block"}} + > + {page} + + ))} + + + + + + + + ) +} +export default Navbar diff --git a/ui/src/components/NoConnectionBar.tsx b/ui/src/components/NoConnectionBar.tsx new file mode 100644 index 0000000..90aa37b --- /dev/null +++ b/ui/src/components/NoConnectionBar.tsx @@ -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 { + 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 ( + + Keine Verbindung + + ) + } + return null + } +} diff --git a/ui/src/components/Settings.tsx b/ui/src/components/Settings.tsx new file mode 100644 index 0000000..0b2569b --- /dev/null +++ b/ui/src/components/Settings.tsx @@ -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 ( + + + + Bewässerungssteuerung + + this.setState({irrigationTime: v})} + /> + + {this.showSubmit() ? ( + + + OK + + + ) : null} + + + ) + } + + render() { + if (this.state.loaded) return this.renderSettings() + + return ( + + + + + + ) + } +} + +type TimeSettingsProps = {} + +type TimeSettingsState = { + loaded: boolean + date: Date + dateStr: string + timezone: string +} + +class TimeSettings extends React.Component { + 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 ( + + + Datum/Uhrzeit + + { + clearInterval(this.timerID) + this.setState({ + dateStr: e.target.value, + date: inputStringToDate(e.target.value), + }) + }} + /> + + this.setState({timezone: e.target.value})} + /> + + {this.showSubmit() ? ( + + + OK + + + ) : null} + + ) + } + + render() { + if (this.state.loaded) return this.renderSettings() + + return ( + + + + + + ) + } +} + +const Settings = () => { + return ( + + + + + TSGRain WebUI Version: {getConfig().version} + + + ) +} + +export default Settings diff --git a/ui/src/components/app.tsx b/ui/src/components/app.tsx deleted file mode 100644 index 3e0efc5..0000000 --- a/ui/src/components/app.tsx +++ /dev/null @@ -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 {getConfig().version} - } -} diff --git a/ui/src/main.tsx b/ui/src/main.tsx index 66f7e6b..eae102b 100644 --- a/ui/src/main.tsx +++ b/ui/src/main.tsx @@ -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(, document.getElementById("app")!) +ReactDOM.render( + + + {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */} + + + + , + document.getElementById("app") +) diff --git a/ui/src/preact.d.ts b/ui/src/preact.d.ts deleted file mode 100644 index ac79d62..0000000 --- a/ui/src/preact.d.ts +++ /dev/null @@ -1 +0,0 @@ -import JSX = preact.JSX diff --git a/ui/src/style/index.scss b/ui/src/style/index.scss deleted file mode 100644 index e69de29..0000000 diff --git a/ui/src/theme.ts b/ui/src/theme.ts new file mode 100644 index 0000000..bc1aba6 --- /dev/null +++ b/ui/src/theme.ts @@ -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 diff --git a/ui/src/tsgrain-client/.openapi-generator/FILES b/ui/src/tsgrain-client/.openapi-generator/FILES index 16b445e..a80cd4f 100644 --- a/ui/src/tsgrain-client/.openapi-generator/FILES +++ b/ui/src/tsgrain-client/.openapi-generator/FILES @@ -1,6 +1,5 @@ .gitignore .npmignore -.openapi-generator-ignore api.ts base.ts common.ts diff --git a/ui/src/tsgrain-client/.openapi-generator/VERSION b/ui/src/tsgrain-client/.openapi-generator/VERSION index 7d3cdbf..1e20ec3 100644 --- a/ui/src/tsgrain-client/.openapi-generator/VERSION +++ b/ui/src/tsgrain-client/.openapi-generator/VERSION @@ -1 +1 @@ -5.3.1 \ No newline at end of file +5.4.0 \ No newline at end of file diff --git a/ui/src/tsgrain-client/api.ts b/ui/src/tsgrain-client/api.ts index 75fba97..0f680cb 100644 --- a/ui/src/tsgrain-client/api.ts +++ b/ui/src/tsgrain-client/api.ts @@ -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 } /** - * 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 => { 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 => { - 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 => { + 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 => { + 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 => { 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 => { - 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 => { - 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 => { + 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 => { + 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 => { + 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 > { 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 - > { - 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 + (axios?: AxiosInstance, basePath?: string) => AxiosPromise > { - 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 + > { + 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 > { - 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 > { - 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 + > { + 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 + > { + 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 + > { + 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 { + deleteJob(jobId?: JobID, options?: any): AxiosPromise { 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 { - 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 { + getSystemTime(options?: any): AxiosPromise { 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 { + 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 { + setAutoMode(state?: AutoMode, options?: any): AxiosPromise { 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 { 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 { + 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 { + 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 { + 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)) } diff --git a/ui/src/util/apiUrls.ts b/ui/src/util/apiUrls.ts index cceed04..36ecca4 100644 --- a/ui/src/util/apiUrls.ts +++ b/ui/src/util/apiUrls.ts @@ -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} diff --git a/ui/src/util/config.ts b/ui/src/util/config.ts index f5950c2..d9f27a7 100644 --- a/ui/src/util/config.ts +++ b/ui/src/util/config.ts @@ -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, } } diff --git a/ui/src/util/functions.ts b/ui/src/util/functions.ts index 18179e2..1b8f912 100644 --- a/ui/src/util/functions.ts +++ b/ui/src/util/functions.ts @@ -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, +} diff --git a/ui/src/util/websocket.ts b/ui/src/util/websocket.ts index 8d75673..56d5f60 100644 --- a/ui/src/util/websocket.ts +++ b/ui/src/util/websocket.ts @@ -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 @@ -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} diff --git a/ui/src/vite-env.d.ts b/ui/src/vite-env.ts similarity index 77% rename from ui/src/vite-env.d.ts rename to ui/src/vite-env.ts index 11f02fe..714c59c 100644 --- a/ui/src/vite-env.d.ts +++ b/ui/src/vite-env.ts @@ -1 +1,3 @@ /// + +export {} diff --git a/ui/tsconfig.json b/ui/tsconfig.json index dd76ca6..bcf685d 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.json @@ -14,8 +14,6 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "preserve", - "jsxFactory": "h", - "jsxFragmentFactory": "Fragment" + "jsx": "preserve" } } diff --git a/ui/vite.config.ts b/ui/vite.config.js similarity index 53% rename from ui/vite.config.ts rename to ui/vite.config.js index 80a2f42..46785b9 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.js @@ -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()], })