73 lines
2.6 KiB
TypeScript
73 lines
2.6 KiB
TypeScript
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>
|
|
);
|
|
}
|