commit 2
This commit is contained in:
+45
-63
@@ -1,85 +1,67 @@
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
import { createClient } from "@connectrpc/connect";
|
||||
import { createConnectTransport } from "@connectrpc/connect-web";
|
||||
import { Dices, Hash, Send } from "lucide-preact";
|
||||
import { Gauge, FlaskConical } from "lucide-preact";
|
||||
import type { Section } from "./gen/homelab/v1/homelab_pb";
|
||||
import { HomelabService } from "./gen/homelab/v1/homelab_pb";
|
||||
import { ScriptCard } from "./components/ScriptCard";
|
||||
import { MonitorCard } from "./components/MonitorCard";
|
||||
|
||||
const client = createClient(HomelabService, createConnectTransport({ baseUrl: "" }));
|
||||
|
||||
// Иконки секций: строка из конфига → компонент. Новый icon в yaml = добавить сюда.
|
||||
const ICONS: Record<string, preact.JSX.ElementType> = { gauge: Gauge, "flask-conical": FlaskConical };
|
||||
|
||||
function DynIcon({ name, size = 20 }: { name: string; size?: number }) {
|
||||
const Cmp = ICONS[name] ?? Gauge;
|
||||
return <Cmp size={size} />;
|
||||
}
|
||||
|
||||
export function App() {
|
||||
const [lucky, setLucky] = useState<number | null>(null);
|
||||
const [count, setCount] = useState(0);
|
||||
const [name, setName] = useState("");
|
||||
const [greeting, setGreeting] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [sections, setSections] = useState<Section[]>([]);
|
||||
const [active, setActive] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
client.getLuckyNumber({}).then((r) => setLucky(r.number));
|
||||
const poll = () => client.listSections({}).then((r) => setSections(r.sections));
|
||||
poll();
|
||||
const id = setInterval(poll, 3000);
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
const run = async (fn: () => Promise<unknown>) => {
|
||||
try {
|
||||
await fn();
|
||||
setError("");
|
||||
} catch (e) {
|
||||
setError(String(e));
|
||||
}
|
||||
};
|
||||
const sec = sections[active];
|
||||
|
||||
return (
|
||||
<div class="mx-auto max-w-md 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">Homelab</span>
|
||||
</div>
|
||||
<div class="navbar-end" />
|
||||
</div>
|
||||
<div class="flex min-h-screen">
|
||||
{/* 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)]">
|
||||
{sections.map((s, i) => (
|
||||
<button
|
||||
class={`btn btn-square btn-ghost ${i === active ? "btn-primary" : ""}`}
|
||||
title={s.name}
|
||||
onClick={() => setActive(i)}
|
||||
>
|
||||
<DynIcon name={s.icon} />
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{error && <div role="alert" class="alert alert-error text-sm">{error}</div>}
|
||||
|
||||
<div class="stats w-full shadow bg-base-200 mt-4">
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-primary"><Dices size={28} /></div>
|
||||
<div class="stat-title">Lucky number</div>
|
||||
<div class="stat-value text-primary">{lucky ?? "…"}</div>
|
||||
<div class="stat-desc">set at server start</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-figure text-secondary"><Hash size={28} /></div>
|
||||
<div class="stat-title">Counter</div>
|
||||
<div class="stat-value text-secondary">{count}</div>
|
||||
<div class="stat-actions">
|
||||
<button class="btn btn-sm btn-secondary" onClick={() => run(async () => setCount((await client.incrementCounter({})).value))}>
|
||||
+1
|
||||
</button>
|
||||
{/* 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>
|
||||
</div>
|
||||
|
||||
<form
|
||||
class="card bg-base-200 shadow mt-4"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
run(async () => setGreeting((await client.sayHello({ name })).greeting));
|
||||
}}
|
||||
>
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-base">Hello</h2>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
class="input input-bordered flex-1"
|
||||
value={name}
|
||||
onInput={(e) => setName((e.target as HTMLInputElement).value)}
|
||||
placeholder="NAME"
|
||||
/>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<Send size={16} /> Send
|
||||
</button>
|
||||
{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>
|
||||
{greeting && <p class="text-xl font-semibold">{greeting}</p>}
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { Item } from "../gen/homelab/v1/homelab_pb";
|
||||
|
||||
/** Карточка монитора: percentage → radial-progress, иначе текст. */
|
||||
export function MonitorCard({ s }: { s: Item }) {
|
||||
const pct = Number.parseInt(s.value, 10);
|
||||
const isPct = s.data === "percentage" && !Number.isNaN(pct);
|
||||
return (
|
||||
<div class="card bg-base-200 shadow">
|
||||
<div class="card-body p-4">
|
||||
<h2 class="card-title text-sm font-medium text-base-content/60">{s.name}</h2>
|
||||
{isPct ? (
|
||||
<div
|
||||
class="radial-progress text-primary"
|
||||
style={{ "--value": pct } as preact.JSX.CSSProperties}
|
||||
role="progressbar"
|
||||
>
|
||||
{pct}%
|
||||
</div>
|
||||
) : (
|
||||
<p class="font-mono text-lg break-all">{s.value || "…"}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { useState } from "preact/hooks";
|
||||
import { createClient } from "@connectrpc/connect";
|
||||
import { createConnectTransport } from "@connectrpc/connect-web";
|
||||
import { X, Play } from "lucide-preact";
|
||||
import type { Item } from "../gen/homelab/v1/homelab_pb";
|
||||
import { HomelabService } from "../gen/homelab/v1/homelab_pb";
|
||||
|
||||
const client = createClient(HomelabService, createConnectTransport({ baseUrl: "" }));
|
||||
|
||||
/** Карточка response-скрипта: форма по количеству аргументов; результат — в модалке. */
|
||||
export function ScriptCard({ s }: { s: Item }) {
|
||||
const [args, setArgs] = useState<string[]>(Array(s.args).fill(""));
|
||||
const [output, setOutput] = useState<string | null>(null);
|
||||
const [error, setError] = useState("");
|
||||
const [running, setRunning] = useState(false);
|
||||
|
||||
const run = async () => {
|
||||
setRunning(true);
|
||||
try {
|
||||
setOutput((await client.runScript({ name: s.name, args })).output);
|
||||
setError("");
|
||||
} catch (e) {
|
||||
setError(String(e));
|
||||
} finally {
|
||||
setRunning(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="card bg-base-200 shadow">
|
||||
<div class="card-body p-4">
|
||||
<h2 class="card-title text-sm font-medium text-base-content/60">
|
||||
<Play size={14} /> {s.name}
|
||||
</h2>
|
||||
{error && <div role="alert" class="alert alert-error text-sm">{error}</div>}
|
||||
<div class="flex flex-wrap gap-2 items-center">
|
||||
{s.argTypes.map((t, i) => (
|
||||
<input
|
||||
class="input input-bordered w-full sm:w-32"
|
||||
type={t === "number" ? "number" : "text"}
|
||||
placeholder={t}
|
||||
value={args[i]}
|
||||
onInput={(e) =>
|
||||
setArgs((a) => {
|
||||
const next = [...a];
|
||||
next[i] = (e.target as HTMLInputElement).value;
|
||||
return next;
|
||||
})
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<button class="btn btn-primary btn-sm" disabled={running} onClick={run}>
|
||||
<Play size={14} /> Run
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{output != null && (
|
||||
<div class="modal modal-open">
|
||||
<div class="modal-box">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onClick={() => setOutput(null)}>
|
||||
<X size={16} />
|
||||
</button>
|
||||
<h3 class="font-bold text-lg">{s.name}</h3>
|
||||
<pre class="bg-base-300 rounded-box p-3 mt-3 text-sm whitespace-pre-wrap">{output}</pre>
|
||||
</div>
|
||||
<div class="modal-backdrop" onClick={() => setOutput(null)} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -10,100 +10,154 @@ import type { Message } from "@bufbuild/protobuf";
|
||||
* Describes the file homelab/v1/homelab.proto.
|
||||
*/
|
||||
export const file_homelab_v1_homelab: GenFile = /*@__PURE__*/
|
||||
fileDesc("Chhob21lbGFiL3YxL2hvbWVsYWIucHJvdG8SCmhvbWVsYWIudjEiFwoVR2V0THVja3lOdW1iZXJSZXF1ZXN0IigKFkdldEx1Y2t5TnVtYmVyUmVzcG9uc2USDgoGbnVtYmVyGAEgASgDIhkKF0luY3JlbWVudENvdW50ZXJSZXF1ZXN0IikKGEluY3JlbWVudENvdW50ZXJSZXNwb25zZRINCgV2YWx1ZRgBIAEoAyIfCg9TYXlIZWxsb1JlcXVlc3QSDAoEbmFtZRgBIAEoCSIkChBTYXlIZWxsb1Jlc3BvbnNlEhAKCGdyZWV0aW5nGAEgASgJMo8CCg5Ib21lbGFiU2VydmljZRJXCg5HZXRMdWNreU51bWJlchIhLmhvbWVsYWIudjEuR2V0THVja3lOdW1iZXJSZXF1ZXN0GiIuaG9tZWxhYi52MS5HZXRMdWNreU51bWJlclJlc3BvbnNlEl0KEEluY3JlbWVudENvdW50ZXISIy5ob21lbGFiLnYxLkluY3JlbWVudENvdW50ZXJSZXF1ZXN0GiQuaG9tZWxhYi52MS5JbmNyZW1lbnRDb3VudGVyUmVzcG9uc2USRQoIU2F5SGVsbG8SGy5ob21lbGFiLnYxLlNheUhlbGxvUmVxdWVzdBocLmhvbWVsYWIudjEuU2F5SGVsbG9SZXNwb25zZUIqWihwd2EtaG9tZWxhYi1tb24vZ2VuL2hvbWVsYWIvdjE7aG9tZWxhYnYxYgZwcm90bzM");
|
||||
fileDesc("Chhob21lbGFiL3YxL2hvbWVsYWIucHJvdG8SCmhvbWVsYWIudjEiYAoESXRlbRIMCgRuYW1lGAEgASgJEgwKBHR5cGUYAiABKAkSDAoEZGF0YRgDIAEoCRINCgV2YWx1ZRgEIAEoCRIMCgRhcmdzGAUgASgFEhEKCWFyZ190eXBlcxgGIAMoCSJGCgdTZWN0aW9uEgwKBG5hbWUYASABKAkSDAoEaWNvbhgCIAEoCRIfCgVpdGVtcxgDIAMoCzIQLmhvbWVsYWIudjEuSXRlbSIVChNMaXN0U2VjdGlvbnNSZXF1ZXN0Ij0KFExpc3RTZWN0aW9uc1Jlc3BvbnNlEiUKCHNlY3Rpb25zGAEgAygLMhMuaG9tZWxhYi52MS5TZWN0aW9uIi4KEFJ1blNjcmlwdFJlcXVlc3QSDAoEbmFtZRgBIAEoCRIMCgRhcmdzGAIgAygJIiMKEVJ1blNjcmlwdFJlc3BvbnNlEg4KBm91dHB1dBgBIAEoCTKtAQoOSG9tZWxhYlNlcnZpY2USUQoMTGlzdFNlY3Rpb25zEh8uaG9tZWxhYi52MS5MaXN0U2VjdGlvbnNSZXF1ZXN0GiAuaG9tZWxhYi52MS5MaXN0U2VjdGlvbnNSZXNwb25zZRJICglSdW5TY3JpcHQSHC5ob21lbGFiLnYxLlJ1blNjcmlwdFJlcXVlc3QaHS5ob21lbGFiLnYxLlJ1blNjcmlwdFJlc3BvbnNlQipaKHB3YS1ob21lbGFiLW1vbi9nZW4vaG9tZWxhYi92MTtob21lbGFidjFiBnByb3RvMw");
|
||||
|
||||
/**
|
||||
* @generated from message homelab.v1.GetLuckyNumberRequest
|
||||
* @generated from message homelab.v1.Item
|
||||
*/
|
||||
export type GetLuckyNumberRequest = Message<"homelab.v1.GetLuckyNumberRequest"> & {
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message homelab.v1.GetLuckyNumberRequest.
|
||||
* Use `create(GetLuckyNumberRequestSchema)` to create a new message.
|
||||
*/
|
||||
export const GetLuckyNumberRequestSchema: GenMessage<GetLuckyNumberRequest> = /*@__PURE__*/
|
||||
messageDesc(file_homelab_v1_homelab, 0);
|
||||
|
||||
/**
|
||||
* @generated from message homelab.v1.GetLuckyNumberResponse
|
||||
*/
|
||||
export type GetLuckyNumberResponse = Message<"homelab.v1.GetLuckyNumberResponse"> & {
|
||||
/**
|
||||
* @generated from field: int64 number = 1;
|
||||
*/
|
||||
number: bigint;
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message homelab.v1.GetLuckyNumberResponse.
|
||||
* Use `create(GetLuckyNumberResponseSchema)` to create a new message.
|
||||
*/
|
||||
export const GetLuckyNumberResponseSchema: GenMessage<GetLuckyNumberResponse> = /*@__PURE__*/
|
||||
messageDesc(file_homelab_v1_homelab, 1);
|
||||
|
||||
/**
|
||||
* @generated from message homelab.v1.IncrementCounterRequest
|
||||
*/
|
||||
export type IncrementCounterRequest = Message<"homelab.v1.IncrementCounterRequest"> & {
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message homelab.v1.IncrementCounterRequest.
|
||||
* Use `create(IncrementCounterRequestSchema)` to create a new message.
|
||||
*/
|
||||
export const IncrementCounterRequestSchema: GenMessage<IncrementCounterRequest> = /*@__PURE__*/
|
||||
messageDesc(file_homelab_v1_homelab, 2);
|
||||
|
||||
/**
|
||||
* @generated from message homelab.v1.IncrementCounterResponse
|
||||
*/
|
||||
export type IncrementCounterResponse = Message<"homelab.v1.IncrementCounterResponse"> & {
|
||||
/**
|
||||
* @generated from field: int64 value = 1;
|
||||
*/
|
||||
value: bigint;
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message homelab.v1.IncrementCounterResponse.
|
||||
* Use `create(IncrementCounterResponseSchema)` to create a new message.
|
||||
*/
|
||||
export const IncrementCounterResponseSchema: GenMessage<IncrementCounterResponse> = /*@__PURE__*/
|
||||
messageDesc(file_homelab_v1_homelab, 3);
|
||||
|
||||
/**
|
||||
* @generated from message homelab.v1.SayHelloRequest
|
||||
*/
|
||||
export type SayHelloRequest = Message<"homelab.v1.SayHelloRequest"> & {
|
||||
export type Item = Message<"homelab.v1.Item"> & {
|
||||
/**
|
||||
* @generated from field: string name = 1;
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* monitor | response
|
||||
*
|
||||
* @generated from field: string type = 2;
|
||||
*/
|
||||
type: string;
|
||||
|
||||
/**
|
||||
* monitor: text | percentage
|
||||
*
|
||||
* @generated from field: string data = 3;
|
||||
*/
|
||||
data: string;
|
||||
|
||||
/**
|
||||
* monitor: last output
|
||||
*
|
||||
* @generated from field: string value = 4;
|
||||
*/
|
||||
value: string;
|
||||
|
||||
/**
|
||||
* response: arg count
|
||||
*
|
||||
* @generated from field: int32 args = 5;
|
||||
*/
|
||||
args: number;
|
||||
|
||||
/**
|
||||
* response: text | number per arg
|
||||
*
|
||||
* @generated from field: repeated string arg_types = 6;
|
||||
*/
|
||||
argTypes: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message homelab.v1.SayHelloRequest.
|
||||
* Use `create(SayHelloRequestSchema)` to create a new message.
|
||||
* Describes the message homelab.v1.Item.
|
||||
* Use `create(ItemSchema)` to create a new message.
|
||||
*/
|
||||
export const SayHelloRequestSchema: GenMessage<SayHelloRequest> = /*@__PURE__*/
|
||||
export const ItemSchema: GenMessage<Item> = /*@__PURE__*/
|
||||
messageDesc(file_homelab_v1_homelab, 0);
|
||||
|
||||
/**
|
||||
* @generated from message homelab.v1.Section
|
||||
*/
|
||||
export type Section = Message<"homelab.v1.Section"> & {
|
||||
/**
|
||||
* @generated from field: string name = 1;
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* @generated from field: string icon = 2;
|
||||
*/
|
||||
icon: string;
|
||||
|
||||
/**
|
||||
* @generated from field: repeated homelab.v1.Item items = 3;
|
||||
*/
|
||||
items: Item[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message homelab.v1.Section.
|
||||
* Use `create(SectionSchema)` to create a new message.
|
||||
*/
|
||||
export const SectionSchema: GenMessage<Section> = /*@__PURE__*/
|
||||
messageDesc(file_homelab_v1_homelab, 1);
|
||||
|
||||
/**
|
||||
* @generated from message homelab.v1.ListSectionsRequest
|
||||
*/
|
||||
export type ListSectionsRequest = Message<"homelab.v1.ListSectionsRequest"> & {
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message homelab.v1.ListSectionsRequest.
|
||||
* Use `create(ListSectionsRequestSchema)` to create a new message.
|
||||
*/
|
||||
export const ListSectionsRequestSchema: GenMessage<ListSectionsRequest> = /*@__PURE__*/
|
||||
messageDesc(file_homelab_v1_homelab, 2);
|
||||
|
||||
/**
|
||||
* @generated from message homelab.v1.ListSectionsResponse
|
||||
*/
|
||||
export type ListSectionsResponse = Message<"homelab.v1.ListSectionsResponse"> & {
|
||||
/**
|
||||
* @generated from field: repeated homelab.v1.Section sections = 1;
|
||||
*/
|
||||
sections: Section[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message homelab.v1.ListSectionsResponse.
|
||||
* Use `create(ListSectionsResponseSchema)` to create a new message.
|
||||
*/
|
||||
export const ListSectionsResponseSchema: GenMessage<ListSectionsResponse> = /*@__PURE__*/
|
||||
messageDesc(file_homelab_v1_homelab, 3);
|
||||
|
||||
/**
|
||||
* @generated from message homelab.v1.RunScriptRequest
|
||||
*/
|
||||
export type RunScriptRequest = Message<"homelab.v1.RunScriptRequest"> & {
|
||||
/**
|
||||
* @generated from field: string name = 1;
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* @generated from field: repeated string args = 2;
|
||||
*/
|
||||
args: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message homelab.v1.RunScriptRequest.
|
||||
* Use `create(RunScriptRequestSchema)` to create a new message.
|
||||
*/
|
||||
export const RunScriptRequestSchema: GenMessage<RunScriptRequest> = /*@__PURE__*/
|
||||
messageDesc(file_homelab_v1_homelab, 4);
|
||||
|
||||
/**
|
||||
* @generated from message homelab.v1.SayHelloResponse
|
||||
* @generated from message homelab.v1.RunScriptResponse
|
||||
*/
|
||||
export type SayHelloResponse = Message<"homelab.v1.SayHelloResponse"> & {
|
||||
export type RunScriptResponse = Message<"homelab.v1.RunScriptResponse"> & {
|
||||
/**
|
||||
* @generated from field: string greeting = 1;
|
||||
* @generated from field: string output = 1;
|
||||
*/
|
||||
greeting: string;
|
||||
output: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes the message homelab.v1.SayHelloResponse.
|
||||
* Use `create(SayHelloResponseSchema)` to create a new message.
|
||||
* Describes the message homelab.v1.RunScriptResponse.
|
||||
* Use `create(RunScriptResponseSchema)` to create a new message.
|
||||
*/
|
||||
export const SayHelloResponseSchema: GenMessage<SayHelloResponse> = /*@__PURE__*/
|
||||
export const RunScriptResponseSchema: GenMessage<RunScriptResponse> = /*@__PURE__*/
|
||||
messageDesc(file_homelab_v1_homelab, 5);
|
||||
|
||||
/**
|
||||
@@ -111,28 +165,20 @@ export const SayHelloResponseSchema: GenMessage<SayHelloResponse> = /*@__PURE__*
|
||||
*/
|
||||
export const HomelabService: GenService<{
|
||||
/**
|
||||
* @generated from rpc homelab.v1.HomelabService.GetLuckyNumber
|
||||
* @generated from rpc homelab.v1.HomelabService.ListSections
|
||||
*/
|
||||
getLuckyNumber: {
|
||||
listSections: {
|
||||
methodKind: "unary";
|
||||
input: typeof GetLuckyNumberRequestSchema;
|
||||
output: typeof GetLuckyNumberResponseSchema;
|
||||
input: typeof ListSectionsRequestSchema;
|
||||
output: typeof ListSectionsResponseSchema;
|
||||
},
|
||||
/**
|
||||
* @generated from rpc homelab.v1.HomelabService.IncrementCounter
|
||||
* @generated from rpc homelab.v1.HomelabService.RunScript
|
||||
*/
|
||||
incrementCounter: {
|
||||
runScript: {
|
||||
methodKind: "unary";
|
||||
input: typeof IncrementCounterRequestSchema;
|
||||
output: typeof IncrementCounterResponseSchema;
|
||||
},
|
||||
/**
|
||||
* @generated from rpc homelab.v1.HomelabService.SayHello
|
||||
*/
|
||||
sayHello: {
|
||||
methodKind: "unary";
|
||||
input: typeof SayHelloRequestSchema;
|
||||
output: typeof SayHelloResponseSchema;
|
||||
input: typeof RunScriptRequestSchema;
|
||||
output: typeof RunScriptResponseSchema;
|
||||
},
|
||||
}> = /*@__PURE__*/
|
||||
serviceDesc(file_homelab_v1_homelab, 0);
|
||||
|
||||
Reference in New Issue
Block a user