184 lines
7.8 KiB
TypeScript
184 lines
7.8 KiB
TypeScript
import { type ScheduleNode } from "../../services/api";
|
|
import { BookText, FileCode2 } from "lucide-react";
|
|
import { shortHash } from "./utils";
|
|
import { PYTHON_VERSION_OPTIONS, type NodeForm } from "./state/types";
|
|
|
|
export function NodeInspector({
|
|
node,
|
|
form,
|
|
busy,
|
|
onChange,
|
|
onSave,
|
|
onDelete,
|
|
}: {
|
|
node: ScheduleNode;
|
|
form: NodeForm;
|
|
busy: boolean;
|
|
onChange: (form: NodeForm) => void;
|
|
onSave: () => void;
|
|
onDelete: () => void;
|
|
}) {
|
|
return (
|
|
<div className="flex min-h-full flex-col">
|
|
<section className="flex flex-row items-center gap-[10px] border-b border-line p-[14px]">
|
|
<span
|
|
className={`grid h-[31px] w-[31px] flex-none place-items-center rounded-[6px] ${
|
|
node.version.script_type === "notebook"
|
|
? "bg-[#fff0ee] text-[#e05e53]"
|
|
: "bg-[#eaf3ff] text-[#326fc1]"
|
|
}`}
|
|
>
|
|
{node.version.script_type === "notebook" ? (
|
|
<BookText size={17} />
|
|
) : (
|
|
<FileCode2 size={17} />
|
|
)}
|
|
</span>
|
|
<div className="flex min-w-0 flex-col gap-[3px]">
|
|
<small className="text-[9px] text-[#96a2af]">节点配置</small>
|
|
<strong className="truncate font-mono text-[11px] text-[#30465d]">
|
|
{node.node_key}
|
|
</strong>
|
|
</div>
|
|
</section>
|
|
<section className="flex flex-col gap-[10px] border-b border-line p-[14px]">
|
|
<h3 className="mb-[2px] text-[13px] text-[#273b50]">稳定版本</h3>
|
|
<div className="flex flex-col gap-[5px] rounded-[6px] border border-[#dae6f2] bg-[#f6faff] p-[9px]">
|
|
<strong className="text-[11px] text-[#35516e]">{node.version.script_name}</strong>
|
|
<span className="w-fit rounded-[8px] bg-[#e3f0fd] px-1.5 py-[2px] text-[9px] font-bold text-[#176ec5]">
|
|
{node.version.version_label}
|
|
</span>
|
|
<small className="truncate font-mono text-[8px] text-[#8190a0]">
|
|
versions_id: {node.versions_id}
|
|
</small>
|
|
<small className="truncate font-mono text-[8px] text-[#8190a0]">
|
|
SHA-256: {shortHash(node.version.content_hash)}
|
|
</small>
|
|
</div>
|
|
</section>
|
|
<section className="flex flex-col gap-[10px] border-b border-line p-[14px]">
|
|
<h3 className="mb-[2px] text-[13px] text-[#273b50]">节点信息</h3>
|
|
<label className="flex flex-col gap-[5px]">
|
|
<span className="text-[10px] font-semibold text-ink-caption">节点名称</span>
|
|
<input
|
|
className="h-[33px] w-full rounded-[5px] border border-line bg-white px-2 text-[11px] text-[#32475d] outline-none focus:border-brand/70 focus:ring-3 focus:ring-brand/10"
|
|
value={form.nodeName}
|
|
onChange={(event) => onChange({
|
|
...form,
|
|
nodeName: event.target.value,
|
|
})}
|
|
/>
|
|
</label>
|
|
<div className="grid grid-cols-2 gap-2">
|
|
<label className="flex flex-col gap-[5px]">
|
|
<span className="text-[10px] font-semibold text-ink-caption">超时(秒)</span>
|
|
<input
|
|
type="number"
|
|
min="1"
|
|
className="h-[33px] w-full rounded-[5px] border border-line bg-white px-2 text-[11px] text-[#32475d] outline-none focus:border-brand/70 focus:ring-3 focus:ring-brand/10"
|
|
value={form.timeoutSeconds}
|
|
onChange={(event) => onChange({
|
|
...form,
|
|
timeoutSeconds: event.target.value,
|
|
})}
|
|
/>
|
|
</label>
|
|
<label className="flex flex-col gap-[5px]">
|
|
<span className="text-[10px] font-semibold text-ink-caption">重试次数</span>
|
|
<input
|
|
type="number"
|
|
min="0"
|
|
max="10"
|
|
className="h-[33px] w-full rounded-[5px] border border-line bg-white px-2 text-[11px] text-[#32475d] outline-none focus:border-brand/70 focus:ring-3 focus:ring-brand/10"
|
|
value={form.retryCount}
|
|
onChange={(event) => onChange({
|
|
...form,
|
|
retryCount: event.target.value,
|
|
})}
|
|
/>
|
|
</label>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-2">
|
|
<label className="flex flex-col gap-[5px]">
|
|
<span className="text-[10px] font-semibold text-ink-caption">重试间隔(秒)</span>
|
|
<input
|
|
type="number"
|
|
min="0"
|
|
className="h-[33px] w-full rounded-[5px] border border-line bg-white px-2 text-[11px] text-[#32475d] outline-none focus:border-brand/70 focus:ring-3 focus:ring-brand/10"
|
|
value={form.retryIntervalSec}
|
|
onChange={(event) => onChange({
|
|
...form,
|
|
retryIntervalSec: event.target.value,
|
|
})}
|
|
/>
|
|
</label>
|
|
<label className="flex flex-col gap-[5px]">
|
|
<span className="text-[10px] font-semibold text-ink-caption">Python 版本</span>
|
|
<select
|
|
className="h-[33px] w-full rounded-[5px] border border-line bg-white px-2 text-[11px] text-[#32475d] outline-none focus:border-brand/70 focus:ring-3 focus:ring-brand/10"
|
|
value={form.pythonVersion}
|
|
onChange={(event) => onChange({
|
|
...form,
|
|
pythonVersion: event.target.value as NodeForm["pythonVersion"],
|
|
})}
|
|
>
|
|
{PYTHON_VERSION_OPTIONS.map((option) => (
|
|
<option key={option} value={option}>
|
|
{option}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
</div>
|
|
</section>
|
|
<section className="flex flex-col gap-[10px] border-b border-line p-[14px]">
|
|
<h3 className="mb-[2px] text-[13px] text-[#273b50]">运行参数</h3>
|
|
<label className="flex flex-col gap-[5px]">
|
|
<span className="text-[10px] font-semibold text-ink-caption">arguments_json</span>
|
|
<textarea
|
|
className="w-full resize-y rounded-[5px] border border-line p-[7px_8px] font-mono text-[10px] leading-[1.5] !bg-[#f8fafc] !text-[#37516a] outline-none focus:border-brand/70 focus:ring-3 focus:ring-brand/10"
|
|
rows={5}
|
|
spellCheck={false}
|
|
value={form.argumentsJson}
|
|
onChange={(event) => onChange({
|
|
...form,
|
|
argumentsJson: event.target.value,
|
|
})}
|
|
/>
|
|
</label>
|
|
<label className="flex flex-col gap-[5px]">
|
|
<span className="text-[10px] font-semibold text-ink-caption">env_refs_json</span>
|
|
<textarea
|
|
className="w-full resize-y rounded-[5px] border border-line p-[7px_8px] font-mono text-[10px] leading-[1.5] !bg-[#f8fafc] !text-[#37516a] outline-none focus:border-brand/70 focus:ring-3 focus:ring-brand/10"
|
|
rows={4}
|
|
spellCheck={false}
|
|
value={form.envRefsJson}
|
|
onChange={(event) => onChange({
|
|
...form,
|
|
envRefsJson: event.target.value,
|
|
})}
|
|
/>
|
|
</label>
|
|
</section>
|
|
<div className="mt-auto grid grid-cols-[1fr_90px] gap-[7px] p-[14px]">
|
|
<button
|
|
className="flex min-h-[34px] cursor-pointer items-center justify-center rounded-[6px] border border-brand bg-brand text-[11px] text-white [font-weight:650] disabled:cursor-not-allowed disabled:opacity-[0.55]"
|
|
type="button"
|
|
disabled={busy}
|
|
onClick={onSave}
|
|
>
|
|
保存节点
|
|
</button>
|
|
<button
|
|
className="flex min-h-[34px] cursor-pointer items-center justify-center rounded-[6px] border border-[#efc6c6] bg-[#fff7f7] text-[11px] text-[#bd4f4f] [font-weight:650] disabled:cursor-not-allowed disabled:opacity-[0.55]"
|
|
type="button"
|
|
disabled={busy}
|
|
onClick={onDelete}
|
|
>
|
|
删除节点
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|