160 lines
5.3 KiB
TypeScript
160 lines
5.3 KiB
TypeScript
import { useCallback } from "react";
|
|
import {
|
|
Accordion,
|
|
AccordionHeader,
|
|
AccordionItem,
|
|
AccordionPanel,
|
|
Field,
|
|
Input,
|
|
Select,
|
|
Switch,
|
|
} from "@fluentui/react-components";
|
|
import { KVEditor } from "./KVEditor";
|
|
import { StringListEditor } from "./StringListEditor";
|
|
import { useConfigStyles } from "./configStyles";
|
|
import type { BackendType, JailSectionConfig } from "../../types/config";
|
|
|
|
const BACKENDS = ["", "auto", "polling", "gamin", "pyinotify", "systemd"] as const;
|
|
|
|
interface JailSectionPanelProps {
|
|
jailName: string;
|
|
section: JailSectionConfig;
|
|
onChange: (next: JailSectionConfig) => void;
|
|
}
|
|
|
|
export function JailSectionPanel({ jailName, section, onChange }: JailSectionPanelProps): React.JSX.Element {
|
|
const styles = useConfigStyles();
|
|
|
|
const update = useCallback(
|
|
(patch: Partial<JailSectionConfig>): void => {
|
|
onChange({ ...section, ...patch });
|
|
},
|
|
[onChange, section],
|
|
);
|
|
|
|
return (
|
|
<div>
|
|
<div className={styles.sectionCard}>
|
|
<div className={styles.fieldRow}>
|
|
<Field label="Enabled">
|
|
<Switch
|
|
checked={section.enabled ?? false}
|
|
onChange={(_e, d) => { update({ enabled: d.checked }); }}
|
|
aria-label={`Enable jail ${jailName}`}
|
|
/>
|
|
</Field>
|
|
<Field label="Port">
|
|
<Input
|
|
value={section.port ?? ""}
|
|
size="small"
|
|
placeholder="e.g. ssh or 22"
|
|
onChange={(_e, d) => { update({ port: d.value || null }); }}
|
|
/>
|
|
</Field>
|
|
<Field label="Filter">
|
|
<Input
|
|
value={section.filter ?? ""}
|
|
size="small"
|
|
placeholder="e.g. sshd"
|
|
className={styles.codeInput}
|
|
onChange={(_e, d) => { update({ filter: d.value || null }); }}
|
|
/>
|
|
</Field>
|
|
<Field label="Backend">
|
|
<Select
|
|
size="small"
|
|
value={section.backend ?? ""}
|
|
onChange={(_e, d) => {
|
|
update({ backend: d.value ? (d.value as BackendType) : null });
|
|
}}
|
|
>
|
|
{BACKENDS.map((b) => (
|
|
<option key={b} value={b}>
|
|
{b === "" ? "(default)" : b}
|
|
</option>
|
|
))}
|
|
</Select>
|
|
</Field>
|
|
<Field label="Max Retry">
|
|
<Input
|
|
value={section.maxretry !== null ? String(section.maxretry) : ""}
|
|
size="small"
|
|
type="number"
|
|
placeholder="e.g. 5"
|
|
onChange={(_e, d) => {
|
|
const n = parseInt(d.value, 10);
|
|
update({ maxretry: d.value === "" ? null : isNaN(n) ? null : n });
|
|
}}
|
|
/>
|
|
</Field>
|
|
<Field label="Find Time (s)">
|
|
<Input
|
|
value={section.findtime !== null ? String(section.findtime) : ""}
|
|
size="small"
|
|
type="number"
|
|
placeholder="e.g. 600"
|
|
onChange={(_e, d) => {
|
|
const n = parseInt(d.value, 10);
|
|
update({ findtime: d.value === "" ? null : isNaN(n) ? null : n });
|
|
}}
|
|
/>
|
|
</Field>
|
|
<Field label="Ban Time (s)">
|
|
<Input
|
|
value={section.bantime !== null ? String(section.bantime) : ""}
|
|
size="small"
|
|
type="number"
|
|
placeholder="e.g. 3600"
|
|
onChange={(_e, d) => {
|
|
const n = parseInt(d.value, 10);
|
|
update({ bantime: d.value === "" ? null : isNaN(n) ? null : n });
|
|
}}
|
|
/>
|
|
</Field>
|
|
</div>
|
|
</div>
|
|
|
|
<Accordion multiple collapsible defaultOpenItems={["logpath"]}>
|
|
<AccordionItem value="logpath" className={styles.accordionItem}>
|
|
<AccordionHeader>Log Paths ({section.logpath.length})</AccordionHeader>
|
|
<AccordionPanel>
|
|
<div className={styles.sectionCard}>
|
|
<StringListEditor
|
|
items={section.logpath}
|
|
onChange={(next) => { update({ logpath: next }); }}
|
|
placeholder="e.g. /var/log/auth.log"
|
|
addLabel="Add log path"
|
|
/>
|
|
</div>
|
|
</AccordionPanel>
|
|
</AccordionItem>
|
|
|
|
<AccordionItem value="actions" className={styles.accordionItem}>
|
|
<AccordionHeader>Actions ({section.action.length})</AccordionHeader>
|
|
<AccordionPanel>
|
|
<div className={styles.sectionCard}>
|
|
<StringListEditor
|
|
items={section.action}
|
|
onChange={(next) => { update({ action: next }); }}
|
|
placeholder="e.g. iptables-multiport[name=SSH, port=ssh]"
|
|
addLabel="Add action"
|
|
/>
|
|
</div>
|
|
</AccordionPanel>
|
|
</AccordionItem>
|
|
|
|
{Object.keys(section.extra).length > 0 && (
|
|
<AccordionItem value="extra" className={styles.accordionItem}>
|
|
<AccordionHeader>Extra Settings ({Object.keys(section.extra).length})</AccordionHeader>
|
|
<AccordionPanel>
|
|
<div className={styles.sectionCard}>
|
|
<KVEditor entries={section.extra} onChange={(next) => { update({ extra: next }); }} />
|
|
</div>
|
|
</AccordionPanel>
|
|
</AccordionItem>
|
|
)}
|
|
</Accordion>
|
|
</div>
|
|
);
|
|
}
|