diff --git a/AGENTS.md b/AGENTS.md index 3c0e50f..52a45fc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -69,6 +69,7 @@ docs/ markdown-доки, НЕ в репо — живут толь - Логи сервера: monitors, RunScript и ServiceAction/ServiceInfo пишут в stdout; ошибки проб сервисов (`probe : ...`) логируются один раз на изменение (появление/восстановление), не на каждый 3s тик; `ListContent` (поллинг-шум) исключён из HTTP-лога echo. - SW: `web/public/sw.js`, версия кэша `homelab-shell-vN` — **бампить при изменении** офлайн-логики, иначе iOS не обновит. - Тесты: `make test` (smoke RPC) + `tsc --noEmit` внутри `make web`. Не плодить фреймворки. +- После правок Go-кода — прогонять `gofmt -w server/` (агент делает это сам после любых правок). ## Подводные камни diff --git a/config.yaml b/config.yaml index 22cecbe..9247acf 100644 --- a/config.yaml +++ b/config.yaml @@ -7,15 +7,12 @@ monitor: - name: cpu script: scripts/cpu.sh refresh: 5 - data: percentage - name: disk script: scripts/disk.sh refresh: 60 - data: percentage - name: mem script: scripts/mem.sh refresh: 5 - data: percentage exec: - name: greet script: scripts/greet.sh diff --git a/gen/homelab/v1/homelab.pb.go b/gen/homelab/v1/homelab.pb.go index 9567b53..d189775 100644 --- a/gen/homelab/v1/homelab.pb.go +++ b/gen/homelab/v1/homelab.pb.go @@ -120,8 +120,6 @@ func (ServiceOp) EnumDescriptor() ([]byte, []int) { type Item struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Data string `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` // monitor: text | percentage - Value string `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` // monitor: last output Args int32 `protobuf:"varint,4,opt,name=args,proto3" json:"args,omitempty"` // exec: arg count ArgTypes []string `protobuf:"bytes,5,rep,name=arg_types,json=argTypes,proto3" json:"arg_types,omitempty"` // exec: text | number per arg unknownFields protoimpl.UnknownFields @@ -165,20 +163,6 @@ func (x *Item) GetName() string { return "" } -func (x *Item) GetData() string { - if x != nil { - return x.Data - } - return "" -} - -func (x *Item) GetValue() string { - if x != nil { - return x.Value - } - return "" -} - func (x *Item) GetArgs() int32 { if x != nil { return x.Args @@ -193,6 +177,119 @@ func (x *Item) GetArgTypes() []string { return nil } +type Stat struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Stat) Reset() { + *x = Stat{} + mi := &file_homelab_v1_homelab_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Stat) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Stat) ProtoMessage() {} + +func (x *Stat) ProtoReflect() protoreflect.Message { + mi := &file_homelab_v1_homelab_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Stat.ProtoReflect.Descriptor instead. +func (*Stat) Descriptor() ([]byte, []int) { + return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{1} +} + +func (x *Stat) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Stat) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +// Карточка монитора: процент (-1 = скрипт не дал процент) + строки для подписей. +type Monitor struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Percentage int32 `protobuf:"varint,2,opt,name=percentage,proto3" json:"percentage,omitempty"` + Stats []*Stat `protobuf:"bytes,3,rep,name=stats,proto3" json:"stats,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Monitor) Reset() { + *x = Monitor{} + mi := &file_homelab_v1_homelab_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Monitor) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Monitor) ProtoMessage() {} + +func (x *Monitor) ProtoReflect() protoreflect.Message { + mi := &file_homelab_v1_homelab_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Monitor.ProtoReflect.Descriptor instead. +func (*Monitor) Descriptor() ([]byte, []int) { + return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{2} +} + +func (x *Monitor) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Monitor) GetPercentage() int32 { + if x != nil { + return x.Percentage + } + return 0 +} + +func (x *Monitor) GetStats() []*Stat { + if x != nil { + return x.Stats + } + return nil +} + type Link struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` @@ -203,7 +300,7 @@ type Link struct { func (x *Link) Reset() { *x = Link{} - mi := &file_homelab_v1_homelab_proto_msgTypes[1] + mi := &file_homelab_v1_homelab_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -215,7 +312,7 @@ func (x *Link) String() string { func (*Link) ProtoMessage() {} func (x *Link) ProtoReflect() protoreflect.Message { - mi := &file_homelab_v1_homelab_proto_msgTypes[1] + mi := &file_homelab_v1_homelab_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -228,7 +325,7 @@ func (x *Link) ProtoReflect() protoreflect.Message { // Deprecated: Use Link.ProtoReflect.Descriptor instead. func (*Link) Descriptor() ([]byte, []int) { - return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{1} + return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{3} } func (x *Link) GetName() string { @@ -253,7 +350,7 @@ type ListContentRequest struct { func (x *ListContentRequest) Reset() { *x = ListContentRequest{} - mi := &file_homelab_v1_homelab_proto_msgTypes[2] + mi := &file_homelab_v1_homelab_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -265,7 +362,7 @@ func (x *ListContentRequest) String() string { func (*ListContentRequest) ProtoMessage() {} func (x *ListContentRequest) ProtoReflect() protoreflect.Message { - mi := &file_homelab_v1_homelab_proto_msgTypes[2] + mi := &file_homelab_v1_homelab_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -278,13 +375,13 @@ func (x *ListContentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListContentRequest.ProtoReflect.Descriptor instead. func (*ListContentRequest) Descriptor() ([]byte, []int) { - return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{2} + return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{4} } type ListContentResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Links []*Link `protobuf:"bytes,1,rep,name=links,proto3" json:"links,omitempty"` - Monitors []*Item `protobuf:"bytes,2,rep,name=monitors,proto3" json:"monitors,omitempty"` + Monitors []*Monitor `protobuf:"bytes,2,rep,name=monitors,proto3" json:"monitors,omitempty"` Exec []*Item `protobuf:"bytes,3,rep,name=exec,proto3" json:"exec,omitempty"` Services []*Service `protobuf:"bytes,4,rep,name=services,proto3" json:"services,omitempty"` unknownFields protoimpl.UnknownFields @@ -293,7 +390,7 @@ type ListContentResponse struct { func (x *ListContentResponse) Reset() { *x = ListContentResponse{} - mi := &file_homelab_v1_homelab_proto_msgTypes[3] + mi := &file_homelab_v1_homelab_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -305,7 +402,7 @@ func (x *ListContentResponse) String() string { func (*ListContentResponse) ProtoMessage() {} func (x *ListContentResponse) ProtoReflect() protoreflect.Message { - mi := &file_homelab_v1_homelab_proto_msgTypes[3] + mi := &file_homelab_v1_homelab_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -318,7 +415,7 @@ func (x *ListContentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListContentResponse.ProtoReflect.Descriptor instead. func (*ListContentResponse) Descriptor() ([]byte, []int) { - return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{3} + return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{5} } func (x *ListContentResponse) GetLinks() []*Link { @@ -328,7 +425,7 @@ func (x *ListContentResponse) GetLinks() []*Link { return nil } -func (x *ListContentResponse) GetMonitors() []*Item { +func (x *ListContentResponse) GetMonitors() []*Monitor { if x != nil { return x.Monitors } @@ -359,7 +456,7 @@ type DocMeta struct { func (x *DocMeta) Reset() { *x = DocMeta{} - mi := &file_homelab_v1_homelab_proto_msgTypes[4] + mi := &file_homelab_v1_homelab_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -371,7 +468,7 @@ func (x *DocMeta) String() string { func (*DocMeta) ProtoMessage() {} func (x *DocMeta) ProtoReflect() protoreflect.Message { - mi := &file_homelab_v1_homelab_proto_msgTypes[4] + mi := &file_homelab_v1_homelab_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -384,7 +481,7 @@ func (x *DocMeta) ProtoReflect() protoreflect.Message { // Deprecated: Use DocMeta.ProtoReflect.Descriptor instead. func (*DocMeta) Descriptor() ([]byte, []int) { - return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{4} + return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{6} } func (x *DocMeta) GetName() string { @@ -409,7 +506,7 @@ type ListDocsRequest struct { func (x *ListDocsRequest) Reset() { *x = ListDocsRequest{} - mi := &file_homelab_v1_homelab_proto_msgTypes[5] + mi := &file_homelab_v1_homelab_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -421,7 +518,7 @@ func (x *ListDocsRequest) String() string { func (*ListDocsRequest) ProtoMessage() {} func (x *ListDocsRequest) ProtoReflect() protoreflect.Message { - mi := &file_homelab_v1_homelab_proto_msgTypes[5] + mi := &file_homelab_v1_homelab_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -434,7 +531,7 @@ func (x *ListDocsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListDocsRequest.ProtoReflect.Descriptor instead. func (*ListDocsRequest) Descriptor() ([]byte, []int) { - return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{5} + return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{7} } type ListDocsResponse struct { @@ -446,7 +543,7 @@ type ListDocsResponse struct { func (x *ListDocsResponse) Reset() { *x = ListDocsResponse{} - mi := &file_homelab_v1_homelab_proto_msgTypes[6] + mi := &file_homelab_v1_homelab_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -458,7 +555,7 @@ func (x *ListDocsResponse) String() string { func (*ListDocsResponse) ProtoMessage() {} func (x *ListDocsResponse) ProtoReflect() protoreflect.Message { - mi := &file_homelab_v1_homelab_proto_msgTypes[6] + mi := &file_homelab_v1_homelab_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -471,7 +568,7 @@ func (x *ListDocsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListDocsResponse.ProtoReflect.Descriptor instead. func (*ListDocsResponse) Descriptor() ([]byte, []int) { - return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{6} + return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{8} } func (x *ListDocsResponse) GetDocs() []*DocMeta { @@ -490,7 +587,7 @@ type GetDocRequest struct { func (x *GetDocRequest) Reset() { *x = GetDocRequest{} - mi := &file_homelab_v1_homelab_proto_msgTypes[7] + mi := &file_homelab_v1_homelab_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -502,7 +599,7 @@ func (x *GetDocRequest) String() string { func (*GetDocRequest) ProtoMessage() {} func (x *GetDocRequest) ProtoReflect() protoreflect.Message { - mi := &file_homelab_v1_homelab_proto_msgTypes[7] + mi := &file_homelab_v1_homelab_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -515,7 +612,7 @@ func (x *GetDocRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDocRequest.ProtoReflect.Descriptor instead. func (*GetDocRequest) Descriptor() ([]byte, []int) { - return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{7} + return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{9} } func (x *GetDocRequest) GetName() string { @@ -534,7 +631,7 @@ type GetDocResponse struct { func (x *GetDocResponse) Reset() { *x = GetDocResponse{} - mi := &file_homelab_v1_homelab_proto_msgTypes[8] + mi := &file_homelab_v1_homelab_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -546,7 +643,7 @@ func (x *GetDocResponse) String() string { func (*GetDocResponse) ProtoMessage() {} func (x *GetDocResponse) ProtoReflect() protoreflect.Message { - mi := &file_homelab_v1_homelab_proto_msgTypes[8] + mi := &file_homelab_v1_homelab_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -559,7 +656,7 @@ func (x *GetDocResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDocResponse.ProtoReflect.Descriptor instead. func (*GetDocResponse) Descriptor() ([]byte, []int) { - return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{8} + return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{10} } func (x *GetDocResponse) GetContent() string { @@ -579,7 +676,7 @@ type SaveDocRequest struct { func (x *SaveDocRequest) Reset() { *x = SaveDocRequest{} - mi := &file_homelab_v1_homelab_proto_msgTypes[9] + mi := &file_homelab_v1_homelab_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -591,7 +688,7 @@ func (x *SaveDocRequest) String() string { func (*SaveDocRequest) ProtoMessage() {} func (x *SaveDocRequest) ProtoReflect() protoreflect.Message { - mi := &file_homelab_v1_homelab_proto_msgTypes[9] + mi := &file_homelab_v1_homelab_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -604,7 +701,7 @@ func (x *SaveDocRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SaveDocRequest.ProtoReflect.Descriptor instead. func (*SaveDocRequest) Descriptor() ([]byte, []int) { - return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{9} + return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{11} } func (x *SaveDocRequest) GetName() string { @@ -630,7 +727,7 @@ type SaveDocResponse struct { func (x *SaveDocResponse) Reset() { *x = SaveDocResponse{} - mi := &file_homelab_v1_homelab_proto_msgTypes[10] + mi := &file_homelab_v1_homelab_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -642,7 +739,7 @@ func (x *SaveDocResponse) String() string { func (*SaveDocResponse) ProtoMessage() {} func (x *SaveDocResponse) ProtoReflect() protoreflect.Message { - mi := &file_homelab_v1_homelab_proto_msgTypes[10] + mi := &file_homelab_v1_homelab_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -655,7 +752,7 @@ func (x *SaveDocResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SaveDocResponse.ProtoReflect.Descriptor instead. func (*SaveDocResponse) Descriptor() ([]byte, []int) { - return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{10} + return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{12} } func (x *SaveDocResponse) GetHash() string { @@ -681,7 +778,7 @@ type Service struct { func (x *Service) Reset() { *x = Service{} - mi := &file_homelab_v1_homelab_proto_msgTypes[11] + mi := &file_homelab_v1_homelab_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -693,7 +790,7 @@ func (x *Service) String() string { func (*Service) ProtoMessage() {} func (x *Service) ProtoReflect() protoreflect.Message { - mi := &file_homelab_v1_homelab_proto_msgTypes[11] + mi := &file_homelab_v1_homelab_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -706,7 +803,7 @@ func (x *Service) ProtoReflect() protoreflect.Message { // Deprecated: Use Service.ProtoReflect.Descriptor instead. func (*Service) Descriptor() ([]byte, []int) { - return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{11} + return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{13} } func (x *Service) GetKind() ServiceKind { @@ -776,7 +873,7 @@ type ServiceActionRequest struct { func (x *ServiceActionRequest) Reset() { *x = ServiceActionRequest{} - mi := &file_homelab_v1_homelab_proto_msgTypes[12] + mi := &file_homelab_v1_homelab_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -788,7 +885,7 @@ func (x *ServiceActionRequest) String() string { func (*ServiceActionRequest) ProtoMessage() {} func (x *ServiceActionRequest) ProtoReflect() protoreflect.Message { - mi := &file_homelab_v1_homelab_proto_msgTypes[12] + mi := &file_homelab_v1_homelab_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -801,7 +898,7 @@ func (x *ServiceActionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ServiceActionRequest.ProtoReflect.Descriptor instead. func (*ServiceActionRequest) Descriptor() ([]byte, []int) { - return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{12} + return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{14} } func (x *ServiceActionRequest) GetName() string { @@ -834,7 +931,7 @@ type ServiceActionResponse struct { func (x *ServiceActionResponse) Reset() { *x = ServiceActionResponse{} - mi := &file_homelab_v1_homelab_proto_msgTypes[13] + mi := &file_homelab_v1_homelab_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -846,7 +943,7 @@ func (x *ServiceActionResponse) String() string { func (*ServiceActionResponse) ProtoMessage() {} func (x *ServiceActionResponse) ProtoReflect() protoreflect.Message { - mi := &file_homelab_v1_homelab_proto_msgTypes[13] + mi := &file_homelab_v1_homelab_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -859,7 +956,7 @@ func (x *ServiceActionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ServiceActionResponse.ProtoReflect.Descriptor instead. func (*ServiceActionResponse) Descriptor() ([]byte, []int) { - return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{13} + return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{15} } func (x *ServiceActionResponse) GetOutput() string { @@ -879,7 +976,7 @@ type ServiceInfoRequest struct { func (x *ServiceInfoRequest) Reset() { *x = ServiceInfoRequest{} - mi := &file_homelab_v1_homelab_proto_msgTypes[14] + mi := &file_homelab_v1_homelab_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -891,7 +988,7 @@ func (x *ServiceInfoRequest) String() string { func (*ServiceInfoRequest) ProtoMessage() {} func (x *ServiceInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_homelab_v1_homelab_proto_msgTypes[14] + mi := &file_homelab_v1_homelab_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -904,7 +1001,7 @@ func (x *ServiceInfoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ServiceInfoRequest.ProtoReflect.Descriptor instead. func (*ServiceInfoRequest) Descriptor() ([]byte, []int) { - return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{14} + return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{16} } func (x *ServiceInfoRequest) GetName() string { @@ -930,7 +1027,7 @@ type ServiceInfoResponse struct { func (x *ServiceInfoResponse) Reset() { *x = ServiceInfoResponse{} - mi := &file_homelab_v1_homelab_proto_msgTypes[15] + mi := &file_homelab_v1_homelab_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -942,7 +1039,7 @@ func (x *ServiceInfoResponse) String() string { func (*ServiceInfoResponse) ProtoMessage() {} func (x *ServiceInfoResponse) ProtoReflect() protoreflect.Message { - mi := &file_homelab_v1_homelab_proto_msgTypes[15] + mi := &file_homelab_v1_homelab_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -955,7 +1052,7 @@ func (x *ServiceInfoResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ServiceInfoResponse.ProtoReflect.Descriptor instead. func (*ServiceInfoResponse) Descriptor() ([]byte, []int) { - return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{15} + return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{17} } func (x *ServiceInfoResponse) GetOutput() string { @@ -975,7 +1072,7 @@ type RunScriptRequest struct { func (x *RunScriptRequest) Reset() { *x = RunScriptRequest{} - mi := &file_homelab_v1_homelab_proto_msgTypes[16] + mi := &file_homelab_v1_homelab_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -987,7 +1084,7 @@ func (x *RunScriptRequest) String() string { func (*RunScriptRequest) ProtoMessage() {} func (x *RunScriptRequest) ProtoReflect() protoreflect.Message { - mi := &file_homelab_v1_homelab_proto_msgTypes[16] + mi := &file_homelab_v1_homelab_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1000,7 +1097,7 @@ func (x *RunScriptRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RunScriptRequest.ProtoReflect.Descriptor instead. func (*RunScriptRequest) Descriptor() ([]byte, []int) { - return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{16} + return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{18} } func (x *RunScriptRequest) GetName() string { @@ -1026,7 +1123,7 @@ type RunScriptResponse struct { func (x *RunScriptResponse) Reset() { *x = RunScriptResponse{} - mi := &file_homelab_v1_homelab_proto_msgTypes[17] + mi := &file_homelab_v1_homelab_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1038,7 +1135,7 @@ func (x *RunScriptResponse) String() string { func (*RunScriptResponse) ProtoMessage() {} func (x *RunScriptResponse) ProtoReflect() protoreflect.Message { - mi := &file_homelab_v1_homelab_proto_msgTypes[17] + mi := &file_homelab_v1_homelab_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1051,7 +1148,7 @@ func (x *RunScriptResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RunScriptResponse.ProtoReflect.Descriptor instead. func (*RunScriptResponse) Descriptor() ([]byte, []int) { - return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{17} + return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{19} } func (x *RunScriptResponse) GetOutput() string { @@ -1066,20 +1163,27 @@ var File_homelab_v1_homelab_proto protoreflect.FileDescriptor const file_homelab_v1_homelab_proto_rawDesc = "" + "\n" + "\x18homelab/v1/homelab.proto\x12\n" + - "homelab.v1\"u\n" + + "homelab.v1\"K\n" + "\x04Item\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" + - "\x04data\x18\x02 \x01(\tR\x04data\x12\x14\n" + - "\x05value\x18\x03 \x01(\tR\x05value\x12\x12\n" + "\x04args\x18\x04 \x01(\x05R\x04args\x12\x1b\n" + - "\targ_types\x18\x05 \x03(\tR\bargTypes\",\n" + + "\targ_types\x18\x05 \x03(\tR\bargTypes\"0\n" + + "\x04Stat\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value\"e\n" + + "\aMonitor\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1e\n" + + "\n" + + "percentage\x18\x02 \x01(\x05R\n" + + "percentage\x12&\n" + + "\x05stats\x18\x03 \x03(\v2\x10.homelab.v1.StatR\x05stats\",\n" + "\x04Link\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x10\n" + "\x03url\x18\x02 \x01(\tR\x03url\"\x14\n" + - "\x12ListContentRequest\"\xc2\x01\n" + + "\x12ListContentRequest\"\xc5\x01\n" + "\x13ListContentResponse\x12&\n" + - "\x05links\x18\x01 \x03(\v2\x10.homelab.v1.LinkR\x05links\x12,\n" + - "\bmonitors\x18\x02 \x03(\v2\x10.homelab.v1.ItemR\bmonitors\x12$\n" + + "\x05links\x18\x01 \x03(\v2\x10.homelab.v1.LinkR\x05links\x12/\n" + + "\bmonitors\x18\x02 \x03(\v2\x13.homelab.v1.MonitorR\bmonitors\x12$\n" + "\x04exec\x18\x03 \x03(\v2\x10.homelab.v1.ItemR\x04exec\x12/\n" + "\bservices\x18\x04 \x03(\v2\x13.homelab.v1.ServiceR\bservices\"1\n" + "\aDocMeta\x12\x12\n" + @@ -1155,58 +1259,61 @@ func file_homelab_v1_homelab_proto_rawDescGZIP() []byte { } var file_homelab_v1_homelab_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_homelab_v1_homelab_proto_msgTypes = make([]protoimpl.MessageInfo, 18) +var file_homelab_v1_homelab_proto_msgTypes = make([]protoimpl.MessageInfo, 20) var file_homelab_v1_homelab_proto_goTypes = []any{ (ServiceKind)(0), // 0: homelab.v1.ServiceKind (ServiceOp)(0), // 1: homelab.v1.ServiceOp (*Item)(nil), // 2: homelab.v1.Item - (*Link)(nil), // 3: homelab.v1.Link - (*ListContentRequest)(nil), // 4: homelab.v1.ListContentRequest - (*ListContentResponse)(nil), // 5: homelab.v1.ListContentResponse - (*DocMeta)(nil), // 6: homelab.v1.DocMeta - (*ListDocsRequest)(nil), // 7: homelab.v1.ListDocsRequest - (*ListDocsResponse)(nil), // 8: homelab.v1.ListDocsResponse - (*GetDocRequest)(nil), // 9: homelab.v1.GetDocRequest - (*GetDocResponse)(nil), // 10: homelab.v1.GetDocResponse - (*SaveDocRequest)(nil), // 11: homelab.v1.SaveDocRequest - (*SaveDocResponse)(nil), // 12: homelab.v1.SaveDocResponse - (*Service)(nil), // 13: homelab.v1.Service - (*ServiceActionRequest)(nil), // 14: homelab.v1.ServiceActionRequest - (*ServiceActionResponse)(nil), // 15: homelab.v1.ServiceActionResponse - (*ServiceInfoRequest)(nil), // 16: homelab.v1.ServiceInfoRequest - (*ServiceInfoResponse)(nil), // 17: homelab.v1.ServiceInfoResponse - (*RunScriptRequest)(nil), // 18: homelab.v1.RunScriptRequest - (*RunScriptResponse)(nil), // 19: homelab.v1.RunScriptResponse + (*Stat)(nil), // 3: homelab.v1.Stat + (*Monitor)(nil), // 4: homelab.v1.Monitor + (*Link)(nil), // 5: homelab.v1.Link + (*ListContentRequest)(nil), // 6: homelab.v1.ListContentRequest + (*ListContentResponse)(nil), // 7: homelab.v1.ListContentResponse + (*DocMeta)(nil), // 8: homelab.v1.DocMeta + (*ListDocsRequest)(nil), // 9: homelab.v1.ListDocsRequest + (*ListDocsResponse)(nil), // 10: homelab.v1.ListDocsResponse + (*GetDocRequest)(nil), // 11: homelab.v1.GetDocRequest + (*GetDocResponse)(nil), // 12: homelab.v1.GetDocResponse + (*SaveDocRequest)(nil), // 13: homelab.v1.SaveDocRequest + (*SaveDocResponse)(nil), // 14: homelab.v1.SaveDocResponse + (*Service)(nil), // 15: homelab.v1.Service + (*ServiceActionRequest)(nil), // 16: homelab.v1.ServiceActionRequest + (*ServiceActionResponse)(nil), // 17: homelab.v1.ServiceActionResponse + (*ServiceInfoRequest)(nil), // 18: homelab.v1.ServiceInfoRequest + (*ServiceInfoResponse)(nil), // 19: homelab.v1.ServiceInfoResponse + (*RunScriptRequest)(nil), // 20: homelab.v1.RunScriptRequest + (*RunScriptResponse)(nil), // 21: homelab.v1.RunScriptResponse } var file_homelab_v1_homelab_proto_depIdxs = []int32{ - 3, // 0: homelab.v1.ListContentResponse.links:type_name -> homelab.v1.Link - 2, // 1: homelab.v1.ListContentResponse.monitors:type_name -> homelab.v1.Item - 2, // 2: homelab.v1.ListContentResponse.exec:type_name -> homelab.v1.Item - 13, // 3: homelab.v1.ListContentResponse.services:type_name -> homelab.v1.Service - 6, // 4: homelab.v1.ListDocsResponse.docs:type_name -> homelab.v1.DocMeta - 0, // 5: homelab.v1.Service.kind:type_name -> homelab.v1.ServiceKind - 0, // 6: homelab.v1.ServiceActionRequest.kind:type_name -> homelab.v1.ServiceKind - 1, // 7: homelab.v1.ServiceActionRequest.op:type_name -> homelab.v1.ServiceOp - 0, // 8: homelab.v1.ServiceInfoRequest.kind:type_name -> homelab.v1.ServiceKind - 4, // 9: homelab.v1.HomelabService.ListContent:input_type -> homelab.v1.ListContentRequest - 7, // 10: homelab.v1.HomelabService.ListDocs:input_type -> homelab.v1.ListDocsRequest - 9, // 11: homelab.v1.HomelabService.GetDoc:input_type -> homelab.v1.GetDocRequest - 11, // 12: homelab.v1.HomelabService.SaveDoc:input_type -> homelab.v1.SaveDocRequest - 14, // 13: homelab.v1.HomelabService.ServiceAction:input_type -> homelab.v1.ServiceActionRequest - 16, // 14: homelab.v1.HomelabService.ServiceInfo:input_type -> homelab.v1.ServiceInfoRequest - 18, // 15: homelab.v1.HomelabService.RunScript:input_type -> homelab.v1.RunScriptRequest - 5, // 16: homelab.v1.HomelabService.ListContent:output_type -> homelab.v1.ListContentResponse - 8, // 17: homelab.v1.HomelabService.ListDocs:output_type -> homelab.v1.ListDocsResponse - 10, // 18: homelab.v1.HomelabService.GetDoc:output_type -> homelab.v1.GetDocResponse - 12, // 19: homelab.v1.HomelabService.SaveDoc:output_type -> homelab.v1.SaveDocResponse - 15, // 20: homelab.v1.HomelabService.ServiceAction:output_type -> homelab.v1.ServiceActionResponse - 17, // 21: homelab.v1.HomelabService.ServiceInfo:output_type -> homelab.v1.ServiceInfoResponse - 19, // 22: homelab.v1.HomelabService.RunScript:output_type -> homelab.v1.RunScriptResponse - 16, // [16:23] is the sub-list for method output_type - 9, // [9:16] 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 + 3, // 0: homelab.v1.Monitor.stats:type_name -> homelab.v1.Stat + 5, // 1: homelab.v1.ListContentResponse.links:type_name -> homelab.v1.Link + 4, // 2: homelab.v1.ListContentResponse.monitors:type_name -> homelab.v1.Monitor + 2, // 3: homelab.v1.ListContentResponse.exec:type_name -> homelab.v1.Item + 15, // 4: homelab.v1.ListContentResponse.services:type_name -> homelab.v1.Service + 8, // 5: homelab.v1.ListDocsResponse.docs:type_name -> homelab.v1.DocMeta + 0, // 6: homelab.v1.Service.kind:type_name -> homelab.v1.ServiceKind + 0, // 7: homelab.v1.ServiceActionRequest.kind:type_name -> homelab.v1.ServiceKind + 1, // 8: homelab.v1.ServiceActionRequest.op:type_name -> homelab.v1.ServiceOp + 0, // 9: homelab.v1.ServiceInfoRequest.kind:type_name -> homelab.v1.ServiceKind + 6, // 10: homelab.v1.HomelabService.ListContent:input_type -> homelab.v1.ListContentRequest + 9, // 11: homelab.v1.HomelabService.ListDocs:input_type -> homelab.v1.ListDocsRequest + 11, // 12: homelab.v1.HomelabService.GetDoc:input_type -> homelab.v1.GetDocRequest + 13, // 13: homelab.v1.HomelabService.SaveDoc:input_type -> homelab.v1.SaveDocRequest + 16, // 14: homelab.v1.HomelabService.ServiceAction:input_type -> homelab.v1.ServiceActionRequest + 18, // 15: homelab.v1.HomelabService.ServiceInfo:input_type -> homelab.v1.ServiceInfoRequest + 20, // 16: homelab.v1.HomelabService.RunScript:input_type -> homelab.v1.RunScriptRequest + 7, // 17: homelab.v1.HomelabService.ListContent:output_type -> homelab.v1.ListContentResponse + 10, // 18: homelab.v1.HomelabService.ListDocs:output_type -> homelab.v1.ListDocsResponse + 12, // 19: homelab.v1.HomelabService.GetDoc:output_type -> homelab.v1.GetDocResponse + 14, // 20: homelab.v1.HomelabService.SaveDoc:output_type -> homelab.v1.SaveDocResponse + 17, // 21: homelab.v1.HomelabService.ServiceAction:output_type -> homelab.v1.ServiceActionResponse + 19, // 22: homelab.v1.HomelabService.ServiceInfo:output_type -> homelab.v1.ServiceInfoResponse + 21, // 23: homelab.v1.HomelabService.RunScript:output_type -> homelab.v1.RunScriptResponse + 17, // [17:24] is the sub-list for method output_type + 10, // [10:17] is the sub-list for method input_type + 10, // [10:10] is the sub-list for extension type_name + 10, // [10:10] is the sub-list for extension extendee + 0, // [0:10] is the sub-list for field type_name } func init() { file_homelab_v1_homelab_proto_init() } @@ -1220,7 +1327,7 @@ func file_homelab_v1_homelab_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_homelab_v1_homelab_proto_rawDesc), len(file_homelab_v1_homelab_proto_rawDesc)), NumEnums: 2, - NumMessages: 18, + NumMessages: 20, NumExtensions: 0, NumServices: 1, }, diff --git a/proto/homelab/v1/homelab.proto b/proto/homelab/v1/homelab.proto index 11d3029..3269d78 100644 --- a/proto/homelab/v1/homelab.proto +++ b/proto/homelab/v1/homelab.proto @@ -6,12 +6,22 @@ option go_package = "pwa-homelab-mon/gen/homelab/v1;homelabv1"; message Item { string name = 1; - string data = 2; // monitor: text | percentage - string value = 3; // monitor: last output int32 args = 4; // exec: arg count repeated string arg_types = 5; // exec: text | number per arg } +message Stat { + string name = 1; + string value = 2; +} + +// Карточка монитора: процент (-1 = скрипт не дал процент) + строки для подписей. +message Monitor { + string name = 1; + int32 percentage = 2; + repeated Stat stats = 3; +} + message Link { string name = 1; string url = 2; @@ -20,7 +30,7 @@ message Link { message ListContentRequest {} message ListContentResponse { repeated Link links = 1; - repeated Item monitors = 2; + repeated Monitor monitors = 2; repeated Item exec = 3; repeated Service services = 4; } diff --git a/scripts/cpu.sh b/scripts/cpu.sh index f9d24bb..1aa0d7b 100755 --- a/scripts/cpu.sh +++ b/scripts/cpu.sh @@ -1,7 +1,8 @@ #!/bin/sh -# Суммарная загрузка CPU в процентах 0-100 (mac + linux). +# CPU: процент загрузки + число ядер. Linux + mac. +# Контракт мониторов: 1-я строка — процент, дальше — key=value. case "$(uname)" in Darwin) cores=$(sysctl -n hw.ncpu) ;; *) cores=$(nproc) ;; esac -ps -A -o %cpu | awk -v c="$cores" '{s+=$1} END {printf "%d", s/c}' +ps -A -o %cpu | awk -v c="$cores" '{s+=$1} END {printf "%d\nядер=%d\n", s/c, c}' diff --git a/scripts/disk.sh b/scripts/disk.sh index 429610a..8dcf99d 100755 --- a/scripts/disk.sh +++ b/scripts/disk.sh @@ -1,6 +1,13 @@ #!/bin/sh -# Заполненность корневого раздела в процентах (linux + mac). +# Диск /: процент + занято/свободно/всего. Linux + mac. +# Контракт мониторов: 1-я строка — процент, дальше — key=value. case "$(uname)" in - Darwin) df -k / | awk 'NR==2 {print $5}' | tr -d '%' ;; - *) df -Pk / | awk 'NR==2 {print $5}' | tr -d '%' ;; -esac + Darwin) df -k / ;; + *) df -Pk / ;; +esac | awk 'NR==2 { + pct = $5; sub(/%/, "", pct) + printf "%d\n", pct + printf "занято=%.1f ГБ\n", $3/1048576 + printf "свободно=%.1f ГБ\n", $4/1048576 + printf "всего=%.1f ГБ\n", $2/1048576 +}' diff --git a/scripts/mem.sh b/scripts/mem.sh index 4d6f38c..8a80341 100755 --- a/scripts/mem.sh +++ b/scripts/mem.sh @@ -1,3 +1,15 @@ #!/bin/sh -# Заполненность RAM в процентах: ps %mem — уже доля от общей памяти, суммируем как есть (mac + linux). -ps -A -o %mem | awk '{s+=$1} END {printf "%d", s}' +# RAM: процент занятого + строка занято/всего. Linux + mac. +# Контракт мониторов: 1-я строка — процент, дальше — key=value. +# ponytail: used = сумма RSS процессов (без кэшей ОС) — на mac «доступно» дёшево не получить; +# если нужен точный linux used — /proc/meminfo MemAvailable. +case "$(uname)" in + Darwin) total_kb=$(( $(sysctl -n hw.memsize) / 1024 )) ;; + *) total_kb=$(awk '/MemTotal/ {print $2}' /proc/meminfo) ;; +esac +ps -A -o rss=,%mem= | awk -v t="$total_kb" ' + {rss+=$1; mem+=$2} + END { + printf "%d\n", mem + printf "занято=%.1f / %.1f ГБ\n", rss/1048576, t/1048576 + }' diff --git a/server/config.go b/server/config.go index d6718bf..adcc298 100644 --- a/server/config.go +++ b/server/config.go @@ -7,9 +7,9 @@ import ( ) type Config struct { - Links []Link `yaml:"links"` - Monitor []Item `yaml:"monitor"` - Exec []Item `yaml:"exec"` + Links []Link `yaml:"links"` + Monitor []Monitor `yaml:"monitor"` + Exec []Item `yaml:"exec"` } func loadConfig(path string) (*Config, error) { @@ -31,21 +31,22 @@ func loadConfig(path string) (*Config, error) { } seen[l.Name] = true } - for _, list := range [][]Item{cfg.Monitor, cfg.Exec} { - for _, it := range list { - if seen[it.Name] { - return nil, fmt.Errorf("duplicate name %q", it.Name) - } - seen[it.Name] = true + for _, m := range cfg.Monitor { + if seen[m.Name] { + return nil, fmt.Errorf("duplicate name %q", m.Name) } + seen[m.Name] = true + } + for _, it := range cfg.Exec { + if seen[it.Name] { + return nil, fmt.Errorf("duplicate name %q", it.Name) + } + seen[it.Name] = true } for _, it := range cfg.Monitor { if it.Refresh <= 0 { return nil, fmt.Errorf("monitor %q: refresh must be > 0", it.Name) } - if it.Data != "text" && it.Data != "percentage" { - return nil, fmt.Errorf("monitor %q: data must be text|percentage", it.Name) - } } for _, it := range cfg.Exec { if len(it.ArgTypes) != it.Args { diff --git a/server/main.go b/server/main.go index f03adf9..1d0320e 100644 --- a/server/main.go +++ b/server/main.go @@ -8,6 +8,7 @@ import ( "os" "os/exec" "path/filepath" + "strconv" "strings" "sync" "time" @@ -24,11 +25,16 @@ type Item struct { Name string `yaml:"name"` Script string `yaml:"script"` Refresh int `yaml:"refresh"` // monitor: seconds - Data string `yaml:"data"` // monitor: text | percentage Args int `yaml:"args"` // exec: arg count ArgTypes []string `yaml:"arg_types"` // exec: text | number per arg } +type Monitor struct { + Name string `yaml:"name"` + Script string `yaml:"script"` + Refresh int `yaml:"refresh"` // seconds +} + type Link struct { Name string `yaml:"name"` URL string `yaml:"url"` @@ -39,7 +45,7 @@ type Link struct { type server struct { cfg *Config exec map[string]*Item // by item name, read-only after start - monVals sync.Map // monitor item name -> string (last output) + monVals sync.Map // monitor item name -> *monData (last parsed output) docsDir string srvDir string // dir with *.service units (~/srv on the box) probeErr sync.Map // probe key -> last logged error; poll errors log once per change, not every 3s tick @@ -48,10 +54,34 @@ type server struct { const execTimeout = 10 * time.Second -func runScript(s *Item, args ...string) (string, error) { +// monData — распарсенный вывод скрипта-монитора: первая строка — процент, +// остальные — строки key=value для подписей в карточке. +type monData struct { + percentage int32 // -1 = скрипт не дал процент + stats []*homelabv1.Stat +} + +func parseMonitor(out string) *monData { + d := &monData{percentage: -1} + lines := strings.Split(strings.TrimSpace(out), "\n") + if lines[0] == "" { + return d + } + if n, err := strconv.Atoi(strings.TrimSpace(lines[0])); err == nil { + d.percentage = int32(n) + } + for _, l := range lines[1:] { + if k, v, ok := strings.Cut(strings.TrimSpace(l), "="); ok { + d.stats = append(d.stats, &homelabv1.Stat{Name: strings.TrimSpace(k), Value: strings.TrimSpace(v)}) + } + } + return d +} + +func runScript(script string, args ...string) (string, error) { ctx, cancel := context.WithTimeout(context.Background(), execTimeout) defer cancel() - out, err := exec.CommandContext(ctx, s.Script, args...).CombinedOutput() + out, err := exec.CommandContext(ctx, script, args...).CombinedOutput() if err != nil { return "", fmt.Errorf("%v: %s", err, out) } @@ -62,17 +92,16 @@ func (s *server) startMonitors() { for i := range s.cfg.Monitor { sc := &s.cfg.Monitor[i] log.Printf("monitor started: %s (%s) every %ds", sc.Name, sc.Script, sc.Refresh) - go func(sc *Item) { + go func(sc *Monitor) { for { - v, err := runScript(sc) + out, err := runScript(sc.Script) if err != nil { log.Printf("monitor %s: %v", sc.Name, err) - v = "error: " + err.Error() - } else { - v = strings.TrimSpace(v) - log.Printf("monitor %s -> %s", sc.Name, v) + out = "-\nerror=" + err.Error() } - s.monVals.Store(sc.Name, v) + d := parseMonitor(out) + log.Printf("monitor %s -> %d%% (%d stats)", sc.Name, d.percentage, len(d.stats)) + s.monVals.Store(sc.Name, d) time.Sleep(time.Duration(sc.Refresh) * time.Second) } }(sc) diff --git a/server/rpc.go b/server/rpc.go index a2e4ff8..fe74abf 100644 --- a/server/rpc.go +++ b/server/rpc.go @@ -18,9 +18,12 @@ func (s *server) ListContent(ctx context.Context, _ *connect.Request[homelabv1.L } for i := range s.cfg.Monitor { it := &s.cfg.Monitor[i] - val, _ := s.monVals.Load(it.Name) - v, _ := val.(string) - resp.Monitors = append(resp.Monitors, &homelabv1.Item{Name: it.Name, Data: it.Data, Value: v}) + m := &homelabv1.Monitor{Name: it.Name} + if d, ok := s.monVals.Load(it.Name); ok { + md := d.(*monData) + m.Percentage, m.Stats = md.percentage, md.stats + } + resp.Monitors = append(resp.Monitors, m) } for i := range s.cfg.Exec { it := &s.cfg.Exec[i] @@ -38,7 +41,7 @@ func (s *server) RunScript(_ context.Context, req *connect.Request[homelabv1.Run if len(req.Msg.GetArgs()) != it.Args { return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("%q takes %d args, got %d", it.Name, it.Args, len(req.Msg.GetArgs()))) } - out, err := runScript(it, req.Msg.GetArgs()...) + out, err := runScript(it.Script, req.Msg.GetArgs()...) if err != nil { log.Printf("script %s args=%v: ERROR %v", it.Name, req.Msg.GetArgs(), err) return nil, connect.NewError(connect.CodeInternal, err) diff --git a/web/src/App.tsx b/web/src/App.tsx index 2e6b031..9b38b2e 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from "preact/hooks"; import { createClient } from "@connectrpc/connect"; import { createConnectTransport } from "@connectrpc/connect-web"; import { BookOpen, Gauge, Link as LinkIcon, Play, Server } from "lucide-preact"; -import type { Item, Link, Service } from "./gen/homelab/v1/homelab_pb"; +import type { Item, Link, Service, Monitor } from "./gen/homelab/v1/homelab_pb"; import { HomelabService } from "./gen/homelab/v1/homelab_pb"; import { MonitorCard } from "./components/MonitorCard"; import { ScriptCard } from "./components/ScriptCard"; @@ -203,7 +203,7 @@ function MainScreen({ type Content = { links: Link[]; - monitors: Item[]; + monitors: Monitor[]; exec: Item[]; services: Service[]; }; diff --git a/web/src/components/MonitorCard.tsx b/web/src/components/MonitorCard.tsx index 5b4a07a..5a9b6c3 100644 --- a/web/src/components/MonitorCard.tsx +++ b/web/src/components/MonitorCard.tsx @@ -1,24 +1,36 @@ -import type { Item } from "../gen/homelab/v1/homelab_pb"; +import type { Item, Monitor } from "../gen/homelab/v1/homelab_pb"; /** Карточка монитора: percentage → radial-progress, иначе текст. */ -export function MonitorCard({ s }: { s: Item }) { - const pct = Number.parseInt(s.value, 10); - const isPct = s.data === "percentage" && !Number.isNaN(pct); +export function MonitorCard({ s }: { s: Monitor }) { + const pct = s.percentage; + const isPct = !Number.isNaN(pct); return (
-
-

{s.name}

- {isPct ? ( -
- {pct}% -
- ) : ( -

{s.value || "…"}

- )} +
+
+

+ {s.name} +

+ {isPct ? ( +
+ {pct}% +
+ ) : ( +

{s.percentage || "…"}

+ )} +
+
+ {s.stats.map((it) => ( +
+ {it.name} + {it.value} +
+ ))} +
); diff --git a/web/src/gen/homelab/v1/homelab_pb.ts b/web/src/gen/homelab/v1/homelab_pb.ts index b3da449..7e94cdb 100644 --- a/web/src/gen/homelab/v1/homelab_pb.ts +++ b/web/src/gen/homelab/v1/homelab_pb.ts @@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file homelab/v1/homelab.proto. */ export const file_homelab_v1_homelab: GenFile = /*@__PURE__*/ - fileDesc("Chhob21lbGFiL3YxL2hvbWVsYWIucHJvdG8SCmhvbWVsYWIudjEiUgoESXRlbRIMCgRuYW1lGAEgASgJEgwKBGRhdGEYAiABKAkSDQoFdmFsdWUYAyABKAkSDAoEYXJncxgEIAEoBRIRCglhcmdfdHlwZXMYBSADKAkiIQoETGluaxIMCgRuYW1lGAEgASgJEgsKA3VybBgCIAEoCSIUChJMaXN0Q29udGVudFJlcXVlc3QioQEKE0xpc3RDb250ZW50UmVzcG9uc2USHwoFbGlua3MYASADKAsyEC5ob21lbGFiLnYxLkxpbmsSIgoIbW9uaXRvcnMYAiADKAsyEC5ob21lbGFiLnYxLkl0ZW0SHgoEZXhlYxgDIAMoCzIQLmhvbWVsYWIudjEuSXRlbRIlCghzZXJ2aWNlcxgEIAMoCzITLmhvbWVsYWIudjEuU2VydmljZSIlCgdEb2NNZXRhEgwKBG5hbWUYASABKAkSDAoEaGFzaBgCIAEoCSIRCg9MaXN0RG9jc1JlcXVlc3QiNQoQTGlzdERvY3NSZXNwb25zZRIhCgRkb2NzGAEgAygLMhMuaG9tZWxhYi52MS5Eb2NNZXRhIh0KDUdldERvY1JlcXVlc3QSDAoEbmFtZRgBIAEoCSIhCg5HZXREb2NSZXNwb25zZRIPCgdjb250ZW50GAEgASgJIi8KDlNhdmVEb2NSZXF1ZXN0EgwKBG5hbWUYASABKAkSDwoHY29udGVudBgCIAEoCSIfCg9TYXZlRG9jUmVzcG9uc2USDAoEaGFzaBgBIAEoCSKvAQoHU2VydmljZRIlCgRraW5kGAEgASgOMhcuaG9tZWxhYi52MS5TZXJ2aWNlS2luZBIMCgRuYW1lGAIgASgJEg0KBXN0YXRlGAMgASgJEhAKCHN1YnN0YXRlGAQgASgJEhIKCmxvYWRfc3RhdGUYBSABKAkSFwoPdW5pdF9maWxlX3N0YXRlGAYgASgJEhIKCmNvbnRhaW5lcnMYByADKAkSDQoFZXJyb3IYCCABKAkibgoUU2VydmljZUFjdGlvblJlcXVlc3QSDAoEbmFtZRgBIAEoCRIlCgRraW5kGAIgASgOMhcuaG9tZWxhYi52MS5TZXJ2aWNlS2luZBIhCgJvcBgDIAEoDjIVLmhvbWVsYWIudjEuU2VydmljZU9wIicKFVNlcnZpY2VBY3Rpb25SZXNwb25zZRIOCgZvdXRwdXQYASABKAkiSQoSU2VydmljZUluZm9SZXF1ZXN0EgwKBG5hbWUYASABKAkSJQoEa2luZBgCIAEoDjIXLmhvbWVsYWIudjEuU2VydmljZUtpbmQiJQoTU2VydmljZUluZm9SZXNwb25zZRIOCgZvdXRwdXQYASABKAkiLgoQUnVuU2NyaXB0UmVxdWVzdBIMCgRuYW1lGAEgASgJEgwKBGFyZ3MYAiADKAkiIwoRUnVuU2NyaXB0UmVzcG9uc2USDgoGb3V0cHV0GAEgASgJKiYKC1NlcnZpY2VLaW5kEgsKB1NZU1RFTUQQABIKCgZET0NLRVIQASotCglTZXJ2aWNlT3ASCQoFU1RBUlQQABIICgRTVE9QEAESCwoHUkVTVEFSVBACMpwECg5Ib21lbGFiU2VydmljZRJOCgtMaXN0Q29udGVudBIeLmhvbWVsYWIudjEuTGlzdENvbnRlbnRSZXF1ZXN0Gh8uaG9tZWxhYi52MS5MaXN0Q29udGVudFJlc3BvbnNlEkUKCExpc3REb2NzEhsuaG9tZWxhYi52MS5MaXN0RG9jc1JlcXVlc3QaHC5ob21lbGFiLnYxLkxpc3REb2NzUmVzcG9uc2USPwoGR2V0RG9jEhkuaG9tZWxhYi52MS5HZXREb2NSZXF1ZXN0GhouaG9tZWxhYi52MS5HZXREb2NSZXNwb25zZRJCCgdTYXZlRG9jEhouaG9tZWxhYi52MS5TYXZlRG9jUmVxdWVzdBobLmhvbWVsYWIudjEuU2F2ZURvY1Jlc3BvbnNlElQKDVNlcnZpY2VBY3Rpb24SIC5ob21lbGFiLnYxLlNlcnZpY2VBY3Rpb25SZXF1ZXN0GiEuaG9tZWxhYi52MS5TZXJ2aWNlQWN0aW9uUmVzcG9uc2USTgoLU2VydmljZUluZm8SHi5ob21lbGFiLnYxLlNlcnZpY2VJbmZvUmVxdWVzdBofLmhvbWVsYWIudjEuU2VydmljZUluZm9SZXNwb25zZRJICglSdW5TY3JpcHQSHC5ob21lbGFiLnYxLlJ1blNjcmlwdFJlcXVlc3QaHS5ob21lbGFiLnYxLlJ1blNjcmlwdFJlc3BvbnNlQipaKHB3YS1ob21lbGFiLW1vbi9nZW4vaG9tZWxhYi92MTtob21lbGFidjFiBnByb3RvMw"); + fileDesc("Chhob21lbGFiL3YxL2hvbWVsYWIucHJvdG8SCmhvbWVsYWIudjEiNQoESXRlbRIMCgRuYW1lGAEgASgJEgwKBGFyZ3MYBCABKAUSEQoJYXJnX3R5cGVzGAUgAygJIiMKBFN0YXQSDAoEbmFtZRgBIAEoCRINCgV2YWx1ZRgCIAEoCSJMCgdNb25pdG9yEgwKBG5hbWUYASABKAkSEgoKcGVyY2VudGFnZRgCIAEoBRIfCgVzdGF0cxgDIAMoCzIQLmhvbWVsYWIudjEuU3RhdCIhCgRMaW5rEgwKBG5hbWUYASABKAkSCwoDdXJsGAIgASgJIhQKEkxpc3RDb250ZW50UmVxdWVzdCKkAQoTTGlzdENvbnRlbnRSZXNwb25zZRIfCgVsaW5rcxgBIAMoCzIQLmhvbWVsYWIudjEuTGluaxIlCghtb25pdG9ycxgCIAMoCzITLmhvbWVsYWIudjEuTW9uaXRvchIeCgRleGVjGAMgAygLMhAuaG9tZWxhYi52MS5JdGVtEiUKCHNlcnZpY2VzGAQgAygLMhMuaG9tZWxhYi52MS5TZXJ2aWNlIiUKB0RvY01ldGESDAoEbmFtZRgBIAEoCRIMCgRoYXNoGAIgASgJIhEKD0xpc3REb2NzUmVxdWVzdCI1ChBMaXN0RG9jc1Jlc3BvbnNlEiEKBGRvY3MYASADKAsyEy5ob21lbGFiLnYxLkRvY01ldGEiHQoNR2V0RG9jUmVxdWVzdBIMCgRuYW1lGAEgASgJIiEKDkdldERvY1Jlc3BvbnNlEg8KB2NvbnRlbnQYASABKAkiLwoOU2F2ZURvY1JlcXVlc3QSDAoEbmFtZRgBIAEoCRIPCgdjb250ZW50GAIgASgJIh8KD1NhdmVEb2NSZXNwb25zZRIMCgRoYXNoGAEgASgJIq8BCgdTZXJ2aWNlEiUKBGtpbmQYASABKA4yFy5ob21lbGFiLnYxLlNlcnZpY2VLaW5kEgwKBG5hbWUYAiABKAkSDQoFc3RhdGUYAyABKAkSEAoIc3Vic3RhdGUYBCABKAkSEgoKbG9hZF9zdGF0ZRgFIAEoCRIXCg91bml0X2ZpbGVfc3RhdGUYBiABKAkSEgoKY29udGFpbmVycxgHIAMoCRINCgVlcnJvchgIIAEoCSJuChRTZXJ2aWNlQWN0aW9uUmVxdWVzdBIMCgRuYW1lGAEgASgJEiUKBGtpbmQYAiABKA4yFy5ob21lbGFiLnYxLlNlcnZpY2VLaW5kEiEKAm9wGAMgASgOMhUuaG9tZWxhYi52MS5TZXJ2aWNlT3AiJwoVU2VydmljZUFjdGlvblJlc3BvbnNlEg4KBm91dHB1dBgBIAEoCSJJChJTZXJ2aWNlSW5mb1JlcXVlc3QSDAoEbmFtZRgBIAEoCRIlCgRraW5kGAIgASgOMhcuaG9tZWxhYi52MS5TZXJ2aWNlS2luZCIlChNTZXJ2aWNlSW5mb1Jlc3BvbnNlEg4KBm91dHB1dBgBIAEoCSIuChBSdW5TY3JpcHRSZXF1ZXN0EgwKBG5hbWUYASABKAkSDAoEYXJncxgCIAMoCSIjChFSdW5TY3JpcHRSZXNwb25zZRIOCgZvdXRwdXQYASABKAkqJgoLU2VydmljZUtpbmQSCwoHU1lTVEVNRBAAEgoKBkRPQ0tFUhABKi0KCVNlcnZpY2VPcBIJCgVTVEFSVBAAEggKBFNUT1AQARILCgdSRVNUQVJUEAIynAQKDkhvbWVsYWJTZXJ2aWNlEk4KC0xpc3RDb250ZW50Eh4uaG9tZWxhYi52MS5MaXN0Q29udGVudFJlcXVlc3QaHy5ob21lbGFiLnYxLkxpc3RDb250ZW50UmVzcG9uc2USRQoITGlzdERvY3MSGy5ob21lbGFiLnYxLkxpc3REb2NzUmVxdWVzdBocLmhvbWVsYWIudjEuTGlzdERvY3NSZXNwb25zZRI/CgZHZXREb2MSGS5ob21lbGFiLnYxLkdldERvY1JlcXVlc3QaGi5ob21lbGFiLnYxLkdldERvY1Jlc3BvbnNlEkIKB1NhdmVEb2MSGi5ob21lbGFiLnYxLlNhdmVEb2NSZXF1ZXN0GhsuaG9tZWxhYi52MS5TYXZlRG9jUmVzcG9uc2USVAoNU2VydmljZUFjdGlvbhIgLmhvbWVsYWIudjEuU2VydmljZUFjdGlvblJlcXVlc3QaIS5ob21lbGFiLnYxLlNlcnZpY2VBY3Rpb25SZXNwb25zZRJOCgtTZXJ2aWNlSW5mbxIeLmhvbWVsYWIudjEuU2VydmljZUluZm9SZXF1ZXN0Gh8uaG9tZWxhYi52MS5TZXJ2aWNlSW5mb1Jlc3BvbnNlEkgKCVJ1blNjcmlwdBIcLmhvbWVsYWIudjEuUnVuU2NyaXB0UmVxdWVzdBodLmhvbWVsYWIudjEuUnVuU2NyaXB0UmVzcG9uc2VCKloocHdhLWhvbWVsYWItbW9uL2dlbi9ob21lbGFiL3YxO2hvbWVsYWJ2MWIGcHJvdG8z"); /** * @generated from message homelab.v1.Item @@ -21,20 +21,6 @@ export type Item = Message<"homelab.v1.Item"> & { */ name: string; - /** - * monitor: text | percentage - * - * @generated from field: string data = 2; - */ - data: string; - - /** - * monitor: last output - * - * @generated from field: string value = 3; - */ - value: string; - /** * exec: arg count * @@ -57,6 +43,57 @@ export type Item = Message<"homelab.v1.Item"> & { export const ItemSchema: GenMessage = /*@__PURE__*/ messageDesc(file_homelab_v1_homelab, 0); +/** + * @generated from message homelab.v1.Stat + */ +export type Stat = Message<"homelab.v1.Stat"> & { + /** + * @generated from field: string name = 1; + */ + name: string; + + /** + * @generated from field: string value = 2; + */ + value: string; +}; + +/** + * Describes the message homelab.v1.Stat. + * Use `create(StatSchema)` to create a new message. + */ +export const StatSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_homelab_v1_homelab, 1); + +/** + * Карточка монитора: процент (-1 = скрипт не дал процент) + строки для подписей. + * + * @generated from message homelab.v1.Monitor + */ +export type Monitor = Message<"homelab.v1.Monitor"> & { + /** + * @generated from field: string name = 1; + */ + name: string; + + /** + * @generated from field: int32 percentage = 2; + */ + percentage: number; + + /** + * @generated from field: repeated homelab.v1.Stat stats = 3; + */ + stats: Stat[]; +}; + +/** + * Describes the message homelab.v1.Monitor. + * Use `create(MonitorSchema)` to create a new message. + */ +export const MonitorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_homelab_v1_homelab, 2); + /** * @generated from message homelab.v1.Link */ @@ -77,7 +114,7 @@ export type Link = Message<"homelab.v1.Link"> & { * Use `create(LinkSchema)` to create a new message. */ export const LinkSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_homelab_v1_homelab, 1); + messageDesc(file_homelab_v1_homelab, 3); /** * @generated from message homelab.v1.ListContentRequest @@ -90,7 +127,7 @@ export type ListContentRequest = Message<"homelab.v1.ListContentRequest"> & { * Use `create(ListContentRequestSchema)` to create a new message. */ export const ListContentRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_homelab_v1_homelab, 2); + messageDesc(file_homelab_v1_homelab, 4); /** * @generated from message homelab.v1.ListContentResponse @@ -102,9 +139,9 @@ export type ListContentResponse = Message<"homelab.v1.ListContentResponse"> & { links: Link[]; /** - * @generated from field: repeated homelab.v1.Item monitors = 2; + * @generated from field: repeated homelab.v1.Monitor monitors = 2; */ - monitors: Item[]; + monitors: Monitor[]; /** * @generated from field: repeated homelab.v1.Item exec = 3; @@ -122,7 +159,7 @@ export type ListContentResponse = Message<"homelab.v1.ListContentResponse"> & { * Use `create(ListContentResponseSchema)` to create a new message. */ export const ListContentResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_homelab_v1_homelab, 3); + messageDesc(file_homelab_v1_homelab, 5); /** * @generated from message homelab.v1.DocMeta @@ -146,7 +183,7 @@ export type DocMeta = Message<"homelab.v1.DocMeta"> & { * Use `create(DocMetaSchema)` to create a new message. */ export const DocMetaSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_homelab_v1_homelab, 4); + messageDesc(file_homelab_v1_homelab, 6); /** * @generated from message homelab.v1.ListDocsRequest @@ -159,7 +196,7 @@ export type ListDocsRequest = Message<"homelab.v1.ListDocsRequest"> & { * Use `create(ListDocsRequestSchema)` to create a new message. */ export const ListDocsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_homelab_v1_homelab, 5); + messageDesc(file_homelab_v1_homelab, 7); /** * @generated from message homelab.v1.ListDocsResponse @@ -176,7 +213,7 @@ export type ListDocsResponse = Message<"homelab.v1.ListDocsResponse"> & { * Use `create(ListDocsResponseSchema)` to create a new message. */ export const ListDocsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_homelab_v1_homelab, 6); + messageDesc(file_homelab_v1_homelab, 8); /** * @generated from message homelab.v1.GetDocRequest @@ -193,7 +230,7 @@ export type GetDocRequest = Message<"homelab.v1.GetDocRequest"> & { * Use `create(GetDocRequestSchema)` to create a new message. */ export const GetDocRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_homelab_v1_homelab, 7); + messageDesc(file_homelab_v1_homelab, 9); /** * @generated from message homelab.v1.GetDocResponse @@ -210,7 +247,7 @@ export type GetDocResponse = Message<"homelab.v1.GetDocResponse"> & { * Use `create(GetDocResponseSchema)` to create a new message. */ export const GetDocResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_homelab_v1_homelab, 8); + messageDesc(file_homelab_v1_homelab, 10); /** * @generated from message homelab.v1.SaveDocRequest @@ -232,7 +269,7 @@ export type SaveDocRequest = Message<"homelab.v1.SaveDocRequest"> & { * Use `create(SaveDocRequestSchema)` to create a new message. */ export const SaveDocRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_homelab_v1_homelab, 9); + messageDesc(file_homelab_v1_homelab, 11); /** * @generated from message homelab.v1.SaveDocResponse @@ -249,7 +286,7 @@ export type SaveDocResponse = Message<"homelab.v1.SaveDocResponse"> & { * Use `create(SaveDocResponseSchema)` to create a new message. */ export const SaveDocResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_homelab_v1_homelab, 10); + messageDesc(file_homelab_v1_homelab, 12); /** * @generated from message homelab.v1.Service @@ -315,7 +352,7 @@ export type Service = Message<"homelab.v1.Service"> & { * Use `create(ServiceSchema)` to create a new message. */ export const ServiceSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_homelab_v1_homelab, 11); + messageDesc(file_homelab_v1_homelab, 13); /** * @generated from message homelab.v1.ServiceActionRequest @@ -342,7 +379,7 @@ export type ServiceActionRequest = Message<"homelab.v1.ServiceActionRequest"> & * Use `create(ServiceActionRequestSchema)` to create a new message. */ export const ServiceActionRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_homelab_v1_homelab, 12); + messageDesc(file_homelab_v1_homelab, 14); /** * @generated from message homelab.v1.ServiceActionResponse @@ -359,7 +396,7 @@ export type ServiceActionResponse = Message<"homelab.v1.ServiceActionResponse"> * Use `create(ServiceActionResponseSchema)` to create a new message. */ export const ServiceActionResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_homelab_v1_homelab, 13); + messageDesc(file_homelab_v1_homelab, 15); /** * @generated from message homelab.v1.ServiceInfoRequest @@ -381,7 +418,7 @@ export type ServiceInfoRequest = Message<"homelab.v1.ServiceInfoRequest"> & { * Use `create(ServiceInfoRequestSchema)` to create a new message. */ export const ServiceInfoRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_homelab_v1_homelab, 14); + messageDesc(file_homelab_v1_homelab, 16); /** * @generated from message homelab.v1.ServiceInfoResponse @@ -398,7 +435,7 @@ export type ServiceInfoResponse = Message<"homelab.v1.ServiceInfoResponse"> & { * Use `create(ServiceInfoResponseSchema)` to create a new message. */ export const ServiceInfoResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_homelab_v1_homelab, 15); + messageDesc(file_homelab_v1_homelab, 17); /** * @generated from message homelab.v1.RunScriptRequest @@ -420,7 +457,7 @@ export type RunScriptRequest = Message<"homelab.v1.RunScriptRequest"> & { * Use `create(RunScriptRequestSchema)` to create a new message. */ export const RunScriptRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_homelab_v1_homelab, 16); + messageDesc(file_homelab_v1_homelab, 18); /** * @generated from message homelab.v1.RunScriptResponse @@ -437,7 +474,7 @@ export type RunScriptResponse = Message<"homelab.v1.RunScriptResponse"> & { * Use `create(RunScriptResponseSchema)` to create a new message. */ export const RunScriptResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_homelab_v1_homelab, 17); + messageDesc(file_homelab_v1_homelab, 19); /** * One card in the Services view: a systemd user unit or a docker compose project.