Clean up Config page: remove Export tab, add CreateJailDialog, fix UI details

- Remove Export tab and all its imports from ConfigPage.tsx
- Remove Refresh and Reload fail2ban buttons from JailsTab; clean up
  associated state (reloading, reloadMsg, deactivating) and handlers
- Add Create Config button to Jails tab list pane (listHeader pattern);
  create CreateJailDialog component that calls createJailConfigFile API
- Remove Active/Inactive and 'Has local override' badges from FilterDetail
  and ActionDetail; remove now-unused Badge imports
- Replace read-only log path spans with editable Input fields in JailConfigDetail
- Export CreateJailDialog from components/config/index.ts
- Mark all 5 tasks done in Docs/Tasks.md
This commit is contained in:
2026-03-14 08:33:46 +01:00
parent 6e4797d71e
commit 201cca8b66
7 changed files with 322 additions and 402 deletions

View File

@@ -22,7 +22,7 @@ import {
tokens,
} from "@fluentui/react-components";
import {
ArrowClockwise24Regular,
Add24Regular,
Dismiss24Regular,
LockClosed24Regular,
LockOpen24Regular,
@@ -50,6 +50,7 @@ import { useJailConfigs } from "../../hooks/useConfig";
import { ActivateJailDialog } from "./ActivateJailDialog";
import { AutoSaveIndicator } from "./AutoSaveIndicator";
import { ConfigListDetail } from "./ConfigListDetail";
import { CreateJailDialog } from "./CreateJailDialog";
import { RawConfigSection } from "./RawConfigSection";
import { RegexList } from "./RegexList";
import { useConfigStyles } from "./configStyles";
@@ -295,11 +296,16 @@ function JailConfigDetail({
(none)
</Text>
) : (
logPaths.map((p) => (
<div key={p} className={styles.regexItem}>
<span className={styles.codeFont} style={{ flexGrow: 1 }}>
{p}
</span>
logPaths.map((p, i) => (
<div key={i} className={styles.regexItem}>
<Input
className={styles.codeFont}
style={{ flexGrow: 1 }}
value={p}
onChange={(_e, d) => {
setLogPaths((prev) => prev.map((v, j) => (j === i ? d.value : v)));
}}
/>
<Button
appearance="subtle"
icon={<Dismiss24Regular />}
@@ -583,18 +589,16 @@ function InactiveJailDetail({
*/
export function JailsTab(): React.JSX.Element {
const styles = useConfigStyles();
const { jails, loading, error, refresh, updateJail, reloadAll } =
const { jails, loading, error, refresh, updateJail } =
useJailConfigs();
const { activeJails } = useConfigActiveStatus();
const [selectedName, setSelectedName] = useState<string | null>(null);
const [reloading, setReloading] = useState(false);
const [reloadMsg, setReloadMsg] = useState<string | null>(null);
const [createDialogOpen, setCreateDialogOpen] = useState(false);
// Inactive jails
const [inactiveJails, setInactiveJails] = useState<InactiveJail[]>([]);
const [inactiveLoading, setInactiveLoading] = useState(false);
const [activateTarget, setActivateTarget] = useState<InactiveJail | null>(null);
const [deactivating, setDeactivating] = useState(false);
const loadInactive = useCallback((): void => {
setInactiveLoading(true);
@@ -608,42 +612,14 @@ export function JailsTab(): React.JSX.Element {
loadInactive();
}, [loadInactive]);
const handleRefresh = useCallback((): void => {
refresh();
loadInactive();
}, [refresh, loadInactive]);
const handleReload = useCallback(async () => {
setReloading(true);
setReloadMsg(null);
try {
await reloadAll();
setReloadMsg("fail2ban reloaded.");
refresh();
loadInactive();
} catch (err: unknown) {
setReloadMsg(err instanceof ApiError ? err.message : "Reload failed.");
} finally {
setReloading(false);
}
}, [reloadAll, refresh, loadInactive]);
const handleDeactivate = useCallback((name: string): void => {
setDeactivating(true);
setReloadMsg(null);
deactivateJail(name)
.then(() => {
setReloadMsg(`Jail "${name}" deactivated.`);
setSelectedName(null);
refresh();
loadInactive();
})
.catch((err: unknown) => {
setReloadMsg(
err instanceof ApiError ? err.message : "Deactivation failed."
);
})
.finally(() => { setDeactivating(false); });
.catch(() => { /* non-critical — list refreshes on next load */ });
}, [refresh, loadInactive]);
const handleActivated = useCallback((): void => {
@@ -717,34 +693,19 @@ export function JailsTab(): React.JSX.Element {
);
}
const listHeader = (
<Button
appearance="outline"
icon={<Add24Regular />}
size="small"
onClick={() => { setCreateDialogOpen(true); }}
>
Create Config
</Button>
);
return (
<div className={styles.tabContent}>
<div className={styles.buttonRow}>
<Button
appearance="secondary"
icon={<ArrowClockwise24Regular />}
onClick={handleRefresh}
>
Refresh
</Button>
<Button
appearance="secondary"
icon={<ArrowClockwise24Regular />}
disabled={reloading || deactivating}
onClick={() => void handleReload()}
>
{reloading ? "Reloading…" : "Reload fail2ban"}
</Button>
</div>
{reloadMsg && (
<MessageBar
style={{ marginTop: tokens.spacingVerticalS }}
intent="info"
>
<MessageBarBody>{reloadMsg}</MessageBarBody>
</MessageBar>
)}
<div style={{ marginTop: tokens.spacingVerticalM }}>
<ConfigListDetail
items={listItems}
@@ -753,6 +714,7 @@ export function JailsTab(): React.JSX.Element {
onSelect={setSelectedName}
loading={false}
error={null}
listHeader={listHeader}
>
{selectedActiveJail !== undefined ? (
<JailConfigDetail
@@ -775,6 +737,16 @@ export function JailsTab(): React.JSX.Element {
onClose={() => { setActivateTarget(null); }}
onActivated={handleActivated}
/>
<CreateJailDialog
open={createDialogOpen}
onClose={() => { setCreateDialogOpen(false); }}
onCreated={() => {
setCreateDialogOpen(false);
refresh();
loadInactive();
}}
/>
</div>
);
}