@@ -15,7 +15,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Deploy to box
|
- name: Deploy to box
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ~/apps/hub/dist ~/apps/hub/scripts
|
mkdir -p ~/apps/hub/dist ~/apps/hub/scripts ~/apps/hub/docs
|
||||||
cp bin/hub-api ~/apps/hub/hub-api.new
|
cp bin/hub-api ~/apps/hub/hub-api.new
|
||||||
mv -f ~/apps/hub/hub-api.new ~/apps/hub/hub-api
|
mv -f ~/apps/hub/hub-api.new ~/apps/hub/hub-api
|
||||||
chmod +x ~/apps/hub/hub-api
|
chmod +x ~/apps/hub/hub-api
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
require("custom.runner").setup({
|
||||||
|
commands = {
|
||||||
|
-- Bottom terminal (focused after exec)
|
||||||
|
-- run = function()
|
||||||
|
-- local command = require("custom.runner").command
|
||||||
|
-- command:add({
|
||||||
|
-- cmd = "make",
|
||||||
|
-- flags = { "run" },
|
||||||
|
-- })
|
||||||
|
-- command.target = { position = "bottom" }
|
||||||
|
-- command.focus = true
|
||||||
|
-- command:run()
|
||||||
|
-- end,
|
||||||
|
-- float-job (no target): executes in a float
|
||||||
|
-- debug = function()
|
||||||
|
-- local command = require("custom.runner").command
|
||||||
|
-- command:add({
|
||||||
|
-- cmd = "odin build .",
|
||||||
|
-- flags = { "-debug" },
|
||||||
|
-- })
|
||||||
|
-- command:run()
|
||||||
|
-- end,
|
||||||
|
},
|
||||||
|
-- additional excludes for snacks picker
|
||||||
|
exclude = {
|
||||||
|
"gen",
|
||||||
|
"*.png",
|
||||||
|
"go.mod",
|
||||||
|
"go.sum",
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -15,14 +15,20 @@ PWA для менеджмента homelab: монорепозиторий Go-с
|
|||||||
```
|
```
|
||||||
proto/homelab/v1/ контракт (единственный источник правды)
|
proto/homelab/v1/ контракт (единственный источник правды)
|
||||||
gen/ сгенерённый Go-код (коммитим)
|
gen/ сгенерённый Go-код (коммитим)
|
||||||
server/main.go весь сервер: конфиг, мониторы, RPC, статика
|
server/main.go весь сервер: конфиг, мониторы, RPC, статика, docs API
|
||||||
web/src/App.tsx layout, сайдбар секций, поллинг ListSections (3s)
|
web/src/App.tsx layout, топбар + hamburger-drawer, 4 хардкод-таба, поллинг ListContent (3s)
|
||||||
web/src/components/ MonitorCard (мониторы), ScriptCard (response-форма + модалка результата)
|
web/src/components/ MonitorCard, ScriptCard, Docs (ридер + textarea-редактор)
|
||||||
|
web/src/docsStore.ts localStorage доков (hub:doc:*), dirty-флаги, syncDocs: pull по хэшам + flush раз в 5s
|
||||||
web/src/gen/ сгенерированный TS-клиент
|
web/src/gen/ сгенерированный TS-клиент
|
||||||
web/public/ PWA-обвязка: manifest, sw.js, иконки
|
web/public/ PWA-обвязка: manifest, sw.js, иконки
|
||||||
scripts/ bash-скрипты, пути из config.yaml
|
scripts/ bash-скрипты, пути из config.yaml
|
||||||
|
docs/ markdown-доки, НЕ в репо — живут только на сервере рядом с бинарьём
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Категории
|
||||||
|
|
||||||
|
Хардкод в бандле (App.tsx CATEGORIES): Links (из config links), Monitor (config monitor), Exec (config exec), Docs (папка docs/, API ListDocs/GetDoc/SaveDoc). Оффлайн: контент-стейт держит последний ответ, доки — localStorage; правки в оффлайне помечаются dirty и флашатся на сервер каждые 5s (client authority — последняя запись побеждает, без base-hash check).
|
||||||
|
|
||||||
## Команды (всё через make)
|
## Команды (всё через make)
|
||||||
|
|
||||||
- `make dev` — сервер :8080 + vite :5173 (работать на http://localhost:5173)
|
- `make dev` — сервер :8080 + vite :5173 (работать на http://localhost:5173)
|
||||||
@@ -46,7 +52,9 @@ scripts/ bash-скрипты, пути из config.yaml
|
|||||||
- `int64` в proto → `bigint` в TS (не `number`!).
|
- `int64` в proto → `bigint` в TS (не `number`!).
|
||||||
- Поля protobuf в TS — camelCase (`arg_types` → `argTypes`).
|
- Поля protobuf в TS — camelCase (`arg_types` → `argTypes`).
|
||||||
- Новый item-тип или поле в yaml = обновить proto + валидацию в `loadConfig` + компонент на фронте.
|
- Новый item-тип или поле в yaml = обновить proto + валидацию в `loadConfig` + компонент на фронте.
|
||||||
- Иконки секций: строка из конфига → ручной маппинг `ICONS` в `App.tsx` (не тянуть весь lucide через `icons` — раздувает бандл в 5 раз).
|
- Иконки: хардкод в компонентах из lucide-preact поимённо (не тянуть весь lucide через `icons` — раздувает бандл в 5 раз).
|
||||||
|
- Маркдаун: `marked` (~10KB gzip) + dangerouslySetInnerHTML без санитайзера (контент свой). Если редактор перестанет нравиться — **переход на MDXEditor** ( agreed upgrade path, изолирован в компоненте Docs).
|
||||||
|
- index.html и sw.js отдаются с `Cache-Control: no-cache` — иначе iOS не подхватит новый бандл.
|
||||||
- Логи сервера: monitors и RunScript пишут в stdout; `ListSections` (поллинг-шум) исключён из HTTP-лога echo.
|
- Логи сервера: monitors и RunScript пишут в stdout; `ListSections` (поллинг-шум) исключён из HTTP-лога echo.
|
||||||
- SW: `web/public/sw.js`, версия кэша `homelab-shell-vN` — **бампить при изменении** офлайн-логики, иначе iOS не обновит.
|
- SW: `web/public/sw.js`, версия кэша `homelab-shell-vN` — **бампить при изменении** офлайн-логики, иначе iOS не обновит.
|
||||||
- Тесты: `make test` (smoke RPC) + `tsc --noEmit` внутри `make web`. Не плодить фреймворки.
|
- Тесты: `make test` (smoke RPC) + `tsc --noEmit` внутри `make web`. Не плодить фреймворки.
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ dev-web:
|
|||||||
test: ## Smoke-test the RPC endpoints
|
test: ## Smoke-test the RPC endpoints
|
||||||
@go build -o bin/hub-api ./server
|
@go build -o bin/hub-api ./server
|
||||||
@./bin/hub-api & sleep 1; \
|
@./bin/hub-api & sleep 1; \
|
||||||
curl -sf -X POST localhost:8080/homelab.v1.HomelabService/ListSections -H 'Content-Type: application/json' -d '{}' | grep -q '"cpu"' || exit 1; \
|
curl -sf -X POST localhost:8080/homelab.v1.HomelabService/ListContent -H 'Content-Type: application/json' -d '{}' | grep -q '"cpu"' || exit 1; \
|
||||||
|
curl -sf -X POST localhost:8080/homelab.v1.HomelabService/ListDocs -H 'Content-Type: application/json' -d '{}' | grep -q 'test.md' || exit 1; \
|
||||||
curl -sf -X POST localhost:8080/homelab.v1.HomelabService/RunScript -H 'Content-Type: application/json' -d '{"name":"greet","args":["pony","42"]}' | grep -q 'arg1: pony' || exit 1; \
|
curl -sf -X POST localhost:8080/homelab.v1.HomelabService/RunScript -H 'Content-Type: application/json' -d '{"name":"greet","args":["pony","42"]}' | grep -q 'arg1: pony' || exit 1; \
|
||||||
kill %1; echo OK
|
kill %1; echo OK
|
||||||
|
|||||||
+5
-6
@@ -1,14 +1,13 @@
|
|||||||
sections:
|
links:
|
||||||
- name: Overview
|
- { name: Proxmox, url: "https://pve.local:8006" }
|
||||||
icon: gauge
|
- { name: Router, url: "http://192.168.1.1" }
|
||||||
items:
|
monitor:
|
||||||
- name: cpu
|
- name: cpu
|
||||||
type: monitor
|
|
||||||
script: scripts/cpu.sh
|
script: scripts/cpu.sh
|
||||||
refresh: 5
|
refresh: 5
|
||||||
data: percentage
|
data: percentage
|
||||||
|
exec:
|
||||||
- name: greet
|
- name: greet
|
||||||
type: response
|
|
||||||
script: scripts/greet.sh
|
script: scripts/greet.sh
|
||||||
args: 2
|
args: 2
|
||||||
arg_types: [text, number]
|
arg_types: [text, number]
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
# Second doc
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Test doc
|
||||||
|
|
||||||
|
Hello **world**, list:
|
||||||
|
- one
|
||||||
|
- two
|
||||||
|
|
||||||
|
`code` and [link](https://example.com)
|
||||||
|
|
||||||
|
|
||||||
|
Edited OFFLINE
|
||||||
|
|
||||||
|
Edited OFFLINE v3
|
||||||
+439
-90
@@ -24,11 +24,10 @@ const (
|
|||||||
type Item struct {
|
type Item struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` // monitor | response
|
Data string `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` // monitor: text | percentage
|
||||||
Data string `protobuf:"bytes,3,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
|
||||||
Value string `protobuf:"bytes,4,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
|
||||||
Args int32 `protobuf:"varint,5,opt,name=args,proto3" json:"args,omitempty"` // response: arg count
|
ArgTypes []string `protobuf:"bytes,5,rep,name=arg_types,json=argTypes,proto3" json:"arg_types,omitempty"` // exec: text | number per arg
|
||||||
ArgTypes []string `protobuf:"bytes,6,rep,name=arg_types,json=argTypes,proto3" json:"arg_types,omitempty"` // response: text | number per arg
|
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@@ -70,13 +69,6 @@ func (x *Item) GetName() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Item) GetType() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Type
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Item) GetData() string {
|
func (x *Item) GetData() string {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Data
|
return x.Data
|
||||||
@@ -105,29 +97,28 @@ func (x *Item) GetArgTypes() []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Section struct {
|
type Link struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
Icon string `protobuf:"bytes,2,opt,name=icon,proto3" json:"icon,omitempty"`
|
Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"`
|
||||||
Items []*Item `protobuf:"bytes,3,rep,name=items,proto3" json:"items,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Section) Reset() {
|
func (x *Link) Reset() {
|
||||||
*x = Section{}
|
*x = Link{}
|
||||||
mi := &file_homelab_v1_homelab_proto_msgTypes[1]
|
mi := &file_homelab_v1_homelab_proto_msgTypes[1]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Section) String() string {
|
func (x *Link) String() string {
|
||||||
return protoimpl.X.MessageStringOf(x)
|
return protoimpl.X.MessageStringOf(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Section) ProtoMessage() {}
|
func (*Link) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *Section) ProtoReflect() protoreflect.Message {
|
func (x *Link) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_homelab_v1_homelab_proto_msgTypes[1]
|
mi := &file_homelab_v1_homelab_proto_msgTypes[1]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
@@ -139,52 +130,45 @@ func (x *Section) ProtoReflect() protoreflect.Message {
|
|||||||
return mi.MessageOf(x)
|
return mi.MessageOf(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: Use Section.ProtoReflect.Descriptor instead.
|
// Deprecated: Use Link.ProtoReflect.Descriptor instead.
|
||||||
func (*Section) Descriptor() ([]byte, []int) {
|
func (*Link) Descriptor() ([]byte, []int) {
|
||||||
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{1}
|
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{1}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Section) GetName() string {
|
func (x *Link) GetName() string {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Name
|
return x.Name
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Section) GetIcon() string {
|
func (x *Link) GetUrl() string {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Icon
|
return x.Url
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Section) GetItems() []*Item {
|
type ListContentRequest struct {
|
||||||
if x != nil {
|
|
||||||
return x.Items
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListSectionsRequest struct {
|
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ListSectionsRequest) Reset() {
|
func (x *ListContentRequest) Reset() {
|
||||||
*x = ListSectionsRequest{}
|
*x = ListContentRequest{}
|
||||||
mi := &file_homelab_v1_homelab_proto_msgTypes[2]
|
mi := &file_homelab_v1_homelab_proto_msgTypes[2]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ListSectionsRequest) String() string {
|
func (x *ListContentRequest) String() string {
|
||||||
return protoimpl.X.MessageStringOf(x)
|
return protoimpl.X.MessageStringOf(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*ListSectionsRequest) ProtoMessage() {}
|
func (*ListContentRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *ListSectionsRequest) ProtoReflect() protoreflect.Message {
|
func (x *ListContentRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_homelab_v1_homelab_proto_msgTypes[2]
|
mi := &file_homelab_v1_homelab_proto_msgTypes[2]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
@@ -196,32 +180,34 @@ func (x *ListSectionsRequest) ProtoReflect() protoreflect.Message {
|
|||||||
return mi.MessageOf(x)
|
return mi.MessageOf(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: Use ListSectionsRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ListContentRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*ListSectionsRequest) Descriptor() ([]byte, []int) {
|
func (*ListContentRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{2}
|
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{2}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListSectionsResponse struct {
|
type ListContentResponse struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Sections []*Section `protobuf:"bytes,1,rep,name=sections,proto3" json:"sections,omitempty"`
|
Links []*Link `protobuf:"bytes,1,rep,name=links,proto3" json:"links,omitempty"`
|
||||||
|
Monitors []*Item `protobuf:"bytes,2,rep,name=monitors,proto3" json:"monitors,omitempty"`
|
||||||
|
Exec []*Item `protobuf:"bytes,3,rep,name=exec,proto3" json:"exec,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ListSectionsResponse) Reset() {
|
func (x *ListContentResponse) Reset() {
|
||||||
*x = ListSectionsResponse{}
|
*x = ListContentResponse{}
|
||||||
mi := &file_homelab_v1_homelab_proto_msgTypes[3]
|
mi := &file_homelab_v1_homelab_proto_msgTypes[3]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ListSectionsResponse) String() string {
|
func (x *ListContentResponse) String() string {
|
||||||
return protoimpl.X.MessageStringOf(x)
|
return protoimpl.X.MessageStringOf(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*ListSectionsResponse) ProtoMessage() {}
|
func (*ListContentResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *ListSectionsResponse) ProtoReflect() protoreflect.Message {
|
func (x *ListContentResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_homelab_v1_homelab_proto_msgTypes[3]
|
mi := &file_homelab_v1_homelab_proto_msgTypes[3]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
@@ -233,18 +219,348 @@ func (x *ListSectionsResponse) ProtoReflect() protoreflect.Message {
|
|||||||
return mi.MessageOf(x)
|
return mi.MessageOf(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: Use ListSectionsResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ListContentResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*ListSectionsResponse) Descriptor() ([]byte, []int) {
|
func (*ListContentResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{3}
|
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{3}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ListSectionsResponse) GetSections() []*Section {
|
func (x *ListContentResponse) GetLinks() []*Link {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Sections
|
return x.Links
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *ListContentResponse) GetMonitors() []*Item {
|
||||||
|
if x != nil {
|
||||||
|
return x.Monitors
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListContentResponse) GetExec() []*Item {
|
||||||
|
if x != nil {
|
||||||
|
return x.Exec
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DocMeta struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
Hash string `protobuf:"bytes,2,opt,name=hash,proto3" json:"hash,omitempty"` // sha256 hex of content
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DocMeta) Reset() {
|
||||||
|
*x = DocMeta{}
|
||||||
|
mi := &file_homelab_v1_homelab_proto_msgTypes[4]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DocMeta) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DocMeta) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *DocMeta) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_homelab_v1_homelab_proto_msgTypes[4]
|
||||||
|
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 DocMeta.ProtoReflect.Descriptor instead.
|
||||||
|
func (*DocMeta) Descriptor() ([]byte, []int) {
|
||||||
|
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DocMeta) GetName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *DocMeta) GetHash() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Hash
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListDocsRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListDocsRequest) Reset() {
|
||||||
|
*x = ListDocsRequest{}
|
||||||
|
mi := &file_homelab_v1_homelab_proto_msgTypes[5]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListDocsRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListDocsRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListDocsRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_homelab_v1_homelab_proto_msgTypes[5]
|
||||||
|
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 ListDocsRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListDocsRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{5}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListDocsResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Docs []*DocMeta `protobuf:"bytes,1,rep,name=docs,proto3" json:"docs,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListDocsResponse) Reset() {
|
||||||
|
*x = ListDocsResponse{}
|
||||||
|
mi := &file_homelab_v1_homelab_proto_msgTypes[6]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListDocsResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ListDocsResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *ListDocsResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_homelab_v1_homelab_proto_msgTypes[6]
|
||||||
|
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 ListDocsResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*ListDocsResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{6}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *ListDocsResponse) GetDocs() []*DocMeta {
|
||||||
|
if x != nil {
|
||||||
|
return x.Docs
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetDocRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetDocRequest) Reset() {
|
||||||
|
*x = GetDocRequest{}
|
||||||
|
mi := &file_homelab_v1_homelab_proto_msgTypes[7]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetDocRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GetDocRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *GetDocRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_homelab_v1_homelab_proto_msgTypes[7]
|
||||||
|
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 GetDocRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*GetDocRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{7}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetDocRequest) GetName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetDocResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Content string `protobuf:"bytes,1,opt,name=content,proto3" json:"content,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetDocResponse) Reset() {
|
||||||
|
*x = GetDocResponse{}
|
||||||
|
mi := &file_homelab_v1_homelab_proto_msgTypes[8]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetDocResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*GetDocResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *GetDocResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_homelab_v1_homelab_proto_msgTypes[8]
|
||||||
|
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 GetDocResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*GetDocResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{8}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetDocResponse) GetContent() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Content
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type SaveDocRequest struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
|
Content string `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SaveDocRequest) Reset() {
|
||||||
|
*x = SaveDocRequest{}
|
||||||
|
mi := &file_homelab_v1_homelab_proto_msgTypes[9]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SaveDocRequest) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*SaveDocRequest) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *SaveDocRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_homelab_v1_homelab_proto_msgTypes[9]
|
||||||
|
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 SaveDocRequest.ProtoReflect.Descriptor instead.
|
||||||
|
func (*SaveDocRequest) Descriptor() ([]byte, []int) {
|
||||||
|
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{9}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SaveDocRequest) GetName() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SaveDocRequest) GetContent() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Content
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type SaveDocResponse struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
Hash string `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SaveDocResponse) Reset() {
|
||||||
|
*x = SaveDocResponse{}
|
||||||
|
mi := &file_homelab_v1_homelab_proto_msgTypes[10]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SaveDocResponse) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*SaveDocResponse) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *SaveDocResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_homelab_v1_homelab_proto_msgTypes[10]
|
||||||
|
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 SaveDocResponse.ProtoReflect.Descriptor instead.
|
||||||
|
func (*SaveDocResponse) Descriptor() ([]byte, []int) {
|
||||||
|
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{10}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *SaveDocResponse) GetHash() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Hash
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
type RunScriptRequest struct {
|
type RunScriptRequest struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||||
@@ -255,7 +571,7 @@ type RunScriptRequest struct {
|
|||||||
|
|
||||||
func (x *RunScriptRequest) Reset() {
|
func (x *RunScriptRequest) Reset() {
|
||||||
*x = RunScriptRequest{}
|
*x = RunScriptRequest{}
|
||||||
mi := &file_homelab_v1_homelab_proto_msgTypes[4]
|
mi := &file_homelab_v1_homelab_proto_msgTypes[11]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -267,7 +583,7 @@ func (x *RunScriptRequest) String() string {
|
|||||||
func (*RunScriptRequest) ProtoMessage() {}
|
func (*RunScriptRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RunScriptRequest) ProtoReflect() protoreflect.Message {
|
func (x *RunScriptRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_homelab_v1_homelab_proto_msgTypes[4]
|
mi := &file_homelab_v1_homelab_proto_msgTypes[11]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -280,7 +596,7 @@ func (x *RunScriptRequest) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RunScriptRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RunScriptRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*RunScriptRequest) Descriptor() ([]byte, []int) {
|
func (*RunScriptRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{4}
|
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{11}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RunScriptRequest) GetName() string {
|
func (x *RunScriptRequest) GetName() string {
|
||||||
@@ -306,7 +622,7 @@ type RunScriptResponse struct {
|
|||||||
|
|
||||||
func (x *RunScriptResponse) Reset() {
|
func (x *RunScriptResponse) Reset() {
|
||||||
*x = RunScriptResponse{}
|
*x = RunScriptResponse{}
|
||||||
mi := &file_homelab_v1_homelab_proto_msgTypes[5]
|
mi := &file_homelab_v1_homelab_proto_msgTypes[12]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
@@ -318,7 +634,7 @@ func (x *RunScriptResponse) String() string {
|
|||||||
func (*RunScriptResponse) ProtoMessage() {}
|
func (*RunScriptResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *RunScriptResponse) ProtoReflect() protoreflect.Message {
|
func (x *RunScriptResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_homelab_v1_homelab_proto_msgTypes[5]
|
mi := &file_homelab_v1_homelab_proto_msgTypes[12]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
@@ -331,7 +647,7 @@ func (x *RunScriptResponse) ProtoReflect() protoreflect.Message {
|
|||||||
|
|
||||||
// Deprecated: Use RunScriptResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use RunScriptResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*RunScriptResponse) Descriptor() ([]byte, []int) {
|
func (*RunScriptResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{5}
|
return file_homelab_v1_homelab_proto_rawDescGZIP(), []int{12}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *RunScriptResponse) GetOutput() string {
|
func (x *RunScriptResponse) GetOutput() string {
|
||||||
@@ -346,28 +662,46 @@ var File_homelab_v1_homelab_proto protoreflect.FileDescriptor
|
|||||||
const file_homelab_v1_homelab_proto_rawDesc = "" +
|
const file_homelab_v1_homelab_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\x18homelab/v1/homelab.proto\x12\n" +
|
"\x18homelab/v1/homelab.proto\x12\n" +
|
||||||
"homelab.v1\"\x89\x01\n" +
|
"homelab.v1\"u\n" +
|
||||||
"\x04Item\x12\x12\n" +
|
"\x04Item\x12\x12\n" +
|
||||||
"\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" +
|
"\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" +
|
||||||
"\x04type\x18\x02 \x01(\tR\x04type\x12\x12\n" +
|
"\x04data\x18\x02 \x01(\tR\x04data\x12\x14\n" +
|
||||||
"\x04data\x18\x03 \x01(\tR\x04data\x12\x14\n" +
|
"\x05value\x18\x03 \x01(\tR\x05value\x12\x12\n" +
|
||||||
"\x05value\x18\x04 \x01(\tR\x05value\x12\x12\n" +
|
"\x04args\x18\x04 \x01(\x05R\x04args\x12\x1b\n" +
|
||||||
"\x04args\x18\x05 \x01(\x05R\x04args\x12\x1b\n" +
|
"\targ_types\x18\x05 \x03(\tR\bargTypes\",\n" +
|
||||||
"\targ_types\x18\x06 \x03(\tR\bargTypes\"Y\n" +
|
"\x04Link\x12\x12\n" +
|
||||||
"\aSection\x12\x12\n" +
|
"\x04name\x18\x01 \x01(\tR\x04name\x12\x10\n" +
|
||||||
|
"\x03url\x18\x02 \x01(\tR\x03url\"\x14\n" +
|
||||||
|
"\x12ListContentRequest\"\x91\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" +
|
||||||
|
"\x04exec\x18\x03 \x03(\v2\x10.homelab.v1.ItemR\x04exec\"1\n" +
|
||||||
|
"\aDocMeta\x12\x12\n" +
|
||||||
"\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" +
|
"\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" +
|
||||||
"\x04icon\x18\x02 \x01(\tR\x04icon\x12&\n" +
|
"\x04hash\x18\x02 \x01(\tR\x04hash\"\x11\n" +
|
||||||
"\x05items\x18\x03 \x03(\v2\x10.homelab.v1.ItemR\x05items\"\x15\n" +
|
"\x0fListDocsRequest\";\n" +
|
||||||
"\x13ListSectionsRequest\"G\n" +
|
"\x10ListDocsResponse\x12'\n" +
|
||||||
"\x14ListSectionsResponse\x12/\n" +
|
"\x04docs\x18\x01 \x03(\v2\x13.homelab.v1.DocMetaR\x04docs\"#\n" +
|
||||||
"\bsections\x18\x01 \x03(\v2\x13.homelab.v1.SectionR\bsections\":\n" +
|
"\rGetDocRequest\x12\x12\n" +
|
||||||
|
"\x04name\x18\x01 \x01(\tR\x04name\"*\n" +
|
||||||
|
"\x0eGetDocResponse\x12\x18\n" +
|
||||||
|
"\acontent\x18\x01 \x01(\tR\acontent\">\n" +
|
||||||
|
"\x0eSaveDocRequest\x12\x12\n" +
|
||||||
|
"\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" +
|
||||||
|
"\acontent\x18\x02 \x01(\tR\acontent\"%\n" +
|
||||||
|
"\x0fSaveDocResponse\x12\x12\n" +
|
||||||
|
"\x04hash\x18\x01 \x01(\tR\x04hash\":\n" +
|
||||||
"\x10RunScriptRequest\x12\x12\n" +
|
"\x10RunScriptRequest\x12\x12\n" +
|
||||||
"\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" +
|
"\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" +
|
||||||
"\x04args\x18\x02 \x03(\tR\x04args\"+\n" +
|
"\x04args\x18\x02 \x03(\tR\x04args\"+\n" +
|
||||||
"\x11RunScriptResponse\x12\x16\n" +
|
"\x11RunScriptResponse\x12\x16\n" +
|
||||||
"\x06output\x18\x01 \x01(\tR\x06output2\xad\x01\n" +
|
"\x06output\x18\x01 \x01(\tR\x06output2\xf6\x02\n" +
|
||||||
"\x0eHomelabService\x12Q\n" +
|
"\x0eHomelabService\x12N\n" +
|
||||||
"\fListSections\x12\x1f.homelab.v1.ListSectionsRequest\x1a .homelab.v1.ListSectionsResponse\x12H\n" +
|
"\vListContent\x12\x1e.homelab.v1.ListContentRequest\x1a\x1f.homelab.v1.ListContentResponse\x12E\n" +
|
||||||
|
"\bListDocs\x12\x1b.homelab.v1.ListDocsRequest\x1a\x1c.homelab.v1.ListDocsResponse\x12?\n" +
|
||||||
|
"\x06GetDoc\x12\x19.homelab.v1.GetDocRequest\x1a\x1a.homelab.v1.GetDocResponse\x12B\n" +
|
||||||
|
"\aSaveDoc\x12\x1a.homelab.v1.SaveDocRequest\x1a\x1b.homelab.v1.SaveDocResponse\x12H\n" +
|
||||||
"\tRunScript\x12\x1c.homelab.v1.RunScriptRequest\x1a\x1d.homelab.v1.RunScriptResponseB*Z(pwa-homelab-mon/gen/homelab/v1;homelabv1b\x06proto3"
|
"\tRunScript\x12\x1c.homelab.v1.RunScriptRequest\x1a\x1d.homelab.v1.RunScriptResponseB*Z(pwa-homelab-mon/gen/homelab/v1;homelabv1b\x06proto3"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -382,27 +716,42 @@ func file_homelab_v1_homelab_proto_rawDescGZIP() []byte {
|
|||||||
return file_homelab_v1_homelab_proto_rawDescData
|
return file_homelab_v1_homelab_proto_rawDescData
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_homelab_v1_homelab_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
|
var file_homelab_v1_homelab_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
|
||||||
var file_homelab_v1_homelab_proto_goTypes = []any{
|
var file_homelab_v1_homelab_proto_goTypes = []any{
|
||||||
(*Item)(nil), // 0: homelab.v1.Item
|
(*Item)(nil), // 0: homelab.v1.Item
|
||||||
(*Section)(nil), // 1: homelab.v1.Section
|
(*Link)(nil), // 1: homelab.v1.Link
|
||||||
(*ListSectionsRequest)(nil), // 2: homelab.v1.ListSectionsRequest
|
(*ListContentRequest)(nil), // 2: homelab.v1.ListContentRequest
|
||||||
(*ListSectionsResponse)(nil), // 3: homelab.v1.ListSectionsResponse
|
(*ListContentResponse)(nil), // 3: homelab.v1.ListContentResponse
|
||||||
(*RunScriptRequest)(nil), // 4: homelab.v1.RunScriptRequest
|
(*DocMeta)(nil), // 4: homelab.v1.DocMeta
|
||||||
(*RunScriptResponse)(nil), // 5: homelab.v1.RunScriptResponse
|
(*ListDocsRequest)(nil), // 5: homelab.v1.ListDocsRequest
|
||||||
|
(*ListDocsResponse)(nil), // 6: homelab.v1.ListDocsResponse
|
||||||
|
(*GetDocRequest)(nil), // 7: homelab.v1.GetDocRequest
|
||||||
|
(*GetDocResponse)(nil), // 8: homelab.v1.GetDocResponse
|
||||||
|
(*SaveDocRequest)(nil), // 9: homelab.v1.SaveDocRequest
|
||||||
|
(*SaveDocResponse)(nil), // 10: homelab.v1.SaveDocResponse
|
||||||
|
(*RunScriptRequest)(nil), // 11: homelab.v1.RunScriptRequest
|
||||||
|
(*RunScriptResponse)(nil), // 12: homelab.v1.RunScriptResponse
|
||||||
}
|
}
|
||||||
var file_homelab_v1_homelab_proto_depIdxs = []int32{
|
var file_homelab_v1_homelab_proto_depIdxs = []int32{
|
||||||
0, // 0: homelab.v1.Section.items:type_name -> homelab.v1.Item
|
1, // 0: homelab.v1.ListContentResponse.links:type_name -> homelab.v1.Link
|
||||||
1, // 1: homelab.v1.ListSectionsResponse.sections:type_name -> homelab.v1.Section
|
0, // 1: homelab.v1.ListContentResponse.monitors:type_name -> homelab.v1.Item
|
||||||
2, // 2: homelab.v1.HomelabService.ListSections:input_type -> homelab.v1.ListSectionsRequest
|
0, // 2: homelab.v1.ListContentResponse.exec:type_name -> homelab.v1.Item
|
||||||
4, // 3: homelab.v1.HomelabService.RunScript:input_type -> homelab.v1.RunScriptRequest
|
4, // 3: homelab.v1.ListDocsResponse.docs:type_name -> homelab.v1.DocMeta
|
||||||
3, // 4: homelab.v1.HomelabService.ListSections:output_type -> homelab.v1.ListSectionsResponse
|
2, // 4: homelab.v1.HomelabService.ListContent:input_type -> homelab.v1.ListContentRequest
|
||||||
5, // 5: homelab.v1.HomelabService.RunScript:output_type -> homelab.v1.RunScriptResponse
|
5, // 5: homelab.v1.HomelabService.ListDocs:input_type -> homelab.v1.ListDocsRequest
|
||||||
4, // [4:6] is the sub-list for method output_type
|
7, // 6: homelab.v1.HomelabService.GetDoc:input_type -> homelab.v1.GetDocRequest
|
||||||
2, // [2:4] is the sub-list for method input_type
|
9, // 7: homelab.v1.HomelabService.SaveDoc:input_type -> homelab.v1.SaveDocRequest
|
||||||
2, // [2:2] is the sub-list for extension type_name
|
11, // 8: homelab.v1.HomelabService.RunScript:input_type -> homelab.v1.RunScriptRequest
|
||||||
2, // [2:2] is the sub-list for extension extendee
|
3, // 9: homelab.v1.HomelabService.ListContent:output_type -> homelab.v1.ListContentResponse
|
||||||
0, // [0:2] is the sub-list for field type_name
|
6, // 10: homelab.v1.HomelabService.ListDocs:output_type -> homelab.v1.ListDocsResponse
|
||||||
|
8, // 11: homelab.v1.HomelabService.GetDoc:output_type -> homelab.v1.GetDocResponse
|
||||||
|
10, // 12: homelab.v1.HomelabService.SaveDoc:output_type -> homelab.v1.SaveDocResponse
|
||||||
|
12, // 13: homelab.v1.HomelabService.RunScript:output_type -> homelab.v1.RunScriptResponse
|
||||||
|
9, // [9:14] is the sub-list for method output_type
|
||||||
|
4, // [4:9] is the sub-list for method input_type
|
||||||
|
4, // [4:4] is the sub-list for extension type_name
|
||||||
|
4, // [4:4] is the sub-list for extension extendee
|
||||||
|
0, // [0:4] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_homelab_v1_homelab_proto_init() }
|
func init() { file_homelab_v1_homelab_proto_init() }
|
||||||
@@ -416,7 +765,7 @@ func file_homelab_v1_homelab_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_homelab_v1_homelab_proto_rawDesc), len(file_homelab_v1_homelab_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_homelab_v1_homelab_proto_rawDesc), len(file_homelab_v1_homelab_proto_rawDesc)),
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 6,
|
NumMessages: 13,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -33,9 +33,15 @@ const (
|
|||||||
// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
|
// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
|
||||||
// period.
|
// period.
|
||||||
const (
|
const (
|
||||||
// HomelabServiceListSectionsProcedure is the fully-qualified name of the HomelabService's
|
// HomelabServiceListContentProcedure is the fully-qualified name of the HomelabService's
|
||||||
// ListSections RPC.
|
// ListContent RPC.
|
||||||
HomelabServiceListSectionsProcedure = "/homelab.v1.HomelabService/ListSections"
|
HomelabServiceListContentProcedure = "/homelab.v1.HomelabService/ListContent"
|
||||||
|
// HomelabServiceListDocsProcedure is the fully-qualified name of the HomelabService's ListDocs RPC.
|
||||||
|
HomelabServiceListDocsProcedure = "/homelab.v1.HomelabService/ListDocs"
|
||||||
|
// HomelabServiceGetDocProcedure is the fully-qualified name of the HomelabService's GetDoc RPC.
|
||||||
|
HomelabServiceGetDocProcedure = "/homelab.v1.HomelabService/GetDoc"
|
||||||
|
// HomelabServiceSaveDocProcedure is the fully-qualified name of the HomelabService's SaveDoc RPC.
|
||||||
|
HomelabServiceSaveDocProcedure = "/homelab.v1.HomelabService/SaveDoc"
|
||||||
// HomelabServiceRunScriptProcedure is the fully-qualified name of the HomelabService's RunScript
|
// HomelabServiceRunScriptProcedure is the fully-qualified name of the HomelabService's RunScript
|
||||||
// RPC.
|
// RPC.
|
||||||
HomelabServiceRunScriptProcedure = "/homelab.v1.HomelabService/RunScript"
|
HomelabServiceRunScriptProcedure = "/homelab.v1.HomelabService/RunScript"
|
||||||
@@ -43,7 +49,10 @@ const (
|
|||||||
|
|
||||||
// HomelabServiceClient is a client for the homelab.v1.HomelabService service.
|
// HomelabServiceClient is a client for the homelab.v1.HomelabService service.
|
||||||
type HomelabServiceClient interface {
|
type HomelabServiceClient interface {
|
||||||
ListSections(context.Context, *connect.Request[v1.ListSectionsRequest]) (*connect.Response[v1.ListSectionsResponse], error)
|
ListContent(context.Context, *connect.Request[v1.ListContentRequest]) (*connect.Response[v1.ListContentResponse], error)
|
||||||
|
ListDocs(context.Context, *connect.Request[v1.ListDocsRequest]) (*connect.Response[v1.ListDocsResponse], error)
|
||||||
|
GetDoc(context.Context, *connect.Request[v1.GetDocRequest]) (*connect.Response[v1.GetDocResponse], error)
|
||||||
|
SaveDoc(context.Context, *connect.Request[v1.SaveDocRequest]) (*connect.Response[v1.SaveDocResponse], error)
|
||||||
RunScript(context.Context, *connect.Request[v1.RunScriptRequest]) (*connect.Response[v1.RunScriptResponse], error)
|
RunScript(context.Context, *connect.Request[v1.RunScriptRequest]) (*connect.Response[v1.RunScriptResponse], error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,10 +67,28 @@ func NewHomelabServiceClient(httpClient connect.HTTPClient, baseURL string, opts
|
|||||||
baseURL = strings.TrimRight(baseURL, "/")
|
baseURL = strings.TrimRight(baseURL, "/")
|
||||||
homelabServiceMethods := v1.File_homelab_v1_homelab_proto.Services().ByName("HomelabService").Methods()
|
homelabServiceMethods := v1.File_homelab_v1_homelab_proto.Services().ByName("HomelabService").Methods()
|
||||||
return &homelabServiceClient{
|
return &homelabServiceClient{
|
||||||
listSections: connect.NewClient[v1.ListSectionsRequest, v1.ListSectionsResponse](
|
listContent: connect.NewClient[v1.ListContentRequest, v1.ListContentResponse](
|
||||||
httpClient,
|
httpClient,
|
||||||
baseURL+HomelabServiceListSectionsProcedure,
|
baseURL+HomelabServiceListContentProcedure,
|
||||||
connect.WithSchema(homelabServiceMethods.ByName("ListSections")),
|
connect.WithSchema(homelabServiceMethods.ByName("ListContent")),
|
||||||
|
connect.WithClientOptions(opts...),
|
||||||
|
),
|
||||||
|
listDocs: connect.NewClient[v1.ListDocsRequest, v1.ListDocsResponse](
|
||||||
|
httpClient,
|
||||||
|
baseURL+HomelabServiceListDocsProcedure,
|
||||||
|
connect.WithSchema(homelabServiceMethods.ByName("ListDocs")),
|
||||||
|
connect.WithClientOptions(opts...),
|
||||||
|
),
|
||||||
|
getDoc: connect.NewClient[v1.GetDocRequest, v1.GetDocResponse](
|
||||||
|
httpClient,
|
||||||
|
baseURL+HomelabServiceGetDocProcedure,
|
||||||
|
connect.WithSchema(homelabServiceMethods.ByName("GetDoc")),
|
||||||
|
connect.WithClientOptions(opts...),
|
||||||
|
),
|
||||||
|
saveDoc: connect.NewClient[v1.SaveDocRequest, v1.SaveDocResponse](
|
||||||
|
httpClient,
|
||||||
|
baseURL+HomelabServiceSaveDocProcedure,
|
||||||
|
connect.WithSchema(homelabServiceMethods.ByName("SaveDoc")),
|
||||||
connect.WithClientOptions(opts...),
|
connect.WithClientOptions(opts...),
|
||||||
),
|
),
|
||||||
runScript: connect.NewClient[v1.RunScriptRequest, v1.RunScriptResponse](
|
runScript: connect.NewClient[v1.RunScriptRequest, v1.RunScriptResponse](
|
||||||
@@ -75,13 +102,31 @@ func NewHomelabServiceClient(httpClient connect.HTTPClient, baseURL string, opts
|
|||||||
|
|
||||||
// homelabServiceClient implements HomelabServiceClient.
|
// homelabServiceClient implements HomelabServiceClient.
|
||||||
type homelabServiceClient struct {
|
type homelabServiceClient struct {
|
||||||
listSections *connect.Client[v1.ListSectionsRequest, v1.ListSectionsResponse]
|
listContent *connect.Client[v1.ListContentRequest, v1.ListContentResponse]
|
||||||
|
listDocs *connect.Client[v1.ListDocsRequest, v1.ListDocsResponse]
|
||||||
|
getDoc *connect.Client[v1.GetDocRequest, v1.GetDocResponse]
|
||||||
|
saveDoc *connect.Client[v1.SaveDocRequest, v1.SaveDocResponse]
|
||||||
runScript *connect.Client[v1.RunScriptRequest, v1.RunScriptResponse]
|
runScript *connect.Client[v1.RunScriptRequest, v1.RunScriptResponse]
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListSections calls homelab.v1.HomelabService.ListSections.
|
// ListContent calls homelab.v1.HomelabService.ListContent.
|
||||||
func (c *homelabServiceClient) ListSections(ctx context.Context, req *connect.Request[v1.ListSectionsRequest]) (*connect.Response[v1.ListSectionsResponse], error) {
|
func (c *homelabServiceClient) ListContent(ctx context.Context, req *connect.Request[v1.ListContentRequest]) (*connect.Response[v1.ListContentResponse], error) {
|
||||||
return c.listSections.CallUnary(ctx, req)
|
return c.listContent.CallUnary(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListDocs calls homelab.v1.HomelabService.ListDocs.
|
||||||
|
func (c *homelabServiceClient) ListDocs(ctx context.Context, req *connect.Request[v1.ListDocsRequest]) (*connect.Response[v1.ListDocsResponse], error) {
|
||||||
|
return c.listDocs.CallUnary(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDoc calls homelab.v1.HomelabService.GetDoc.
|
||||||
|
func (c *homelabServiceClient) GetDoc(ctx context.Context, req *connect.Request[v1.GetDocRequest]) (*connect.Response[v1.GetDocResponse], error) {
|
||||||
|
return c.getDoc.CallUnary(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveDoc calls homelab.v1.HomelabService.SaveDoc.
|
||||||
|
func (c *homelabServiceClient) SaveDoc(ctx context.Context, req *connect.Request[v1.SaveDocRequest]) (*connect.Response[v1.SaveDocResponse], error) {
|
||||||
|
return c.saveDoc.CallUnary(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunScript calls homelab.v1.HomelabService.RunScript.
|
// RunScript calls homelab.v1.HomelabService.RunScript.
|
||||||
@@ -91,7 +136,10 @@ func (c *homelabServiceClient) RunScript(ctx context.Context, req *connect.Reque
|
|||||||
|
|
||||||
// HomelabServiceHandler is an implementation of the homelab.v1.HomelabService service.
|
// HomelabServiceHandler is an implementation of the homelab.v1.HomelabService service.
|
||||||
type HomelabServiceHandler interface {
|
type HomelabServiceHandler interface {
|
||||||
ListSections(context.Context, *connect.Request[v1.ListSectionsRequest]) (*connect.Response[v1.ListSectionsResponse], error)
|
ListContent(context.Context, *connect.Request[v1.ListContentRequest]) (*connect.Response[v1.ListContentResponse], error)
|
||||||
|
ListDocs(context.Context, *connect.Request[v1.ListDocsRequest]) (*connect.Response[v1.ListDocsResponse], error)
|
||||||
|
GetDoc(context.Context, *connect.Request[v1.GetDocRequest]) (*connect.Response[v1.GetDocResponse], error)
|
||||||
|
SaveDoc(context.Context, *connect.Request[v1.SaveDocRequest]) (*connect.Response[v1.SaveDocResponse], error)
|
||||||
RunScript(context.Context, *connect.Request[v1.RunScriptRequest]) (*connect.Response[v1.RunScriptResponse], error)
|
RunScript(context.Context, *connect.Request[v1.RunScriptRequest]) (*connect.Response[v1.RunScriptResponse], error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,10 +150,28 @@ type HomelabServiceHandler interface {
|
|||||||
// and JSON codecs. They also support gzip compression.
|
// and JSON codecs. They also support gzip compression.
|
||||||
func NewHomelabServiceHandler(svc HomelabServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
|
func NewHomelabServiceHandler(svc HomelabServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
|
||||||
homelabServiceMethods := v1.File_homelab_v1_homelab_proto.Services().ByName("HomelabService").Methods()
|
homelabServiceMethods := v1.File_homelab_v1_homelab_proto.Services().ByName("HomelabService").Methods()
|
||||||
homelabServiceListSectionsHandler := connect.NewUnaryHandler(
|
homelabServiceListContentHandler := connect.NewUnaryHandler(
|
||||||
HomelabServiceListSectionsProcedure,
|
HomelabServiceListContentProcedure,
|
||||||
svc.ListSections,
|
svc.ListContent,
|
||||||
connect.WithSchema(homelabServiceMethods.ByName("ListSections")),
|
connect.WithSchema(homelabServiceMethods.ByName("ListContent")),
|
||||||
|
connect.WithHandlerOptions(opts...),
|
||||||
|
)
|
||||||
|
homelabServiceListDocsHandler := connect.NewUnaryHandler(
|
||||||
|
HomelabServiceListDocsProcedure,
|
||||||
|
svc.ListDocs,
|
||||||
|
connect.WithSchema(homelabServiceMethods.ByName("ListDocs")),
|
||||||
|
connect.WithHandlerOptions(opts...),
|
||||||
|
)
|
||||||
|
homelabServiceGetDocHandler := connect.NewUnaryHandler(
|
||||||
|
HomelabServiceGetDocProcedure,
|
||||||
|
svc.GetDoc,
|
||||||
|
connect.WithSchema(homelabServiceMethods.ByName("GetDoc")),
|
||||||
|
connect.WithHandlerOptions(opts...),
|
||||||
|
)
|
||||||
|
homelabServiceSaveDocHandler := connect.NewUnaryHandler(
|
||||||
|
HomelabServiceSaveDocProcedure,
|
||||||
|
svc.SaveDoc,
|
||||||
|
connect.WithSchema(homelabServiceMethods.ByName("SaveDoc")),
|
||||||
connect.WithHandlerOptions(opts...),
|
connect.WithHandlerOptions(opts...),
|
||||||
)
|
)
|
||||||
homelabServiceRunScriptHandler := connect.NewUnaryHandler(
|
homelabServiceRunScriptHandler := connect.NewUnaryHandler(
|
||||||
@@ -116,8 +182,14 @@ func NewHomelabServiceHandler(svc HomelabServiceHandler, opts ...connect.Handler
|
|||||||
)
|
)
|
||||||
return "/homelab.v1.HomelabService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return "/homelab.v1.HomelabService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.URL.Path {
|
switch r.URL.Path {
|
||||||
case HomelabServiceListSectionsProcedure:
|
case HomelabServiceListContentProcedure:
|
||||||
homelabServiceListSectionsHandler.ServeHTTP(w, r)
|
homelabServiceListContentHandler.ServeHTTP(w, r)
|
||||||
|
case HomelabServiceListDocsProcedure:
|
||||||
|
homelabServiceListDocsHandler.ServeHTTP(w, r)
|
||||||
|
case HomelabServiceGetDocProcedure:
|
||||||
|
homelabServiceGetDocHandler.ServeHTTP(w, r)
|
||||||
|
case HomelabServiceSaveDocProcedure:
|
||||||
|
homelabServiceSaveDocHandler.ServeHTTP(w, r)
|
||||||
case HomelabServiceRunScriptProcedure:
|
case HomelabServiceRunScriptProcedure:
|
||||||
homelabServiceRunScriptHandler.ServeHTTP(w, r)
|
homelabServiceRunScriptHandler.ServeHTTP(w, r)
|
||||||
default:
|
default:
|
||||||
@@ -129,8 +201,20 @@ func NewHomelabServiceHandler(svc HomelabServiceHandler, opts ...connect.Handler
|
|||||||
// UnimplementedHomelabServiceHandler returns CodeUnimplemented from all methods.
|
// UnimplementedHomelabServiceHandler returns CodeUnimplemented from all methods.
|
||||||
type UnimplementedHomelabServiceHandler struct{}
|
type UnimplementedHomelabServiceHandler struct{}
|
||||||
|
|
||||||
func (UnimplementedHomelabServiceHandler) ListSections(context.Context, *connect.Request[v1.ListSectionsRequest]) (*connect.Response[v1.ListSectionsResponse], error) {
|
func (UnimplementedHomelabServiceHandler) ListContent(context.Context, *connect.Request[v1.ListContentRequest]) (*connect.Response[v1.ListContentResponse], error) {
|
||||||
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("homelab.v1.HomelabService.ListSections is not implemented"))
|
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("homelab.v1.HomelabService.ListContent is not implemented"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedHomelabServiceHandler) ListDocs(context.Context, *connect.Request[v1.ListDocsRequest]) (*connect.Response[v1.ListDocsResponse], error) {
|
||||||
|
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("homelab.v1.HomelabService.ListDocs is not implemented"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedHomelabServiceHandler) GetDoc(context.Context, *connect.Request[v1.GetDocRequest]) (*connect.Response[v1.GetDocResponse], error) {
|
||||||
|
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("homelab.v1.HomelabService.GetDoc is not implemented"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedHomelabServiceHandler) SaveDoc(context.Context, *connect.Request[v1.SaveDocRequest]) (*connect.Response[v1.SaveDocResponse], error) {
|
||||||
|
return nil, connect.NewError(connect.CodeUnimplemented, errors.New("homelab.v1.HomelabService.SaveDoc is not implemented"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedHomelabServiceHandler) RunScript(context.Context, *connect.Request[v1.RunScriptRequest]) (*connect.Response[v1.RunScriptResponse], error) {
|
func (UnimplementedHomelabServiceHandler) RunScript(context.Context, *connect.Request[v1.RunScriptRequest]) (*connect.Response[v1.RunScriptResponse], error) {
|
||||||
|
|||||||
@@ -6,22 +6,55 @@ option go_package = "pwa-homelab-mon/gen/homelab/v1;homelabv1";
|
|||||||
|
|
||||||
message Item {
|
message Item {
|
||||||
string name = 1;
|
string name = 1;
|
||||||
string type = 2; // monitor | response
|
string data = 2; // monitor: text | percentage
|
||||||
string data = 3; // monitor: text | percentage
|
string value = 3; // monitor: last output
|
||||||
string value = 4; // monitor: last output
|
int32 args = 4; // exec: arg count
|
||||||
int32 args = 5; // response: arg count
|
repeated string arg_types = 5; // exec: text | number per arg
|
||||||
repeated string arg_types = 6; // response: text | number per arg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Section {
|
message Link {
|
||||||
string name = 1;
|
string name = 1;
|
||||||
string icon = 2;
|
string url = 2;
|
||||||
repeated Item items = 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message ListSectionsRequest {}
|
message ListContentRequest {}
|
||||||
message ListSectionsResponse {
|
message ListContentResponse {
|
||||||
repeated Section sections = 1;
|
repeated Link links = 1;
|
||||||
|
repeated Item monitors = 2;
|
||||||
|
repeated Item exec = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DocMeta {
|
||||||
|
string name = 1;
|
||||||
|
string hash = 2; // sha256 hex of content
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListDocsRequest {}
|
||||||
|
message ListDocsResponse {
|
||||||
|
repeated DocMeta docs = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetDocRequest {
|
||||||
|
string name = 1;
|
||||||
|
}
|
||||||
|
message GetDocResponse {
|
||||||
|
string content = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SaveDocRequest {
|
||||||
|
string name = 1;
|
||||||
|
string content = 2;
|
||||||
|
}
|
||||||
|
message SaveDocResponse {
|
||||||
|
string hash = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
service HomelabService {
|
||||||
|
rpc ListContent(ListContentRequest) returns (ListContentResponse);
|
||||||
|
rpc ListDocs(ListDocsRequest) returns (ListDocsResponse);
|
||||||
|
rpc GetDoc(GetDocRequest) returns (GetDocResponse);
|
||||||
|
rpc SaveDoc(SaveDocRequest) returns (SaveDocResponse);
|
||||||
|
rpc RunScript(RunScriptRequest) returns (RunScriptResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
message RunScriptRequest {
|
message RunScriptRequest {
|
||||||
@@ -31,8 +64,3 @@ message RunScriptRequest {
|
|||||||
message RunScriptResponse {
|
message RunScriptResponse {
|
||||||
string output = 1;
|
string output = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
service HomelabService {
|
|
||||||
rpc ListSections(ListSectionsRequest) returns (ListSectionsResponse);
|
|
||||||
rpc RunScript(RunScriptRequest) returns (RunScriptResponse);
|
|
||||||
}
|
|
||||||
|
|||||||
+115
-58
@@ -1,14 +1,15 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
//test
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -25,23 +26,22 @@ import (
|
|||||||
|
|
||||||
type Item struct {
|
type Item struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Type string `yaml:"type"` // monitor | response
|
|
||||||
Script string `yaml:"script"`
|
Script string `yaml:"script"`
|
||||||
Refresh int `yaml:"refresh"` // monitor: seconds
|
Refresh int `yaml:"refresh"` // monitor: seconds
|
||||||
Data string `yaml:"data"` // monitor: text | percentage
|
Data string `yaml:"data"` // monitor: text | percentage
|
||||||
Args int `yaml:"args"` // response: arg count
|
Args int `yaml:"args"` // exec: arg count
|
||||||
ArgTypes []string `yaml:"arg_types"` // response: text | number per arg
|
ArgTypes []string `yaml:"arg_types"` // exec: text | number per arg
|
||||||
}
|
}
|
||||||
|
|
||||||
// Section is a full view on the web page: a list of items plus sidebar metadata.
|
type Link struct {
|
||||||
type Section struct {
|
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Icon string `yaml:"icon"`
|
URL string `yaml:"url"`
|
||||||
Items []Item `yaml:"items"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Sections []Section `yaml:"sections"`
|
Links []Link `yaml:"links"`
|
||||||
|
Monitor []Item `yaml:"monitor"`
|
||||||
|
Exec []Item `yaml:"exec"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadConfig(path string) (*Config, error) {
|
func loadConfig(path string) (*Config, error) {
|
||||||
@@ -56,27 +56,21 @@ func loadConfig(path string) (*Config, error) {
|
|||||||
if err := dec.Decode(&cfg); err != nil {
|
if err := dec.Decode(&cfg); err != nil {
|
||||||
return nil, fmt.Errorf("parse %s: %w", path, err)
|
return nil, fmt.Errorf("parse %s: %w", path, err)
|
||||||
}
|
}
|
||||||
for _, sec := range cfg.Sections {
|
for _, it := range cfg.Monitor {
|
||||||
for _, it := range sec.Items {
|
|
||||||
switch it.Type {
|
|
||||||
case "monitor":
|
|
||||||
if it.Refresh <= 0 {
|
if it.Refresh <= 0 {
|
||||||
return nil, fmt.Errorf("item %q: refresh must be > 0", it.Name)
|
return nil, fmt.Errorf("monitor %q: refresh must be > 0", it.Name)
|
||||||
}
|
}
|
||||||
if it.Data != "text" && it.Data != "percentage" {
|
if it.Data != "text" && it.Data != "percentage" {
|
||||||
return nil, fmt.Errorf("item %q: data must be text|percentage", it.Name)
|
return nil, fmt.Errorf("monitor %q: data must be text|percentage", it.Name)
|
||||||
}
|
}
|
||||||
case "response":
|
}
|
||||||
|
for _, it := range cfg.Exec {
|
||||||
if len(it.ArgTypes) != it.Args {
|
if len(it.ArgTypes) != it.Args {
|
||||||
return nil, fmt.Errorf("item %q: arg_types must have args entries", it.Name)
|
return nil, fmt.Errorf("exec %q: arg_types must have args entries", it.Name)
|
||||||
}
|
}
|
||||||
for _, t := range it.ArgTypes {
|
for _, t := range it.ArgTypes {
|
||||||
if t != "text" && t != "number" {
|
if t != "text" && t != "number" {
|
||||||
return nil, fmt.Errorf("item %q: arg type must be text|number", it.Name)
|
return nil, fmt.Errorf("exec %q: arg type must be text|number", it.Name)
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("item %q: unknown type %q", it.Name, it.Type)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,8 +81,9 @@ func loadConfig(path string) (*Config, error) {
|
|||||||
|
|
||||||
type server struct {
|
type server struct {
|
||||||
cfg *Config
|
cfg *Config
|
||||||
scripts map[string]*Item // by item name, read-only after start
|
exec map[string]*Item // by item name, read-only after start
|
||||||
monVals sync.Map // item name -> string (last monitor output)
|
monVals sync.Map // monitor item name -> string (last output)
|
||||||
|
docsDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
const execTimeout = 10 * time.Second
|
const execTimeout = 10 * time.Second
|
||||||
@@ -104,12 +99,8 @@ func runScript(s *Item, args ...string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) startMonitors() {
|
func (s *server) startMonitors() {
|
||||||
for _, sec := range s.cfg.Sections {
|
for i := range s.cfg.Monitor {
|
||||||
for _, it := range sec.Items {
|
sc := &s.cfg.Monitor[i]
|
||||||
if it.Type != "monitor" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
sc := it
|
|
||||||
log.Printf("monitor started: %s (%s) every %ds", sc.Name, sc.Script, sc.Refresh)
|
log.Printf("monitor started: %s (%s) every %ds", sc.Name, sc.Script, sc.Refresh)
|
||||||
go func(sc *Item) {
|
go func(sc *Item) {
|
||||||
for {
|
for {
|
||||||
@@ -123,37 +114,98 @@ func (s *server) startMonitors() {
|
|||||||
s.monVals.Store(sc.Name, v)
|
s.monVals.Store(sc.Name, v)
|
||||||
time.Sleep(time.Duration(sc.Refresh) * time.Second)
|
time.Sleep(time.Duration(sc.Refresh) * time.Second)
|
||||||
}
|
}
|
||||||
}(&sc)
|
}(sc)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toProto(it *Item, val string) *homelabv1.Item {
|
// --- docs ---
|
||||||
p := &homelabv1.Item{Name: it.Name, Type: it.Type, Data: it.Data, Args: int32(it.Args), ArgTypes: it.ArgTypes}
|
|
||||||
if it.Type == "monitor" {
|
func docHash(b []byte) string {
|
||||||
p.Value = val
|
h := sha256.Sum256(b)
|
||||||
}
|
return hex.EncodeToString(h[:])
|
||||||
return p
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) ListSections(context.Context, *connect.Request[homelabv1.ListSectionsRequest]) (*connect.Response[homelabv1.ListSectionsResponse], error) {
|
// docPath resolves a doc name safely: plain file name only, no separators, always inside docsDir.
|
||||||
resp := &homelabv1.ListSectionsResponse{}
|
func (s *server) docPath(name string) (string, error) {
|
||||||
for _, sec := range s.cfg.Sections {
|
if filepath.Base(name) != name || filepath.Ext(name) != ".md" {
|
||||||
ps := &homelabv1.Section{Name: sec.Name, Icon: sec.Icon}
|
return "", fmt.Errorf("bad doc name %q", name)
|
||||||
for _, it := range sec.Items {
|
}
|
||||||
|
p := filepath.Join(s.docsDir, name)
|
||||||
|
if filepath.Dir(p) != s.docsDir {
|
||||||
|
return "", fmt.Errorf("bad doc name %q", name)
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) ListDocs(context.Context, *connect.Request[homelabv1.ListDocsRequest]) (*connect.Response[homelabv1.ListDocsResponse], error) {
|
||||||
|
resp := &homelabv1.ListDocsResponse{}
|
||||||
|
entries, err := os.ReadDir(s.docsDir)
|
||||||
|
if err != nil {
|
||||||
|
return connect.NewResponse(resp), nil // no docs dir = no docs
|
||||||
|
}
|
||||||
|
for _, e := range entries {
|
||||||
|
if e.IsDir() || !strings.HasSuffix(e.Name(), ".md") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b, err := os.ReadFile(filepath.Join(s.docsDir, e.Name()))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
resp.Docs = append(resp.Docs, &homelabv1.DocMeta{Name: e.Name(), Hash: docHash(b)})
|
||||||
|
}
|
||||||
|
return connect.NewResponse(resp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) GetDoc(_ context.Context, req *connect.Request[homelabv1.GetDocRequest]) (*connect.Response[homelabv1.GetDocResponse], error) {
|
||||||
|
p, err := s.docPath(req.Msg.GetName())
|
||||||
|
if err != nil {
|
||||||
|
return nil, connect.NewError(connect.CodeInvalidArgument, err)
|
||||||
|
}
|
||||||
|
b, err := os.ReadFile(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, connect.NewError(connect.CodeNotFound, err)
|
||||||
|
}
|
||||||
|
return connect.NewResponse(&homelabv1.GetDocResponse{Content: string(b)}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) SaveDoc(_ context.Context, req *connect.Request[homelabv1.SaveDocRequest]) (*connect.Response[homelabv1.SaveDocResponse], error) {
|
||||||
|
p, err := s.docPath(req.Msg.GetName())
|
||||||
|
if err != nil {
|
||||||
|
return nil, connect.NewError(connect.CodeInvalidArgument, err)
|
||||||
|
}
|
||||||
|
content := []byte(req.Msg.GetContent())
|
||||||
|
// ponytail: client authority — last write wins, no base-hash check; add optimistic locking if concurrent edits appear
|
||||||
|
if err := os.WriteFile(p, content, 0o644); err != nil {
|
||||||
|
return nil, connect.NewError(connect.CodeInternal, err)
|
||||||
|
}
|
||||||
|
log.Printf("doc saved: %s (%d bytes)", filepath.Base(p), len(content))
|
||||||
|
return connect.NewResponse(&homelabv1.SaveDocResponse{Hash: docHash(content)}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- content RPC ---
|
||||||
|
|
||||||
|
func (s *server) ListContent(context.Context, *connect.Request[homelabv1.ListContentRequest]) (*connect.Response[homelabv1.ListContentResponse], error) {
|
||||||
|
resp := &homelabv1.ListContentResponse{}
|
||||||
|
for _, l := range s.cfg.Links {
|
||||||
|
resp.Links = append(resp.Links, &homelabv1.Link{Name: l.Name, Url: l.URL})
|
||||||
|
}
|
||||||
|
for i := range s.cfg.Monitor {
|
||||||
|
it := &s.cfg.Monitor[i]
|
||||||
val, _ := s.monVals.Load(it.Name)
|
val, _ := s.monVals.Load(it.Name)
|
||||||
v, _ := val.(string)
|
v, _ := val.(string)
|
||||||
ps.Items = append(ps.Items, toProto(&it, v))
|
resp.Monitors = append(resp.Monitors, &homelabv1.Item{Name: it.Name, Data: it.Data, Value: v})
|
||||||
}
|
}
|
||||||
resp.Sections = append(resp.Sections, ps)
|
for i := range s.cfg.Exec {
|
||||||
|
it := &s.cfg.Exec[i]
|
||||||
|
resp.Exec = append(resp.Exec, &homelabv1.Item{Name: it.Name, Args: int32(it.Args), ArgTypes: it.ArgTypes})
|
||||||
}
|
}
|
||||||
return connect.NewResponse(resp), nil
|
return connect.NewResponse(resp), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) RunScript(_ context.Context, req *connect.Request[homelabv1.RunScriptRequest]) (*connect.Response[homelabv1.RunScriptResponse], error) {
|
func (s *server) RunScript(_ context.Context, req *connect.Request[homelabv1.RunScriptRequest]) (*connect.Response[homelabv1.RunScriptResponse], error) {
|
||||||
it, ok := s.scripts[req.Msg.GetName()]
|
it, ok := s.exec[req.Msg.GetName()]
|
||||||
if !ok || it.Type != "response" {
|
if !ok {
|
||||||
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("no response script %q", req.Msg.GetName()))
|
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("no exec item %q", req.Msg.GetName()))
|
||||||
}
|
}
|
||||||
if len(req.Msg.GetArgs()) != it.Args {
|
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())))
|
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("%q takes %d args, got %d", it.Name, it.Args, len(req.Msg.GetArgs())))
|
||||||
@@ -180,22 +232,26 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
svc := &server{cfg: cfg, scripts: map[string]*Item{}}
|
docsDir := "docs"
|
||||||
for _, sec := range cfg.Sections {
|
if err := os.MkdirAll(docsDir, 0o755); err != nil {
|
||||||
for i := range sec.Items {
|
fmt.Fprintln(os.Stderr, "docs:", err)
|
||||||
svc.scripts[sec.Items[i].Name] = &sec.Items[i]
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
svc := &server{cfg: cfg, exec: map[string]*Item{}, docsDir: docsDir}
|
||||||
|
for i := range cfg.Exec {
|
||||||
|
svc.exec[cfg.Exec[i].Name] = &cfg.Exec[i]
|
||||||
}
|
}
|
||||||
svc.startMonitors()
|
svc.startMonitors()
|
||||||
|
|
||||||
log.Printf("config %s: %d sections, %d items", cfgPath, len(cfg.Sections), len(svc.scripts))
|
log.Printf("config %s: %d links, %d monitors, %d exec", cfgPath, len(cfg.Links), len(cfg.Monitor), len(cfg.Exec))
|
||||||
|
|
||||||
e := echo.New()
|
e := echo.New()
|
||||||
e.HideBanner = true
|
e.HideBanner = true
|
||||||
// HTTP request log, except the ListSections heartbeat (every 3s per client — pure noise).
|
// HTTP request log, except the ListContent heartbeat (every 3s per client — pure noise).
|
||||||
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
|
e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
|
||||||
Skipper: func(c echo.Context) bool {
|
Skipper: func(c echo.Context) bool {
|
||||||
return c.Request().URL.Path == "/homelab.v1.HomelabService/ListSections"
|
return c.Request().URL.Path == "/homelab.v1.HomelabService/ListContent"
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@@ -204,10 +260,11 @@ func main() {
|
|||||||
mux.Handle(homelabv1connect.NewHomelabServiceHandler(svc))
|
mux.Handle(homelabv1connect.NewHomelabServiceHandler(svc))
|
||||||
e.Any("/homelab.v1.HomelabService/*", echo.WrapHandler(mux))
|
e.Any("/homelab.v1.HomelabService/*", echo.WrapHandler(mux))
|
||||||
|
|
||||||
// sw.js must never be cached, otherwise iOS serves a stale service worker.
|
// sw.js and index.html must never be cached: stale index = stale asset references = iOS never updates.
|
||||||
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(c echo.Context) error {
|
return func(c echo.Context) error {
|
||||||
if c.Request().URL.Path == "/sw.js" || c.Request().URL.Path == "/manifest.webmanifest" {
|
p := c.Request().URL.Path
|
||||||
|
if p == "/sw.js" || p == "/manifest.webmanifest" || p == "/index.html" || p == "/" {
|
||||||
c.Response().Header().Set("Cache-Control", "no-cache")
|
c.Response().Header().Set("Cache-Control", "no-cache")
|
||||||
}
|
}
|
||||||
return next(c)
|
return next(c)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"@tailwindcss/vite": "^4.3.3",
|
"@tailwindcss/vite": "^4.3.3",
|
||||||
"daisyui": "^5.7.22",
|
"daisyui": "^5.7.22",
|
||||||
"lucide-preact": "^1.35.0",
|
"lucide-preact": "^1.35.0",
|
||||||
|
"marked": "^18.0.11",
|
||||||
"preact": "^10.29.8",
|
"preact": "^10.29.8",
|
||||||
"tailwindcss": "^4.3.3"
|
"tailwindcss": "^4.3.3"
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+10
@@ -26,6 +26,9 @@ importers:
|
|||||||
lucide-preact:
|
lucide-preact:
|
||||||
specifier: ^1.35.0
|
specifier: ^1.35.0
|
||||||
version: 1.35.0(preact@10.29.8)
|
version: 1.35.0(preact@10.29.8)
|
||||||
|
marked:
|
||||||
|
specifier: ^18.0.11
|
||||||
|
version: 18.0.11
|
||||||
preact:
|
preact:
|
||||||
specifier: ^10.29.8
|
specifier: ^10.29.8
|
||||||
version: 10.29.8
|
version: 10.29.8
|
||||||
@@ -808,6 +811,11 @@ packages:
|
|||||||
magic-string@0.30.21:
|
magic-string@0.30.21:
|
||||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||||
|
|
||||||
|
marked@18.0.11:
|
||||||
|
resolution: {integrity: sha512-HnslJfsZkRPBDJRHvVtAaWlZHEpSu7u8LgQuJCELjRKuWR+hpq4A7sLq3p8HaI9ypVoXDXxV34CsQJEe1+J5Aw==}
|
||||||
|
engines: {node: '>= 20'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
ms@2.1.3:
|
ms@2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
@@ -1569,6 +1577,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.6.0
|
'@jridgewell/sourcemap-codec': 1.6.0
|
||||||
|
|
||||||
|
marked@18.0.11: {}
|
||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
|
|
||||||
nanoid@3.3.18: {}
|
nanoid@3.3.18: {}
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
/* Offline shell: navigations are network-first, static assets cache-first. */
|
/* Offline shell: navigations are network-first, static assets cache-first. */
|
||||||
const CACHE = "homelab-shell-v2";
|
const CACHE = "homelab-shell-v3";
|
||||||
|
|
||||||
self.addEventListener("install", (e) => {
|
self.addEventListener("install", (e) => {
|
||||||
e.waitUntil(caches.open(CACHE).then((c) => c.addAll(["/", "/index.html"])));
|
e.waitUntil(caches.open(CACHE).then((c) => c.addAll(["/", "/index.html"])));
|
||||||
|
|||||||
+93
-36
@@ -1,66 +1,123 @@
|
|||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
import { createClient } from "@connectrpc/connect";
|
import { createClient } from "@connectrpc/connect";
|
||||||
import { createConnectTransport } from "@connectrpc/connect-web";
|
import { createConnectTransport } from "@connectrpc/connect-web";
|
||||||
import { Gauge, FlaskConical } from "lucide-preact";
|
import { BookOpen, Gauge, Link as LinkIcon, Menu, Play } from "lucide-preact";
|
||||||
import type { Section } from "./gen/homelab/v1/homelab_pb";
|
import type { Item, Link } from "./gen/homelab/v1/homelab_pb";
|
||||||
import { HomelabService } from "./gen/homelab/v1/homelab_pb";
|
import { HomelabService } from "./gen/homelab/v1/homelab_pb";
|
||||||
import { ScriptCard } from "./components/ScriptCard";
|
|
||||||
import { MonitorCard } from "./components/MonitorCard";
|
import { MonitorCard } from "./components/MonitorCard";
|
||||||
|
import { ScriptCard } from "./components/ScriptCard";
|
||||||
|
import { DocsView, DocView, localDocs } from "./components/Docs";
|
||||||
|
import { syncDocs } from "./docsStore";
|
||||||
|
|
||||||
const client = createClient(HomelabService, createConnectTransport({ baseUrl: "" }));
|
const client = createClient(HomelabService, createConnectTransport({ baseUrl: "" }));
|
||||||
|
|
||||||
// Иконки секций: строка из конфига → компонент. Новый icon в yaml = добавить сюда.
|
const CATEGORIES = [
|
||||||
const ICONS: Record<string, preact.JSX.ElementType> = { gauge: Gauge, "flask-conical": FlaskConical };
|
{ id: "links", label: "Links", icon: LinkIcon },
|
||||||
|
{ id: "monitor", label: "Monitor", icon: Gauge },
|
||||||
|
{ id: "exec", label: "Exec", icon: Play },
|
||||||
|
{ id: "docs", label: "Docs", icon: BookOpen },
|
||||||
|
] as const;
|
||||||
|
|
||||||
function DynIcon({ name, size = 20 }: { name: string; size?: number }) {
|
type Category = (typeof CATEGORIES)[number]["id"];
|
||||||
const Cmp = ICONS[name] ?? Gauge;
|
|
||||||
return <Cmp size={size} />;
|
function LinkList({ links }: { links: Link[] }) {
|
||||||
|
if (links.length === 0) return <div class="alert mt-4">No links in config</div>;
|
||||||
|
return (
|
||||||
|
<div class="flex flex-col gap-2 mt-4">
|
||||||
|
{links.map((l) => (
|
||||||
|
<a href={l.url} target="_blank" rel="noreferrer" class="card bg-base-200 shadow">
|
||||||
|
<div class="card-body p-4 py-3 flex-row items-center gap-2">
|
||||||
|
<LinkIcon size={14} class="text-primary" />
|
||||||
|
<span class="flex-1 truncate">{l.name}</span>
|
||||||
|
<span class="text-xs text-base-content/40 truncate max-w-40">{l.url}</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const [sections, setSections] = useState<Section[]>([]);
|
const [content, setContent] = useState<{ links: Link[]; monitors: Item[]; exec: Item[] }>({
|
||||||
const [active, setActive] = useState(0);
|
links: [],
|
||||||
|
monitors: [],
|
||||||
|
exec: [],
|
||||||
|
});
|
||||||
|
const [category, setCategory] = useState<Category>("monitor");
|
||||||
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
|
const [docs, setDocs] = useState(localDocs());
|
||||||
|
const [openDoc, setOpenDoc] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const poll = () => client.listSections({}).then((r) => setSections(r.sections));
|
const poll = () =>
|
||||||
|
client
|
||||||
|
.listContent({})
|
||||||
|
.then((r) => setContent({ links: r.links, monitors: r.monitors, exec: r.exec }))
|
||||||
|
.catch(() => {}); // offline: keep last state
|
||||||
poll();
|
poll();
|
||||||
const id = setInterval(poll, 3000);
|
const id = setInterval(poll, 3000);
|
||||||
return () => clearInterval(id);
|
return () => clearInterval(id);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const sec = sections[active];
|
// Docs sync + offline dirty flush, every 5s.
|
||||||
|
useEffect(() => {
|
||||||
|
const tick = () => syncDocs().then(() => setDocs(localDocs())).catch(() => {});
|
||||||
|
tick();
|
||||||
|
const id = setInterval(tick, 5000);
|
||||||
|
return () => clearInterval(id);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const cat = CATEGORIES.find((c) => c.id === category)!;
|
||||||
|
const open = docs.find((d) => d.name === openDoc);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="flex min-h-screen">
|
<div class="drawer min-h-screen">
|
||||||
|
<input id="nav-drawer" type="checkbox" class="drawer-toggle" checked={drawerOpen} onChange={() => setDrawerOpen((v) => !v)} />
|
||||||
|
|
||||||
|
<div class="drawer-content flex flex-col">
|
||||||
|
{/* Top bar */}
|
||||||
|
<div class="navbar bg-base-200 px-2 pt-[env(safe-area-inset-top)]">
|
||||||
|
<label class="btn btn-square btn-ghost" onClick={() => setDrawerOpen(true)}>
|
||||||
|
<Menu size={22} />
|
||||||
|
</label>
|
||||||
|
<span class="text-lg font-bold mx-auto">{open && category === "docs" ? open.name.replace(/\.md$/, "") : cat.label}</span>
|
||||||
|
<span class="w-11" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="max-w-md w-full mx-auto px-4 pb-[env(safe-area-inset-bottom)]">
|
||||||
|
{category === "links" && <LinkList links={content.links} />}
|
||||||
|
{category === "monitor" && (
|
||||||
|
<div class="flex flex-col gap-3 mt-4">
|
||||||
|
{content.monitors.map((it) => <MonitorCard s={it} />)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{category === "exec" && content.exec.map((it) => <ScriptCard s={it} />)}
|
||||||
|
{category === "docs" &&
|
||||||
|
(open ? (
|
||||||
|
<DocView doc={open} onBack={() => setOpenDoc(null)} />
|
||||||
|
) : (
|
||||||
|
<DocsView docs={docs} onOpen={(d) => setOpenDoc(d.name)} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<nav class="bg-base-200 w-20 flex flex-col items-center gap-1 py-4 pt-[calc(env(safe-area-inset-top)+1rem)]">
|
<div class="drawer-side z-20">
|
||||||
{sections.map((s, i) => (
|
<label class="drawer-overlay" onClick={() => setDrawerOpen(false)} />
|
||||||
|
<nav class="bg-base-200 min-h-full w-56 p-3 pt-[calc(env(safe-area-inset-top)+1rem)] flex flex-col gap-1">
|
||||||
|
{CATEGORIES.map((c) => (
|
||||||
<button
|
<button
|
||||||
class={`btn btn-square btn-ghost ${i === active ? "btn-primary" : ""}`}
|
class={`btn btn-ghost justify-start ${c.id === category ? "btn-primary" : ""}`}
|
||||||
title={s.name}
|
onClick={() => {
|
||||||
onClick={() => setActive(i)}
|
setCategory(c.id);
|
||||||
|
setOpenDoc(null);
|
||||||
|
setDrawerOpen(false);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<DynIcon name={s.icon} />
|
<c.icon size={18} /> {c.label}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Active section view */}
|
|
||||||
<div class="flex-1 max-w-md mx-auto px-4 pt-[env(safe-area-inset-top)] pb-[env(safe-area-inset-bottom)]">
|
|
||||||
<div class="navbar px-0">
|
|
||||||
<div class="navbar-start" />
|
|
||||||
<div class="navbar-center">
|
|
||||||
<span class="text-lg font-bold">{sec?.name ?? "Homelab"}</span>
|
|
||||||
</div>
|
|
||||||
<div class="navbar-end" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{sec && (
|
|
||||||
<div class="flex flex-col gap-4 pb-6">
|
|
||||||
{sec.items.filter((it) => it.type === "monitor").map((it) => <MonitorCard s={it} />)}
|
|
||||||
{sec.items.filter((it) => it.type === "response").map((it) => <ScriptCard s={it} />)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { useState } from "preact/hooks";
|
||||||
|
import { marked } from "marked";
|
||||||
|
import { ArrowLeft, Pencil } from "lucide-preact";
|
||||||
|
import { editDoc, localDocs, type StoredDoc } from "../docsStore";
|
||||||
|
|
||||||
|
// ponytail: no sanitizer — docs come from our own server; add DOMPurify if foreign content ever lands here
|
||||||
|
const render = (md: string) => marked.parse(md, { async: false });
|
||||||
|
|
||||||
|
/** Просмотр одного дока: рендер сверху, textarea-редактор ниже. Правки — в localStorage (client authority). */
|
||||||
|
export function DocView({ doc, onBack }: { doc: StoredDoc; onBack: () => void }) {
|
||||||
|
const [content, setContent] = useState(doc.content);
|
||||||
|
const [dirty, setDirty] = useState(doc.dirty);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button class="btn btn-sm btn-ghost" onClick={onBack}>
|
||||||
|
<ArrowLeft size={16} /> Docs
|
||||||
|
</button>
|
||||||
|
<span class="font-semibold flex-1 truncate">{doc.name}</span>
|
||||||
|
{dirty && <span class="badge badge-warning badge-sm">изменён</span>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card bg-base-200 shadow">
|
||||||
|
<div
|
||||||
|
class="prose prose-sm p-4 max-w-none break-words"
|
||||||
|
dangerouslySetInnerHTML={{ __html: render(content) }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card bg-base-200 shadow">
|
||||||
|
<div class="card-body p-4 gap-2">
|
||||||
|
<h2 class="card-title text-sm font-medium text-base-content/60">
|
||||||
|
<Pencil size={14} /> Edit
|
||||||
|
</h2>
|
||||||
|
<textarea
|
||||||
|
class="textarea textarea-bordered w-full font-mono text-sm h-64"
|
||||||
|
value={content}
|
||||||
|
onInput={(e) => {
|
||||||
|
const v = (e.target as HTMLTextAreaElement).value;
|
||||||
|
setContent(v);
|
||||||
|
editDoc(doc.name, v);
|
||||||
|
setDirty(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Список доков из localStorage (offline-first), обновляется через App-поллинг. */
|
||||||
|
export function DocsView({ docs, onOpen }: { docs: StoredDoc[]; onOpen: (d: StoredDoc) => void }) {
|
||||||
|
if (docs.length === 0) {
|
||||||
|
return <div class="alert mt-4">No docs yet (offline without cache, or empty docs dir)</div>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div class="flex flex-col gap-2 mt-4">
|
||||||
|
{docs.map((d) => (
|
||||||
|
<button class="card bg-base-200 shadow text-left" onClick={() => onOpen(d)}>
|
||||||
|
<div class="card-body p-4 py-3 flex-row items-center gap-2">
|
||||||
|
<span class="flex-1 truncate">{d.name.replace(/\.md$/, "")}</span>
|
||||||
|
{d.dirty && <span class="badge badge-warning badge-sm">*</span>}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { localDocs };
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { createClient } from "@connectrpc/connect";
|
||||||
|
import { createConnectTransport } from "@connectrpc/connect-web";
|
||||||
|
import { HomelabService } from "./gen/homelab/v1/homelab_pb";
|
||||||
|
|
||||||
|
const client = createClient(HomelabService, createConnectTransport({ baseUrl: "" }));
|
||||||
|
|
||||||
|
// localStorage: hub:doc:<name> = {hash, content}, hub:doc-dirty:<name> = "1"
|
||||||
|
|
||||||
|
export type StoredDoc = { name: string; hash: string; content: string; dirty: boolean };
|
||||||
|
|
||||||
|
const key = (name: string) => `hub:doc:${name}`;
|
||||||
|
const dirtyKey = (name: string) => `hub:doc-dirty:${name}`;
|
||||||
|
|
||||||
|
function get(name: string): StoredDoc | null {
|
||||||
|
const raw = localStorage.getItem(key(name));
|
||||||
|
if (!raw) return null;
|
||||||
|
const { hash, content } = JSON.parse(raw);
|
||||||
|
return { name, hash, content, dirty: localStorage.getItem(dirtyKey(name)) === "1" };
|
||||||
|
}
|
||||||
|
|
||||||
|
function put(name: string, hash: string, content: string, dirty: boolean) {
|
||||||
|
localStorage.setItem(key(name), JSON.stringify({ hash, content }));
|
||||||
|
if (dirty) localStorage.setItem(dirtyKey(name), "1");
|
||||||
|
else localStorage.removeItem(dirtyKey(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** All locally known docs (the offline source of truth). */
|
||||||
|
export function localDocs(): StoredDoc[] {
|
||||||
|
const out: StoredDoc[] = [];
|
||||||
|
for (let i = 0; i < localStorage.length; i++) {
|
||||||
|
const k = localStorage.key(i)!;
|
||||||
|
if (k.startsWith("hub:doc:")) {
|
||||||
|
const d = get(k.slice(8));
|
||||||
|
if (d) out.push(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function editDoc(name: string, content: string) {
|
||||||
|
const d = get(name);
|
||||||
|
if (d) put(name, d.hash, content, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function syncDocs(): Promise<void> {
|
||||||
|
// Pull: download new/changed docs (unless we have local edits — client authority).
|
||||||
|
const metas = await client.listDocs({});
|
||||||
|
for (const m of metas.docs) {
|
||||||
|
const local = get(m.name);
|
||||||
|
if (local && !local.dirty && local.hash !== m.hash) {
|
||||||
|
const { content } = await client.getDoc({ name: m.name });
|
||||||
|
put(m.name, m.hash, content, false);
|
||||||
|
} else if (!local) {
|
||||||
|
const { content } = await client.getDoc({ name: m.name });
|
||||||
|
put(m.name, m.hash, content, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Push: flush dirty docs, every tick until server accepts.
|
||||||
|
for (const d of localDocs().filter((d) => d.dirty)) {
|
||||||
|
try {
|
||||||
|
const { hash } = await client.saveDoc({ name: d.name, content: d.content });
|
||||||
|
put(d.name, hash, d.content, false);
|
||||||
|
} catch {
|
||||||
|
// offline or server error — stays dirty, retried next tick
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf";
|
|||||||
* Describes the file homelab/v1/homelab.proto.
|
* Describes the file homelab/v1/homelab.proto.
|
||||||
*/
|
*/
|
||||||
export const file_homelab_v1_homelab: GenFile = /*@__PURE__*/
|
export const file_homelab_v1_homelab: GenFile = /*@__PURE__*/
|
||||||
fileDesc("Chhob21lbGFiL3YxL2hvbWVsYWIucHJvdG8SCmhvbWVsYWIudjEiYAoESXRlbRIMCgRuYW1lGAEgASgJEgwKBHR5cGUYAiABKAkSDAoEZGF0YRgDIAEoCRINCgV2YWx1ZRgEIAEoCRIMCgRhcmdzGAUgASgFEhEKCWFyZ190eXBlcxgGIAMoCSJGCgdTZWN0aW9uEgwKBG5hbWUYASABKAkSDAoEaWNvbhgCIAEoCRIfCgVpdGVtcxgDIAMoCzIQLmhvbWVsYWIudjEuSXRlbSIVChNMaXN0U2VjdGlvbnNSZXF1ZXN0Ij0KFExpc3RTZWN0aW9uc1Jlc3BvbnNlEiUKCHNlY3Rpb25zGAEgAygLMhMuaG9tZWxhYi52MS5TZWN0aW9uIi4KEFJ1blNjcmlwdFJlcXVlc3QSDAoEbmFtZRgBIAEoCRIMCgRhcmdzGAIgAygJIiMKEVJ1blNjcmlwdFJlc3BvbnNlEg4KBm91dHB1dBgBIAEoCTKtAQoOSG9tZWxhYlNlcnZpY2USUQoMTGlzdFNlY3Rpb25zEh8uaG9tZWxhYi52MS5MaXN0U2VjdGlvbnNSZXF1ZXN0GiAuaG9tZWxhYi52MS5MaXN0U2VjdGlvbnNSZXNwb25zZRJICglSdW5TY3JpcHQSHC5ob21lbGFiLnYxLlJ1blNjcmlwdFJlcXVlc3QaHS5ob21lbGFiLnYxLlJ1blNjcmlwdFJlc3BvbnNlQipaKHB3YS1ob21lbGFiLW1vbi9nZW4vaG9tZWxhYi92MTtob21lbGFidjFiBnByb3RvMw");
|
fileDesc("Chhob21lbGFiL3YxL2hvbWVsYWIucHJvdG8SCmhvbWVsYWIudjEiUgoESXRlbRIMCgRuYW1lGAEgASgJEgwKBGRhdGEYAiABKAkSDQoFdmFsdWUYAyABKAkSDAoEYXJncxgEIAEoBRIRCglhcmdfdHlwZXMYBSADKAkiIQoETGluaxIMCgRuYW1lGAEgASgJEgsKA3VybBgCIAEoCSIUChJMaXN0Q29udGVudFJlcXVlc3QiegoTTGlzdENvbnRlbnRSZXNwb25zZRIfCgVsaW5rcxgBIAMoCzIQLmhvbWVsYWIudjEuTGluaxIiCghtb25pdG9ycxgCIAMoCzIQLmhvbWVsYWIudjEuSXRlbRIeCgRleGVjGAMgAygLMhAuaG9tZWxhYi52MS5JdGVtIiUKB0RvY01ldGESDAoEbmFtZRgBIAEoCRIMCgRoYXNoGAIgASgJIhEKD0xpc3REb2NzUmVxdWVzdCI1ChBMaXN0RG9jc1Jlc3BvbnNlEiEKBGRvY3MYASADKAsyEy5ob21lbGFiLnYxLkRvY01ldGEiHQoNR2V0RG9jUmVxdWVzdBIMCgRuYW1lGAEgASgJIiEKDkdldERvY1Jlc3BvbnNlEg8KB2NvbnRlbnQYASABKAkiLwoOU2F2ZURvY1JlcXVlc3QSDAoEbmFtZRgBIAEoCRIPCgdjb250ZW50GAIgASgJIh8KD1NhdmVEb2NSZXNwb25zZRIMCgRoYXNoGAEgASgJIi4KEFJ1blNjcmlwdFJlcXVlc3QSDAoEbmFtZRgBIAEoCRIMCgRhcmdzGAIgAygJIiMKEVJ1blNjcmlwdFJlc3BvbnNlEg4KBm91dHB1dBgBIAEoCTL2AgoOSG9tZWxhYlNlcnZpY2USTgoLTGlzdENvbnRlbnQSHi5ob21lbGFiLnYxLkxpc3RDb250ZW50UmVxdWVzdBofLmhvbWVsYWIudjEuTGlzdENvbnRlbnRSZXNwb25zZRJFCghMaXN0RG9jcxIbLmhvbWVsYWIudjEuTGlzdERvY3NSZXF1ZXN0GhwuaG9tZWxhYi52MS5MaXN0RG9jc1Jlc3BvbnNlEj8KBkdldERvYxIZLmhvbWVsYWIudjEuR2V0RG9jUmVxdWVzdBoaLmhvbWVsYWIudjEuR2V0RG9jUmVzcG9uc2USQgoHU2F2ZURvYxIaLmhvbWVsYWIudjEuU2F2ZURvY1JlcXVlc3QaGy5ob21lbGFiLnYxLlNhdmVEb2NSZXNwb25zZRJICglSdW5TY3JpcHQSHC5ob21lbGFiLnYxLlJ1blNjcmlwdFJlcXVlc3QaHS5ob21lbGFiLnYxLlJ1blNjcmlwdFJlc3BvbnNlQipaKHB3YS1ob21lbGFiLW1vbi9nZW4vaG9tZWxhYi92MTtob21lbGFidjFiBnByb3RvMw");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @generated from message homelab.v1.Item
|
* @generated from message homelab.v1.Item
|
||||||
@@ -21,38 +21,31 @@ export type Item = Message<"homelab.v1.Item"> & {
|
|||||||
*/
|
*/
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* monitor | response
|
|
||||||
*
|
|
||||||
* @generated from field: string type = 2;
|
|
||||||
*/
|
|
||||||
type: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* monitor: text | percentage
|
* monitor: text | percentage
|
||||||
*
|
*
|
||||||
* @generated from field: string data = 3;
|
* @generated from field: string data = 2;
|
||||||
*/
|
*/
|
||||||
data: string;
|
data: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* monitor: last output
|
* monitor: last output
|
||||||
*
|
*
|
||||||
* @generated from field: string value = 4;
|
* @generated from field: string value = 3;
|
||||||
*/
|
*/
|
||||||
value: string;
|
value: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* response: arg count
|
* exec: arg count
|
||||||
*
|
*
|
||||||
* @generated from field: int32 args = 5;
|
* @generated from field: int32 args = 4;
|
||||||
*/
|
*/
|
||||||
args: number;
|
args: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* response: text | number per arg
|
* exec: text | number per arg
|
||||||
*
|
*
|
||||||
* @generated from field: repeated string arg_types = 6;
|
* @generated from field: repeated string arg_types = 5;
|
||||||
*/
|
*/
|
||||||
argTypes: string[];
|
argTypes: string[];
|
||||||
};
|
};
|
||||||
@@ -65,62 +58,194 @@ export const ItemSchema: GenMessage<Item> = /*@__PURE__*/
|
|||||||
messageDesc(file_homelab_v1_homelab, 0);
|
messageDesc(file_homelab_v1_homelab, 0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @generated from message homelab.v1.Section
|
* @generated from message homelab.v1.Link
|
||||||
*/
|
*/
|
||||||
export type Section = Message<"homelab.v1.Section"> & {
|
export type Link = Message<"homelab.v1.Link"> & {
|
||||||
/**
|
/**
|
||||||
* @generated from field: string name = 1;
|
* @generated from field: string name = 1;
|
||||||
*/
|
*/
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @generated from field: string icon = 2;
|
* @generated from field: string url = 2;
|
||||||
*/
|
*/
|
||||||
icon: string;
|
url: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* @generated from field: repeated homelab.v1.Item items = 3;
|
|
||||||
*/
|
|
||||||
items: Item[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes the message homelab.v1.Section.
|
* Describes the message homelab.v1.Link.
|
||||||
* Use `create(SectionSchema)` to create a new message.
|
* Use `create(LinkSchema)` to create a new message.
|
||||||
*/
|
*/
|
||||||
export const SectionSchema: GenMessage<Section> = /*@__PURE__*/
|
export const LinkSchema: GenMessage<Link> = /*@__PURE__*/
|
||||||
messageDesc(file_homelab_v1_homelab, 1);
|
messageDesc(file_homelab_v1_homelab, 1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @generated from message homelab.v1.ListSectionsRequest
|
* @generated from message homelab.v1.ListContentRequest
|
||||||
*/
|
*/
|
||||||
export type ListSectionsRequest = Message<"homelab.v1.ListSectionsRequest"> & {
|
export type ListContentRequest = Message<"homelab.v1.ListContentRequest"> & {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes the message homelab.v1.ListSectionsRequest.
|
* Describes the message homelab.v1.ListContentRequest.
|
||||||
* Use `create(ListSectionsRequestSchema)` to create a new message.
|
* Use `create(ListContentRequestSchema)` to create a new message.
|
||||||
*/
|
*/
|
||||||
export const ListSectionsRequestSchema: GenMessage<ListSectionsRequest> = /*@__PURE__*/
|
export const ListContentRequestSchema: GenMessage<ListContentRequest> = /*@__PURE__*/
|
||||||
messageDesc(file_homelab_v1_homelab, 2);
|
messageDesc(file_homelab_v1_homelab, 2);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @generated from message homelab.v1.ListSectionsResponse
|
* @generated from message homelab.v1.ListContentResponse
|
||||||
*/
|
*/
|
||||||
export type ListSectionsResponse = Message<"homelab.v1.ListSectionsResponse"> & {
|
export type ListContentResponse = Message<"homelab.v1.ListContentResponse"> & {
|
||||||
/**
|
/**
|
||||||
* @generated from field: repeated homelab.v1.Section sections = 1;
|
* @generated from field: repeated homelab.v1.Link links = 1;
|
||||||
*/
|
*/
|
||||||
sections: Section[];
|
links: Link[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated homelab.v1.Item monitors = 2;
|
||||||
|
*/
|
||||||
|
monitors: Item[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated homelab.v1.Item exec = 3;
|
||||||
|
*/
|
||||||
|
exec: Item[];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes the message homelab.v1.ListSectionsResponse.
|
* Describes the message homelab.v1.ListContentResponse.
|
||||||
* Use `create(ListSectionsResponseSchema)` to create a new message.
|
* Use `create(ListContentResponseSchema)` to create a new message.
|
||||||
*/
|
*/
|
||||||
export const ListSectionsResponseSchema: GenMessage<ListSectionsResponse> = /*@__PURE__*/
|
export const ListContentResponseSchema: GenMessage<ListContentResponse> = /*@__PURE__*/
|
||||||
messageDesc(file_homelab_v1_homelab, 3);
|
messageDesc(file_homelab_v1_homelab, 3);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message homelab.v1.DocMeta
|
||||||
|
*/
|
||||||
|
export type DocMeta = Message<"homelab.v1.DocMeta"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string name = 1;
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sha256 hex of content
|
||||||
|
*
|
||||||
|
* @generated from field: string hash = 2;
|
||||||
|
*/
|
||||||
|
hash: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message homelab.v1.DocMeta.
|
||||||
|
* Use `create(DocMetaSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const DocMetaSchema: GenMessage<DocMeta> = /*@__PURE__*/
|
||||||
|
messageDesc(file_homelab_v1_homelab, 4);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message homelab.v1.ListDocsRequest
|
||||||
|
*/
|
||||||
|
export type ListDocsRequest = Message<"homelab.v1.ListDocsRequest"> & {
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message homelab.v1.ListDocsRequest.
|
||||||
|
* Use `create(ListDocsRequestSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const ListDocsRequestSchema: GenMessage<ListDocsRequest> = /*@__PURE__*/
|
||||||
|
messageDesc(file_homelab_v1_homelab, 5);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message homelab.v1.ListDocsResponse
|
||||||
|
*/
|
||||||
|
export type ListDocsResponse = Message<"homelab.v1.ListDocsResponse"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated homelab.v1.DocMeta docs = 1;
|
||||||
|
*/
|
||||||
|
docs: DocMeta[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message homelab.v1.ListDocsResponse.
|
||||||
|
* Use `create(ListDocsResponseSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const ListDocsResponseSchema: GenMessage<ListDocsResponse> = /*@__PURE__*/
|
||||||
|
messageDesc(file_homelab_v1_homelab, 6);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message homelab.v1.GetDocRequest
|
||||||
|
*/
|
||||||
|
export type GetDocRequest = Message<"homelab.v1.GetDocRequest"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string name = 1;
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message homelab.v1.GetDocRequest.
|
||||||
|
* Use `create(GetDocRequestSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const GetDocRequestSchema: GenMessage<GetDocRequest> = /*@__PURE__*/
|
||||||
|
messageDesc(file_homelab_v1_homelab, 7);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message homelab.v1.GetDocResponse
|
||||||
|
*/
|
||||||
|
export type GetDocResponse = Message<"homelab.v1.GetDocResponse"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string content = 1;
|
||||||
|
*/
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message homelab.v1.GetDocResponse.
|
||||||
|
* Use `create(GetDocResponseSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const GetDocResponseSchema: GenMessage<GetDocResponse> = /*@__PURE__*/
|
||||||
|
messageDesc(file_homelab_v1_homelab, 8);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message homelab.v1.SaveDocRequest
|
||||||
|
*/
|
||||||
|
export type SaveDocRequest = Message<"homelab.v1.SaveDocRequest"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string name = 1;
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string content = 2;
|
||||||
|
*/
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message homelab.v1.SaveDocRequest.
|
||||||
|
* Use `create(SaveDocRequestSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const SaveDocRequestSchema: GenMessage<SaveDocRequest> = /*@__PURE__*/
|
||||||
|
messageDesc(file_homelab_v1_homelab, 9);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message homelab.v1.SaveDocResponse
|
||||||
|
*/
|
||||||
|
export type SaveDocResponse = Message<"homelab.v1.SaveDocResponse"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string hash = 1;
|
||||||
|
*/
|
||||||
|
hash: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message homelab.v1.SaveDocResponse.
|
||||||
|
* Use `create(SaveDocResponseSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const SaveDocResponseSchema: GenMessage<SaveDocResponse> = /*@__PURE__*/
|
||||||
|
messageDesc(file_homelab_v1_homelab, 10);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @generated from message homelab.v1.RunScriptRequest
|
* @generated from message homelab.v1.RunScriptRequest
|
||||||
*/
|
*/
|
||||||
@@ -141,7 +266,7 @@ export type RunScriptRequest = Message<"homelab.v1.RunScriptRequest"> & {
|
|||||||
* Use `create(RunScriptRequestSchema)` to create a new message.
|
* Use `create(RunScriptRequestSchema)` to create a new message.
|
||||||
*/
|
*/
|
||||||
export const RunScriptRequestSchema: GenMessage<RunScriptRequest> = /*@__PURE__*/
|
export const RunScriptRequestSchema: GenMessage<RunScriptRequest> = /*@__PURE__*/
|
||||||
messageDesc(file_homelab_v1_homelab, 4);
|
messageDesc(file_homelab_v1_homelab, 11);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @generated from message homelab.v1.RunScriptResponse
|
* @generated from message homelab.v1.RunScriptResponse
|
||||||
@@ -158,19 +283,43 @@ export type RunScriptResponse = Message<"homelab.v1.RunScriptResponse"> & {
|
|||||||
* Use `create(RunScriptResponseSchema)` to create a new message.
|
* Use `create(RunScriptResponseSchema)` to create a new message.
|
||||||
*/
|
*/
|
||||||
export const RunScriptResponseSchema: GenMessage<RunScriptResponse> = /*@__PURE__*/
|
export const RunScriptResponseSchema: GenMessage<RunScriptResponse> = /*@__PURE__*/
|
||||||
messageDesc(file_homelab_v1_homelab, 5);
|
messageDesc(file_homelab_v1_homelab, 12);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @generated from service homelab.v1.HomelabService
|
* @generated from service homelab.v1.HomelabService
|
||||||
*/
|
*/
|
||||||
export const HomelabService: GenService<{
|
export const HomelabService: GenService<{
|
||||||
/**
|
/**
|
||||||
* @generated from rpc homelab.v1.HomelabService.ListSections
|
* @generated from rpc homelab.v1.HomelabService.ListContent
|
||||||
*/
|
*/
|
||||||
listSections: {
|
listContent: {
|
||||||
methodKind: "unary";
|
methodKind: "unary";
|
||||||
input: typeof ListSectionsRequestSchema;
|
input: typeof ListContentRequestSchema;
|
||||||
output: typeof ListSectionsResponseSchema;
|
output: typeof ListContentResponseSchema;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @generated from rpc homelab.v1.HomelabService.ListDocs
|
||||||
|
*/
|
||||||
|
listDocs: {
|
||||||
|
methodKind: "unary";
|
||||||
|
input: typeof ListDocsRequestSchema;
|
||||||
|
output: typeof ListDocsResponseSchema;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @generated from rpc homelab.v1.HomelabService.GetDoc
|
||||||
|
*/
|
||||||
|
getDoc: {
|
||||||
|
methodKind: "unary";
|
||||||
|
input: typeof GetDocRequestSchema;
|
||||||
|
output: typeof GetDocResponseSchema;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @generated from rpc homelab.v1.HomelabService.SaveDoc
|
||||||
|
*/
|
||||||
|
saveDoc: {
|
||||||
|
methodKind: "unary";
|
||||||
|
input: typeof SaveDocRequestSchema;
|
||||||
|
output: typeof SaveDocResponseSchema;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @generated from rpc homelab.v1.HomelabService.RunScript
|
* @generated from rpc homelab.v1.HomelabService.RunScript
|
||||||
|
|||||||
Reference in New Issue
Block a user