import { AlertTriangleIcon, Loader2Icon } from "lucide-react"; import { useEffect, useState, type FormEvent } from "react"; import type { MachineOperationPolicy, UpdateMachineOperationPolicyRequest } from "@opengeni/sdk"; import { useOpenGeniText, type OpenGeniText } from "../../messages/runtime"; export type MachineOperationPolicyEditorProps = { policy: MachineOperationPolicy; memorySupported: boolean; cpuSupported: boolean; saving?: boolean | undefined; onSave: (request: UpdateMachineOperationPolicyRequest) => Promise | unknown; }; function fieldValue(value: number | null): string { return value === null ? "" : String(value); } function parseByteCount(t: OpenGeniText, label: string, value: string): number | null { const trimmed = value.trim(); if (!trimmed) return null; if (!/^\d+$/.test(trimmed)) { throw new Error(t("machines.machineOperationPolicy.bytesNotWholeNumber", { label })); } const parsed = Number(trimmed); if (!Number.isSafeInteger(parsed) || parsed <= 0) { throw new Error(t("machines.machineOperationPolicy.bytesNotPositive", { label })); } return parsed; } function parseCpuMillicores(t: OpenGeniText, value: string): number | null { const trimmed = value.trim(); if (!trimmed) return null; if (!/^\d+$/.test(trimmed)) { throw new Error(t("machines.machineOperationPolicy.cpuNotWholeNumber")); } const parsed = Number(trimmed); if (!Number.isSafeInteger(parsed) || parsed <= 0 || parsed > 0xffff_ffff) { throw new Error(t("machines.machineOperationPolicy.cpuOutOfRange")); } return parsed; } /** Exact editor for the optional per-enrollment command resource policy. Blank * values remain genuinely unrestricted; CPU is a period-independent ratio. */ export function MachineOperationPolicyEditor({ policy, memorySupported, cpuSupported, saving = false, onSave, }: MachineOperationPolicyEditorProps) { const t = useOpenGeniText(); const [memoryMaxBytes, setMemoryMaxBytes] = useState(fieldValue(policy.memoryMaxBytes)); const [memoryHighBytes, setMemoryHighBytes] = useState(fieldValue(policy.memoryHighBytes)); const [cpuMaxMillicores, setCpuMaxMillicores] = useState(fieldValue(policy.cpuMaxMillicores)); const [validationError, setValidationError] = useState(null); useEffect(() => { setMemoryMaxBytes(fieldValue(policy.memoryMaxBytes)); setMemoryHighBytes(fieldValue(policy.memoryHighBytes)); setCpuMaxMillicores(fieldValue(policy.cpuMaxMillicores)); setValidationError(null); }, [policy.memoryMaxBytes, policy.memoryHighBytes, policy.cpuMaxMillicores, policy.revision]); const submit = async (event: FormEvent) => { event.preventDefault(); try { const max = parseByteCount( t, t("machines.machineOperationPolicy.hardCeiling"), memoryMaxBytes, ); const high = parseByteCount( t, t("machines.machineOperationPolicy.reclaimThreshold"), memoryHighBytes, ); if (max !== null && high !== null && high > max) { throw new Error(t("machines.machineOperationPolicy.reclaimAboveCeiling")); } const cpuMax = parseCpuMillicores(t, cpuMaxMillicores); setValidationError(null); await onSave({ memoryMaxBytes: max, memoryHighBytes: high, cpuMaxMillicores: cpuMax, expectedRevision: policy.revision, }); } catch (error) { setValidationError(error instanceof Error ? error.message : String(error)); } }; const configured = policy.memoryMaxBytes !== null || policy.memoryHighBytes !== null || policy.cpuMaxMillicores !== null; const memoryConfigured = policy.memoryMaxBytes !== null || policy.memoryHighBytes !== null; const unsupportedMemory = memoryConfigured && !memorySupported; const unsupportedCpu = policy.cpuMaxMillicores !== null && (!memorySupported || !cpuSupported); return (

{t("machines.machineOperationPolicy.perCommandResourcePolicy")}

{t("machines.machineOperationPolicy.optionalLimitsAppliedSeparatelyTo")}

{configured && (unsupportedMemory || unsupportedCpu) ? (
{unsupportedCpu ? t("machines.machineOperationPolicy.thisRunnerCannotEnforceThe") : t("machines.machineOperationPolicy.thisRunnerCannotEnforceThe2")}{" "} {t("machines.machineOperationPolicy.commandExecutionFailsClosedUntil")}
) : null}
{validationError}
); }