Merge Global tab into Server tab and remove Global tab

Global tab provided the same four editable fields as Server tab:
log_level, log_target, db_purge_age, db_max_matches. Server tab already
has these fields plus additional read-only info (db_path, syslog_socket)
and a Flush Logs button.

- Add hint text to DB Purge Age and DB Max Matches fields in ServerTab
- Remove GlobalTab component import from ConfigPage
- Remove 'global' from TabValue type
- Remove Global tab element from TabList
- Remove conditional render for GlobalTab
- Remove GlobalTab from barrel export (index.ts)
- Delete GlobalTab.tsx file
- Update ConfigPage test to remove Global tab test case

All 123 frontend tests pass.
This commit is contained in:
2026-03-14 21:52:44 +01:00
parent 2e1a4b3b2b
commit 037c18eb00
6 changed files with 150 additions and 236 deletions

View File

@@ -1,142 +0,0 @@
/**
* GlobalTab — global fail2ban settings editor.
*
* Provides form fields for log level, log target, database purge age,
* and database max matches.
*/
import { useEffect, useMemo, useState } from "react";
import {
Field,
Input,
MessageBar,
MessageBarBody,
Select,
Spinner,
} from "@fluentui/react-components";
import type { GlobalConfigUpdate } from "../../types/config";
import { useGlobalConfig } from "../../hooks/useConfig";
import { useAutoSave } from "../../hooks/useAutoSave";
import { AutoSaveIndicator } from "./AutoSaveIndicator";
import { useConfigStyles } from "./configStyles";
/** Available fail2ban log levels in descending severity order. */
const LOG_LEVELS = ["CRITICAL", "ERROR", "WARNING", "NOTICE", "INFO", "DEBUG"];
/**
* Tab component for editing global fail2ban configuration.
*
* @returns JSX element.
*/
export function GlobalTab(): React.JSX.Element {
const styles = useConfigStyles();
const { config, loading, error, updateConfig } = useGlobalConfig();
const [logLevel, setLogLevel] = useState("");
const [logTarget, setLogTarget] = useState("");
const [dbPurgeAge, setDbPurgeAge] = useState("");
const [dbMaxMatches, setDbMaxMatches] = useState("");
// Sync local state when config loads for the first time.
useEffect(() => {
if (config && logLevel === "") {
setLogLevel(config.log_level);
setLogTarget(config.log_target);
setDbPurgeAge(String(config.db_purge_age));
setDbMaxMatches(String(config.db_max_matches));
}
// Only run on first config load.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [config]);
const effectiveLogLevel = logLevel || config?.log_level || "";
const effectiveLogTarget = logTarget || config?.log_target || "";
const effectiveDbPurgeAge =
dbPurgeAge || (config ? String(config.db_purge_age) : "");
const effectiveDbMaxMatches =
dbMaxMatches || (config ? String(config.db_max_matches) : "");
const updatePayload = useMemo<GlobalConfigUpdate>(() => {
const update: GlobalConfigUpdate = {};
if (effectiveLogLevel) update.log_level = effectiveLogLevel;
if (effectiveLogTarget) update.log_target = effectiveLogTarget;
if (effectiveDbPurgeAge)
update.db_purge_age = Number(effectiveDbPurgeAge);
if (effectiveDbMaxMatches)
update.db_max_matches = Number(effectiveDbMaxMatches);
return update;
}, [effectiveLogLevel, effectiveLogTarget, effectiveDbPurgeAge, effectiveDbMaxMatches]);
const { status: saveStatus, errorText: saveErrorText, retry: retrySave } =
useAutoSave(updatePayload, updateConfig);
if (loading) return <Spinner label="Loading global config…" />;
if (error)
return (
<MessageBar intent="error">
<MessageBarBody>{error}</MessageBarBody>
</MessageBar>
);
return (
<div>
<div className={styles.sectionCard}>
<AutoSaveIndicator
status={saveStatus}
errorText={saveErrorText}
onRetry={retrySave}
/>
<div className={styles.fieldRow}>
<Field label="Log Level">
<Select
value={effectiveLogLevel}
onChange={(_e, d) => {
setLogLevel(d.value);
}}
>
{LOG_LEVELS.map((l) => (
<option key={l} value={l}>
{l}
</option>
))}
</Select>
</Field>
<Field label="Log Target">
<Input
value={effectiveLogTarget}
placeholder="STDOUT / /var/log/fail2ban.log"
onChange={(_e, d) => {
setLogTarget(d.value);
}}
/>
</Field>
</div>
<div className={styles.fieldRow}>
<Field
label="DB Purge Age (s)"
hint="Ban records older than this are removed from the fail2ban database."
>
<Input
type="number"
value={effectiveDbPurgeAge}
onChange={(_e, d) => {
setDbPurgeAge(d.value);
}}
/>
</Field>
<Field
label="DB Max Matches"
hint="Maximum number of log-line matches stored per ban record."
>
<Input
type="number"
value={effectiveDbMaxMatches}
onChange={(_e, d) => {
setDbMaxMatches(d.value);
}}
/>
</Field>
</div>
</div>
</div>
);
}

View File

@@ -154,7 +154,10 @@ export function ServerTab(): React.JSX.Element {
</Field>
</div>
<div className={styles.fieldRow}>
<Field label="DB Purge Age (s)">
<Field
label="DB Purge Age (s)"
hint="Ban records older than this are removed from the fail2ban database."
>
<Input
type="number"
value={effectiveDbPurgeAge}
@@ -163,7 +166,10 @@ export function ServerTab(): React.JSX.Element {
}}
/>
</Field>
<Field label="DB Max Matches">
<Field
label="DB Max Matches"
hint="Maximum number of log-line matches stored per ban record."
>
<Input
type="number"
value={effectiveDbMaxMatches}

View File

@@ -30,7 +30,6 @@ export { ExportTab } from "./ExportTab";
export { FilterForm } from "./FilterForm";
export type { FilterFormProps } from "./FilterForm";
export { FiltersTab } from "./FiltersTab";
export { GlobalTab } from "./GlobalTab";
export { JailFilesTab } from "./JailFilesTab";
export { JailFileForm } from "./JailFileForm";
export { JailsTab } from "./JailsTab";