commit 2
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user