Refactor frontend pages and config components into single-component files for Task 13
This commit is contained in:
179
frontend/src/pages/jails/BanUnbanForm.tsx
Normal file
179
frontend/src/pages/jails/BanUnbanForm.tsx
Normal file
@@ -0,0 +1,179 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Field,
|
||||
Input,
|
||||
MessageBar,
|
||||
MessageBarBody,
|
||||
Select,
|
||||
Text,
|
||||
tokens,
|
||||
} from "@fluentui/react-components";
|
||||
import { LockClosedRegular, LockOpenRegular } from "@fluentui/react-icons";
|
||||
import { useCommonSectionStyles } from "../../theme/commonStyles";
|
||||
import { useJailsPageStyles } from "./jailsPageStyles";
|
||||
import { ApiError } from "../../api/client";
|
||||
|
||||
interface BanUnbanFormProps {
|
||||
jailNames: string[];
|
||||
onBan: (jail: string, ip: string) => Promise<void>;
|
||||
onUnban: (ip: string, jail?: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export function BanUnbanForm({ jailNames, onBan, onUnban }: BanUnbanFormProps): React.JSX.Element {
|
||||
const styles = useJailsPageStyles();
|
||||
const sectionStyles = useCommonSectionStyles();
|
||||
const [banIpVal, setBanIpVal] = useState("");
|
||||
const [banJail, setBanJail] = useState("");
|
||||
const [unbanIpVal, setUnbanIpVal] = useState("");
|
||||
const [unbanJail, setUnbanJail] = useState("");
|
||||
const [formError, setFormError] = useState<string | null>(null);
|
||||
const [formSuccess, setFormSuccess] = useState<string | null>(null);
|
||||
|
||||
const handleBan = (): void => {
|
||||
setFormError(null);
|
||||
setFormSuccess(null);
|
||||
if (!banIpVal.trim() || !banJail) {
|
||||
setFormError("Both IP address and jail are required.");
|
||||
return;
|
||||
}
|
||||
onBan(banJail, banIpVal.trim())
|
||||
.then(() => {
|
||||
setFormSuccess(`${banIpVal.trim()} banned in ${banJail}.`);
|
||||
setBanIpVal("");
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
const msg =
|
||||
err instanceof ApiError
|
||||
? `${String(err.status)}: ${err.body}`
|
||||
: err instanceof Error
|
||||
? err.message
|
||||
: String(err);
|
||||
setFormError(msg);
|
||||
});
|
||||
};
|
||||
|
||||
const handleUnban = (fromAllJails: boolean): void => {
|
||||
setFormError(null);
|
||||
setFormSuccess(null);
|
||||
if (!unbanIpVal.trim()) {
|
||||
setFormError("IP address is required.");
|
||||
return;
|
||||
}
|
||||
const jail = fromAllJails ? undefined : unbanJail || undefined;
|
||||
onUnban(unbanIpVal.trim(), jail)
|
||||
.then(() => {
|
||||
const scope = jail ?? "all jails";
|
||||
setFormSuccess(`${unbanIpVal.trim()} unbanned from ${scope}.`);
|
||||
setUnbanIpVal("");
|
||||
setUnbanJail("");
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
const msg =
|
||||
err instanceof ApiError
|
||||
? `${String(err.status)}: ${err.body}`
|
||||
: err instanceof Error
|
||||
? err.message
|
||||
: String(err);
|
||||
setFormError(msg);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={sectionStyles.section}>
|
||||
<div className={sectionStyles.sectionHeader}>
|
||||
<Text as="h2" size={500} weight="semibold">
|
||||
Ban / Unban IP
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{formError && (
|
||||
<MessageBar intent="error">
|
||||
<MessageBarBody>{formError}</MessageBarBody>
|
||||
</MessageBar>
|
||||
)}
|
||||
{formSuccess && (
|
||||
<MessageBar intent="success">
|
||||
<MessageBarBody>{formSuccess}</MessageBarBody>
|
||||
</MessageBar>
|
||||
)}
|
||||
|
||||
<Text size={300} weight="semibold">
|
||||
Ban an IP
|
||||
</Text>
|
||||
<div className={styles.formRow}>
|
||||
<div className={styles.formField}>
|
||||
<Field label="IP Address">
|
||||
<Input
|
||||
placeholder="e.g. 192.168.1.100"
|
||||
value={banIpVal}
|
||||
onChange={(_, d) => {
|
||||
setBanIpVal(d.value);
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
<div className={styles.formField}>
|
||||
<Field label="Jail">
|
||||
<Select
|
||||
value={banJail}
|
||||
onChange={(_, d) => {
|
||||
setBanJail(d.value);
|
||||
}}
|
||||
>
|
||||
<option value="">Select jail…</option>
|
||||
{jailNames.map((n) => (
|
||||
<option key={n} value={n}>
|
||||
{n}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</Field>
|
||||
</div>
|
||||
<Button appearance="primary" icon={<LockClosedRegular />} onClick={handleBan}>
|
||||
Ban
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Text size={300} weight="semibold" style={{ marginTop: tokens.spacingVerticalS }}>
|
||||
Unban an IP
|
||||
</Text>
|
||||
<div className={styles.formRow}>
|
||||
<div className={styles.formField}>
|
||||
<Field label="IP Address">
|
||||
<Input
|
||||
placeholder="e.g. 192.168.1.100"
|
||||
value={unbanIpVal}
|
||||
onChange={(_, d) => {
|
||||
setUnbanIpVal(d.value);
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
<div className={styles.formField}>
|
||||
<Field label="Jail (optional — leave blank for all)">
|
||||
<Select
|
||||
value={unbanJail}
|
||||
onChange={(_, d) => {
|
||||
setUnbanJail(d.value);
|
||||
}}
|
||||
>
|
||||
<option value="">All jails</option>
|
||||
{jailNames.map((n) => (
|
||||
<option key={n} value={n}>
|
||||
{n}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</Field>
|
||||
</div>
|
||||
<Button appearance="secondary" icon={<LockOpenRegular />} onClick={() => { handleUnban(false); }}>
|
||||
Unban
|
||||
</Button>
|
||||
<Button appearance="outline" icon={<LockOpenRegular />} onClick={() => { handleUnban(true); }}>
|
||||
Unban from All Jails
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user