Config page tasks 1-4: dropdowns, key props, inactive jail full GUI, banaction fix
Task 1: Backend/LogEncoding/DatePattern dropdowns in JailConfigDetail
- Added BACKENDS, LOG_ENCODINGS, DATE_PATTERN_PRESETS constants
- Backend and Log Encoding: <Input readOnly> → <Select> (editable, auto-saves)
- Date Pattern: <Input> → <Combobox freeform> with presets
- Extended JailConfigUpdate model (backend, log_encoding) and service
- Added readOnly prop to JailConfigDetail (all fields, toggles, buttons)
- Extended RegexList with readOnly prop
Task 2: Fix raw action/filter config always blank
- Added key={selectedAction.name} to ActionDetail in ActionsTab
- Added key={selectedFilter.name} to FilterDetail in FiltersTab
Task 3: Inactive jail full GUI same as active jails
- Extended InactiveJail Pydantic model with all config fields
- Added _parse_time_to_seconds helper to config_file_service
- Updated _build_inactive_jail to populate all extended fields
- Extended InactiveJail TypeScript type to match
- Rewrote InactiveJailDetail to reuse JailConfigDetail (readOnly=true)
Task 4: Fix banaction interpolation error when activating jails
- _write_local_override_sync now includes banaction=iptables-multiport
and banaction_allports=iptables-allports in every .local file
This commit is contained in:
@@ -299,6 +299,7 @@ export function ActionsTab(): React.JSX.Element {
|
||||
>
|
||||
{selectedAction !== null && (
|
||||
<ActionDetail
|
||||
key={selectedAction.name}
|
||||
action={selectedAction}
|
||||
onAssignClick={() => { setAssignOpen(true); }}
|
||||
onRemovedFromJail={handleRemovedFromJail}
|
||||
|
||||
@@ -233,6 +233,7 @@ export function FiltersTab(): React.JSX.Element {
|
||||
>
|
||||
{selectedFilter !== null && (
|
||||
<FilterDetail
|
||||
key={selectedFilter.name}
|
||||
filter={selectedFilter}
|
||||
onAssignClick={() => { setAssignOpen(true); }}
|
||||
/>
|
||||
|
||||
@@ -10,10 +10,12 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Combobox,
|
||||
Field,
|
||||
Input,
|
||||
MessageBar,
|
||||
MessageBarBody,
|
||||
Option,
|
||||
Select,
|
||||
Skeleton,
|
||||
SkeletonItem,
|
||||
@@ -55,6 +57,36 @@ import { RawConfigSection } from "./RawConfigSection";
|
||||
import { RegexList } from "./RegexList";
|
||||
import { useConfigStyles } from "./configStyles";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const BACKENDS = [
|
||||
{ value: "auto", label: "auto — pyinotify, then polling" },
|
||||
{ value: "polling", label: "polling — standard polling algorithm" },
|
||||
{ value: "pyinotify", label: "pyinotify — requires pyinotify library" },
|
||||
{ value: "systemd", label: "systemd — uses systemd journal" },
|
||||
{ value: "gamin", label: "gamin — legacy file alteration monitor" },
|
||||
] as const;
|
||||
|
||||
const LOG_ENCODINGS = [
|
||||
{ value: "auto", label: "auto — use system locale" },
|
||||
{ value: "ascii", label: "ascii" },
|
||||
{ value: "utf-8", label: "utf-8" },
|
||||
{ value: "latin-1", label: "latin-1 (ISO 8859-1)" },
|
||||
] as const;
|
||||
|
||||
const DATE_PATTERN_PRESETS = [
|
||||
{ value: "", label: "auto-detect (leave blank)" },
|
||||
{ value: "{^LN-BEG}", label: "{^LN-BEG} — line beginning" },
|
||||
{ value: "%%Y-%%m-%%d %%H:%%M:%%S", label: "YYYY-MM-DD HH:MM:SS" },
|
||||
{ value: "%%d/%%b/%%Y:%%H:%%M:%%S", label: "DD/Mon/YYYY:HH:MM:SS (Apache)" },
|
||||
{ value: "%%b %%d %%H:%%M:%%S", label: "Mon DD HH:MM:SS (syslog)" },
|
||||
{ value: "EPOCH", label: "EPOCH — Unix timestamp" },
|
||||
{ value: "%%Y-%%m-%%dT%%H:%%M:%%S", label: "ISO 8601" },
|
||||
{ value: "TAI64N", label: "TAI64N" },
|
||||
] as const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// JailConfigDetail
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -63,6 +95,10 @@ interface JailConfigDetailProps {
|
||||
jail: JailConfig;
|
||||
onSave: (name: string, update: JailConfigUpdate) => Promise<void>;
|
||||
onDeactivate?: () => void;
|
||||
/** When true all fields are read-only and auto-save is suppressed. */
|
||||
readOnly?: boolean;
|
||||
/** When provided (and readOnly=true) shows an Activate Jail button. */
|
||||
onActivate?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,6 +114,8 @@ function JailConfigDetail({
|
||||
jail,
|
||||
onSave,
|
||||
onDeactivate,
|
||||
readOnly = false,
|
||||
onActivate,
|
||||
}: JailConfigDetailProps): React.JSX.Element {
|
||||
const styles = useConfigStyles();
|
||||
const [banTime, setBanTime] = useState(String(jail.ban_time));
|
||||
@@ -88,6 +126,8 @@ function JailConfigDetail({
|
||||
const [logPaths, setLogPaths] = useState<string[]>(jail.log_paths);
|
||||
const [datePattern, setDatePattern] = useState(jail.date_pattern ?? "");
|
||||
const [dnsMode, setDnsMode] = useState(jail.use_dns);
|
||||
const [backend, setBackend] = useState(jail.backend);
|
||||
const [logEncoding, setLogEncoding] = useState(jail.log_encoding);
|
||||
const [prefRegex, setPrefRegex] = useState(jail.prefregex);
|
||||
const [deletingPath, setDeletingPath] = useState<string | null>(null);
|
||||
const [newLogPath, setNewLogPath] = useState("");
|
||||
@@ -165,6 +205,8 @@ function JailConfigDetail({
|
||||
ignore_regex: ignoreRegex,
|
||||
date_pattern: datePattern !== "" ? datePattern : null,
|
||||
dns_mode: dnsMode,
|
||||
backend,
|
||||
log_encoding: logEncoding,
|
||||
prefregex: prefRegex !== "" ? prefRegex : null,
|
||||
bantime_escalation: {
|
||||
increment: escEnabled,
|
||||
@@ -178,17 +220,18 @@ function JailConfigDetail({
|
||||
}),
|
||||
[
|
||||
banTime, findTime, maxRetry, failRegex, ignoreRegex, datePattern,
|
||||
dnsMode, prefRegex, escEnabled, escFactor, escFormula, escMultipliers,
|
||||
escMaxTime, escRndTime, escOverallJails,
|
||||
dnsMode, backend, logEncoding, prefRegex, escEnabled, escFactor,
|
||||
escFormula, escMultipliers, escMaxTime, escRndTime, escOverallJails,
|
||||
jail.ban_time, jail.find_time, jail.max_retry,
|
||||
],
|
||||
);
|
||||
|
||||
const saveCurrent = useCallback(
|
||||
async (update: JailConfigUpdate): Promise<void> => {
|
||||
if (readOnly) return;
|
||||
await onSave(jail.name, update);
|
||||
},
|
||||
[jail.name, onSave],
|
||||
[jail.name, onSave, readOnly],
|
||||
);
|
||||
|
||||
const { status: saveStatus, errorText: saveErrorText, retry: retrySave } =
|
||||
@@ -220,6 +263,7 @@ function JailConfigDetail({
|
||||
<Input
|
||||
type="number"
|
||||
value={banTime}
|
||||
readOnly={readOnly}
|
||||
onChange={(_e, d) => {
|
||||
setBanTime(d.value);
|
||||
}}
|
||||
@@ -229,6 +273,7 @@ function JailConfigDetail({
|
||||
<Input
|
||||
type="number"
|
||||
value={findTime}
|
||||
readOnly={readOnly}
|
||||
onChange={(_e, d) => {
|
||||
setFindTime(d.value);
|
||||
}}
|
||||
@@ -238,6 +283,7 @@ function JailConfigDetail({
|
||||
<Input
|
||||
type="number"
|
||||
value={maxRetry}
|
||||
readOnly={readOnly}
|
||||
onChange={(_e, d) => {
|
||||
setMaxRetry(d.value);
|
||||
}}
|
||||
@@ -246,26 +292,57 @@ function JailConfigDetail({
|
||||
</div>
|
||||
<div className={styles.fieldRow}>
|
||||
<Field label="Backend">
|
||||
<Input readOnly value={jail.backend} />
|
||||
<Select
|
||||
value={backend}
|
||||
disabled={readOnly}
|
||||
onChange={(_e, d) => {
|
||||
setBackend(d.value);
|
||||
}}
|
||||
>
|
||||
{BACKENDS.map((b) => (
|
||||
<option key={b.value} value={b.value}>{b.label}</option>
|
||||
))}
|
||||
</Select>
|
||||
</Field>
|
||||
<Field label="Log Encoding">
|
||||
<Input readOnly value={jail.log_encoding} />
|
||||
<Select
|
||||
value={logEncoding.toLowerCase()}
|
||||
disabled={readOnly}
|
||||
onChange={(_e, d) => {
|
||||
setLogEncoding(d.value);
|
||||
}}
|
||||
>
|
||||
{LOG_ENCODINGS.map((e) => (
|
||||
<option key={e.value} value={e.value}>{e.label}</option>
|
||||
))}
|
||||
</Select>
|
||||
</Field>
|
||||
</div>
|
||||
<div className={styles.fieldRow}>
|
||||
<Field label="Date Pattern" hint="Leave blank for auto-detect.">
|
||||
<Input
|
||||
<Combobox
|
||||
className={styles.codeFont}
|
||||
placeholder="auto-detect"
|
||||
value={datePattern}
|
||||
onChange={(_e, d) => {
|
||||
setDatePattern(d.value);
|
||||
selectedOptions={[datePattern]}
|
||||
freeform
|
||||
disabled={readOnly}
|
||||
onOptionSelect={(_e, d) => {
|
||||
setDatePattern(d.optionValue ?? "");
|
||||
}}
|
||||
/>
|
||||
onChange={(e) => {
|
||||
setDatePattern(e.target.value);
|
||||
}}
|
||||
>
|
||||
{DATE_PATTERN_PRESETS.map((p) => (
|
||||
<Option key={p.value} value={p.value}>{p.label}</Option>
|
||||
))}
|
||||
</Combobox>
|
||||
</Field>
|
||||
<Field label="DNS Mode">
|
||||
<Select
|
||||
value={dnsMode}
|
||||
disabled={readOnly}
|
||||
onChange={(_e, d) => {
|
||||
setDnsMode(d.value);
|
||||
}}
|
||||
@@ -285,6 +362,7 @@ function JailConfigDetail({
|
||||
className={styles.codeFont}
|
||||
placeholder="e.g. ^%(__prefix_line)s"
|
||||
value={prefRegex}
|
||||
readOnly={readOnly}
|
||||
onChange={(_e, d) => {
|
||||
setPrefRegex(d.value);
|
||||
}}
|
||||
@@ -302,22 +380,26 @@ function JailConfigDetail({
|
||||
className={styles.codeFont}
|
||||
style={{ flexGrow: 1 }}
|
||||
value={p}
|
||||
readOnly={readOnly}
|
||||
onChange={(_e, d) => {
|
||||
setLogPaths((prev) => prev.map((v, j) => (j === i ? d.value : v)));
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
appearance="subtle"
|
||||
icon={<Dismiss24Regular />}
|
||||
size="small"
|
||||
disabled={deletingPath === p}
|
||||
title="Remove log path"
|
||||
onClick={() => void handleDeleteLogPath(p)}
|
||||
/>
|
||||
{!readOnly && (
|
||||
<Button
|
||||
appearance="subtle"
|
||||
icon={<Dismiss24Regular />}
|
||||
size="small"
|
||||
disabled={deletingPath === p}
|
||||
title="Remove log path"
|
||||
onClick={() => void handleDeleteLogPath(p)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
{/* Add log path inline form */}
|
||||
{/* Add log path inline form — hidden in read-only mode */}
|
||||
{!readOnly && (
|
||||
<div className={styles.regexItem} style={{ marginTop: tokens.spacingVerticalXS }}>
|
||||
<Input
|
||||
className={styles.codeFont}
|
||||
@@ -347,12 +429,14 @@ function JailConfigDetail({
|
||||
{addingLogPath ? "Adding…" : "Add"}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<div style={{ marginTop: tokens.spacingVerticalS }}>
|
||||
<RegexList
|
||||
label="Fail Regex"
|
||||
patterns={failRegex}
|
||||
onChange={setFailRegex}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginTop: tokens.spacingVerticalS }}>
|
||||
@@ -360,6 +444,7 @@ function JailConfigDetail({
|
||||
label="Ignore Regex"
|
||||
patterns={ignoreRegex}
|
||||
onChange={setIgnoreRegex}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</div>
|
||||
{jail.actions.length > 0 && (
|
||||
@@ -387,6 +472,7 @@ function JailConfigDetail({
|
||||
<Switch
|
||||
label="Enable incremental banning"
|
||||
checked={escEnabled}
|
||||
disabled={readOnly}
|
||||
onChange={(_e, d) => {
|
||||
setEscEnabled(d.checked);
|
||||
}}
|
||||
@@ -398,6 +484,7 @@ function JailConfigDetail({
|
||||
<Input
|
||||
type="number"
|
||||
value={escFactor}
|
||||
readOnly={readOnly}
|
||||
onChange={(_e, d) => {
|
||||
setEscFactor(d.value);
|
||||
}}
|
||||
@@ -407,6 +494,7 @@ function JailConfigDetail({
|
||||
<Input
|
||||
type="number"
|
||||
value={escMaxTime}
|
||||
readOnly={readOnly}
|
||||
onChange={(_e, d) => {
|
||||
setEscMaxTime(d.value);
|
||||
}}
|
||||
@@ -416,6 +504,7 @@ function JailConfigDetail({
|
||||
<Input
|
||||
type="number"
|
||||
value={escRndTime}
|
||||
readOnly={readOnly}
|
||||
onChange={(_e, d) => {
|
||||
setEscRndTime(d.value);
|
||||
}}
|
||||
@@ -425,6 +514,7 @@ function JailConfigDetail({
|
||||
<Field label="Formula">
|
||||
<Input
|
||||
value={escFormula}
|
||||
readOnly={readOnly}
|
||||
onChange={(_e, d) => {
|
||||
setEscFormula(d.value);
|
||||
}}
|
||||
@@ -433,6 +523,7 @@ function JailConfigDetail({
|
||||
<Field label="Multipliers (space-separated)">
|
||||
<Input
|
||||
value={escMultipliers}
|
||||
readOnly={readOnly}
|
||||
onChange={(_e, d) => {
|
||||
setEscMultipliers(d.value);
|
||||
}}
|
||||
@@ -441,6 +532,7 @@ function JailConfigDetail({
|
||||
<Switch
|
||||
label="Count repeat offences across all jails"
|
||||
checked={escOverallJails}
|
||||
disabled={readOnly}
|
||||
onChange={(_e, d) => {
|
||||
setEscOverallJails(d.checked);
|
||||
}}
|
||||
@@ -449,15 +541,17 @@ function JailConfigDetail({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: tokens.spacingVerticalS }}>
|
||||
<AutoSaveIndicator
|
||||
status={saveStatus}
|
||||
errorText={saveErrorText}
|
||||
onRetry={retrySave}
|
||||
/>
|
||||
</div>
|
||||
{!readOnly && (
|
||||
<div style={{ marginTop: tokens.spacingVerticalS }}>
|
||||
<AutoSaveIndicator
|
||||
status={saveStatus}
|
||||
errorText={saveErrorText}
|
||||
onRetry={retrySave}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{onDeactivate !== undefined && (
|
||||
{!readOnly && onDeactivate !== undefined && (
|
||||
<div style={{ marginTop: tokens.spacingVerticalM }}>
|
||||
<Button
|
||||
appearance="secondary"
|
||||
@@ -469,14 +563,28 @@ function JailConfigDetail({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Raw Configuration */}
|
||||
<div style={{ marginTop: tokens.spacingVerticalL }}>
|
||||
<RawConfigSection
|
||||
fetchContent={fetchRaw}
|
||||
saveContent={saveRaw}
|
||||
label="Raw Jail Configuration"
|
||||
/>
|
||||
</div>
|
||||
{readOnly && onActivate !== undefined && (
|
||||
<div style={{ marginTop: tokens.spacingVerticalM }}>
|
||||
<Button
|
||||
appearance="primary"
|
||||
icon={<Play24Regular />}
|
||||
onClick={onActivate}
|
||||
>
|
||||
Activate Jail
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Raw Configuration — hidden in read-only (inactive jail) mode */}
|
||||
{!readOnly && (
|
||||
<div style={{ marginTop: tokens.spacingVerticalL }}>
|
||||
<RawConfigSection
|
||||
fetchContent={fetchRaw}
|
||||
saveContent={saveRaw}
|
||||
label="Raw Jail Configuration"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -491,7 +599,12 @@ interface InactiveJailDetailProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* Read-only detail view for an inactive jail, with an Activate button.
|
||||
* Detail view for an inactive jail.
|
||||
*
|
||||
* Maps the parsed config fields to a JailConfig-compatible object and renders
|
||||
* JailConfigDetail in read-only mode, so the UI is identical to the active
|
||||
* jail view but with all fields disabled and an Activate button instead of
|
||||
* a Deactivate button.
|
||||
*
|
||||
* @param props - Component props.
|
||||
* @returns JSX element.
|
||||
@@ -502,13 +615,29 @@ function InactiveJailDetail({
|
||||
}: InactiveJailDetailProps): React.JSX.Element {
|
||||
const styles = useConfigStyles();
|
||||
|
||||
return (
|
||||
<div className={styles.detailPane}>
|
||||
<Text weight="semibold" size={500} block style={{ marginBottom: tokens.spacingVerticalM }}>
|
||||
{jail.name}
|
||||
</Text>
|
||||
const jailConfig = useMemo<JailConfig>(
|
||||
() => ({
|
||||
name: jail.name,
|
||||
ban_time: jail.ban_time_seconds,
|
||||
find_time: jail.find_time_seconds,
|
||||
max_retry: jail.maxretry ?? 5,
|
||||
fail_regex: jail.fail_regex,
|
||||
ignore_regex: jail.ignore_regex,
|
||||
log_paths: jail.logpath,
|
||||
date_pattern: jail.date_pattern,
|
||||
log_encoding: jail.log_encoding,
|
||||
backend: jail.backend,
|
||||
use_dns: jail.use_dns,
|
||||
prefregex: jail.prefregex,
|
||||
actions: jail.actions,
|
||||
bantime_escalation: jail.bantime_escalation,
|
||||
}),
|
||||
[jail],
|
||||
);
|
||||
|
||||
<div className={styles.fieldRow}>
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.fieldRow} style={{ marginBottom: tokens.spacingVerticalS }}>
|
||||
<Field label="Filter">
|
||||
<Input readOnly value={jail.filter || "(none)"} className={styles.codeFont} />
|
||||
</Field>
|
||||
@@ -516,63 +645,15 @@ function InactiveJailDetail({
|
||||
<Input readOnly value={jail.port ?? "(auto)"} />
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<div className={styles.fieldRowThree}>
|
||||
<Field label="Ban time">
|
||||
<Input readOnly value={jail.bantime ?? "(default)"} />
|
||||
</Field>
|
||||
<Field label="Find time">
|
||||
<Input readOnly value={jail.findtime ?? "(default)"} />
|
||||
</Field>
|
||||
<Field label="Max retry">
|
||||
<Input readOnly value={jail.maxretry != null ? String(jail.maxretry) : "(default)"} />
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<Field label="Log path(s)">
|
||||
{jail.logpath.length === 0 ? (
|
||||
<Text size={200} className={styles.infoText}>(none)</Text>
|
||||
) : (
|
||||
<div>
|
||||
{jail.logpath.map((p) => (
|
||||
<Text key={p} block size={200} className={styles.codeFont}>
|
||||
{p}
|
||||
</Text>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
{jail.actions.length > 0 && (
|
||||
<Field label="Actions">
|
||||
<div>
|
||||
{jail.actions.map((a) => (
|
||||
<Badge
|
||||
key={a}
|
||||
appearance="tint"
|
||||
color="informative"
|
||||
style={{ marginRight: tokens.spacingHorizontalXS }}
|
||||
>
|
||||
{a}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</Field>
|
||||
)}
|
||||
|
||||
<Field label="Source file">
|
||||
<Field label="Source file" style={{ marginBottom: tokens.spacingVerticalM }}>
|
||||
<Input readOnly value={jail.source_file} className={styles.codeFont} />
|
||||
</Field>
|
||||
|
||||
<div style={{ marginTop: tokens.spacingVerticalL }}>
|
||||
<Button
|
||||
appearance="primary"
|
||||
icon={<Play24Regular />}
|
||||
onClick={onActivate}
|
||||
>
|
||||
Activate Jail
|
||||
</Button>
|
||||
</div>
|
||||
<JailConfigDetail
|
||||
jail={jailConfig}
|
||||
onSave={async () => { /* read-only — never called */ }}
|
||||
readOnly
|
||||
onActivate={onActivate}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ export interface RegexListProps {
|
||||
patterns: string[];
|
||||
/** Called when the list changes (add, delete, or edit). */
|
||||
onChange: (next: string[]) => void;
|
||||
/** When true, patterns are displayed read-only with no add/delete controls. */
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,6 +31,7 @@ export function RegexList({
|
||||
label,
|
||||
patterns,
|
||||
onChange,
|
||||
readOnly = false,
|
||||
}: RegexListProps): React.JSX.Element {
|
||||
const styles = useConfigStyles();
|
||||
const [newPattern, setNewPattern] = useState("");
|
||||
@@ -64,6 +67,7 @@ export function RegexList({
|
||||
<Input
|
||||
className={styles.regexInput}
|
||||
value={p}
|
||||
readOnly={readOnly}
|
||||
aria-label={`${label} pattern ${String(i + 1)}`}
|
||||
onChange={(_e, d) => {
|
||||
const next = [...patterns];
|
||||
@@ -71,33 +75,37 @@ export function RegexList({
|
||||
onChange(next);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
appearance="subtle"
|
||||
icon={<Dismiss24Regular />}
|
||||
size="small"
|
||||
aria-label={`Remove ${label} pattern ${String(i + 1)}`}
|
||||
onClick={() => {
|
||||
handleDelete(i);
|
||||
}}
|
||||
/>
|
||||
{!readOnly && (
|
||||
<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>
|
||||
{!readOnly && (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -77,6 +77,8 @@ export interface JailConfigUpdate {
|
||||
prefregex?: string | null;
|
||||
date_pattern?: string | null;
|
||||
dns_mode?: string | null;
|
||||
backend?: string | null;
|
||||
log_encoding?: string | null;
|
||||
enabled?: boolean | null;
|
||||
bantime_escalation?: BantimeEscalationUpdate | null;
|
||||
}
|
||||
@@ -498,6 +500,26 @@ export interface InactiveJail {
|
||||
findtime: string | null;
|
||||
/** Number of failures before a ban is issued, or null. */
|
||||
maxretry: number | null;
|
||||
/** Ban duration in seconds, parsed from bantime. */
|
||||
ban_time_seconds: number;
|
||||
/** Failure-counting window in seconds, parsed from findtime. */
|
||||
find_time_seconds: number;
|
||||
/** Log encoding, e.g. ``"auto"`` or ``"utf-8"``. */
|
||||
log_encoding: string;
|
||||
/** Log-monitoring backend. */
|
||||
backend: string;
|
||||
/** Date pattern for log parsing, or null for auto-detect. */
|
||||
date_pattern: string | null;
|
||||
/** DNS resolution mode. */
|
||||
use_dns: string;
|
||||
/** Prefix regex prepended to every failregex. */
|
||||
prefregex: string;
|
||||
/** List of failure regex patterns. */
|
||||
fail_regex: string[];
|
||||
/** List of ignore regex patterns. */
|
||||
ignore_regex: string[];
|
||||
/** Ban-time escalation configuration, or null. */
|
||||
bantime_escalation: BantimeEscalation | null;
|
||||
/** Absolute path to the config file where this jail is defined. */
|
||||
source_file: string;
|
||||
/** Effective ``enabled`` value — always ``false`` for inactive jails. */
|
||||
|
||||
Reference in New Issue
Block a user