import { ArrowRight, Terminal } from "lucide-react"; import { useState } from "react"; import Chip from "~/components/chip"; import TableList from "~/components/table-list"; import type { AclRule, Policy, SshRule } from "~/utils/acl-policy"; import AclRuleDialog from "../dialogs/acl-rule"; import HostDialog from "../dialogs/host"; import SshRuleDialog from "../dialogs/ssh-rule"; import { Empty, RowActions, Section } from "./editor-section"; interface RulesEditorProps { policy: Policy; onChange: (policy: Policy) => void; isDisabled: boolean; sources: string[]; destinations: string[]; } type Editing = | { kind: "acl"; index: number | null } | { kind: "ssh"; index: number | null } | { kind: "host"; name: string | null } | null; export default function RulesEditor({ policy, onChange, isDisabled, sources, destinations, }: RulesEditorProps) { const [editing, setEditing] = useState(null); const aclRule = editing?.kind === "acl" && editing.index !== null ? policy.acls[editing.index] : undefined; const sshRule = editing?.kind === "ssh" && editing.index !== null ? policy.ssh[editing.index] : undefined; const hostName = editing?.kind === "host" ? editing.name : null; function saveAcl(rule: AclRule) { const acls = [...policy.acls]; if (editing?.kind === "acl" && editing.index !== null) { acls[editing.index] = rule; } else { acls.push(rule); } onChange({ ...policy, acls }); } function saveSsh(rule: SshRule) { const ssh = [...policy.ssh]; if (editing?.kind === "ssh" && editing.index !== null) { ssh[editing.index] = rule; } else { ssh.push(rule); } onChange({ ...policy, ssh }); } function saveHost(name: string, value: string) { const hosts = { ...policy.hosts }; if (hostName !== null && hostName !== name) { delete hosts[hostName]; } hosts[name] = value; onChange({ ...policy, hosts }); } const hostEntries = Object.entries(policy.hosts).sort(([a], [b]) => a.localeCompare(b)); return (
{editing?.kind === "acl" ? ( { if (!open) setEditing(null); }} sources={sources} /> ) : null} {editing?.kind === "ssh" ? ( { if (!open) setEditing(null); }} sources={sources} /> ) : null} {editing?.kind === "host" ? ( { if (!open) setEditing(null); }} value={hostName ? policy.hosts[hostName] : undefined} /> ) : null}
setEditing({ kind: "acl", index: null })} title="Access rules" > {policy.acls.length === 0 ? ( ) : ( policy.acls.map((rule, index) => (
{/* An unknown action is shown verbatim, never relabelled. */} {rule.action === "accept" ? "Allow" : rule.action} {rule.proto ? : null}
onChange({ ...policy, acls: policy.acls.filter((_, i) => i !== index) }) } onEdit={() => setEditing({ kind: "acl", index })} />
)) )}
setEditing({ kind: "ssh", index: null })} title="SSH rules" > {policy.ssh.length === 0 ? ( ) : ( policy.ssh.map((rule, index) => (
{rule.action} as
onChange({ ...policy, ssh: policy.ssh.filter((_, i) => i !== index) }) } onEdit={() => setEditing({ kind: "ssh", index })} />
)) )}
setEditing({ kind: "host", name: null })} title="Hosts" > {hostEntries.length === 0 ? ( ) : ( hostEntries.map(([name, value]) => (
{name} {value}
{ const hosts = { ...policy.hosts }; delete hosts[name]; onChange({ ...policy, hosts }); }} onEdit={() => setEditing({ kind: "host", name })} />
)) )}
); } function ChipRow({ values }: { values: string[] }) { return ( {values.map((value) => ( ))} ); }