Files
model-platform/frontend/app/features/schedules/NodeInspector.tsx
T
2026-08-28 12:00:14 +08:00

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>
);
}