refactor(frontend): decompose ConfigPage into dedicated config components

- Extract tab components: JailsTab, ActionsTab, FiltersTab, JailFilesTab,
  GlobalTab, ServerTab, ConfFilesTab, RegexTesterTab, MapTab, ExportTab
- Add form components: JailFileForm, ActionForm, FilterForm
- Add AutoSaveIndicator, RegexList, configStyles, and barrel index
- ConfigPage now composes these components; greatly reduces file size
- Add tests: ConfigPage.test.tsx, useAutoSave.test.ts
This commit is contained in:
2026-03-13 13:48:09 +01:00
parent a0e8566ff8
commit 9b73f6719d
23 changed files with 4275 additions and 1828 deletions

View File

@@ -0,0 +1,103 @@
/**
* RegexList — editable list of regex patterns.
*
* Renders a list of monospace inputs with add/delete controls.
* Used in jail config panels and the filter form.
*/
import { useCallback, useState } from "react";
import { Button, Input, Text } from "@fluentui/react-components";
import { Dismiss24Regular } from "@fluentui/react-icons";
import { useConfigStyles } from "./configStyles";
export interface RegexListProps {
/** Section label displayed above the list. */
label: string;
/** Current list of regex patterns. */
patterns: string[];
/** Called when the list changes (add, delete, or edit). */
onChange: (next: string[]) => void;
}
/**
* Renders an editable list of regex patterns with add and delete controls.
*
* @param props - Component props.
* @returns JSX element.
*/
export function RegexList({
label,
patterns,
onChange,
}: RegexListProps): React.JSX.Element {
const styles = useConfigStyles();
const [newPattern, setNewPattern] = useState("");
const handleAdd = useCallback(() => {
const p = newPattern.trim();
if (p) {
onChange([...patterns, p]);
setNewPattern("");
}
}, [newPattern, patterns, onChange]);
const handleDelete = useCallback(
(idx: number) => {
onChange(patterns.filter((_, i) => i !== idx));
},
[patterns, onChange],
);
return (
<div>
<Text size={200} weight="semibold">
{label}
</Text>
{patterns.length === 0 && (
<Text className={styles.infoText} size={200}>
{" "}
(none)
</Text>
)}
{patterns.map((p, i) => (
<div key={i} className={styles.regexItem}>
<Input
className={styles.regexInput}
value={p}
aria-label={`${label} pattern ${String(i + 1)}`}
onChange={(_e, d) => {
const next = [...patterns];
next[i] = d.value;
onChange(next);
}}
/>
<Button
appearance="subtle"
icon={<Dismiss24Regular />}
size="small"
aria-label={`Remove ${label} pattern ${String(i + 1)}`}
onClick={() => {
handleDelete(i);
}}
/>
</div>
))}
<div className={styles.regexItem}>
<Input
className={styles.regexInput}
placeholder="New pattern…"
value={newPattern}
onChange={(_e, d) => {
setNewPattern(d.value);
}}
onKeyDown={(e) => {
if (e.key === "Enter") handleAdd();
}}
/>
<Button size="small" onClick={handleAdd}>
Add
</Button>
</div>
</div>
);
}