Refactor frontend pages and config components into single-component files for Task 13

This commit is contained in:
2026-04-19 09:30:35 +02:00
parent 6c053cdaee
commit 38b9d35255
31 changed files with 2158 additions and 2603 deletions

View File

@@ -0,0 +1,157 @@
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 { 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 || 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>
);
}