190 lines
5.5 KiB
TypeScript
190 lines
5.5 KiB
TypeScript
import { useState } from "react";
|
|
import {
|
|
Button,
|
|
Field,
|
|
Input,
|
|
MessageBar,
|
|
MessageBarBody,
|
|
Select,
|
|
Text,
|
|
} from "@fluentui/react-components";
|
|
import { LockClosedRegular, LockOpenRegular } from "@fluentui/react-icons";
|
|
import { useCommonSectionStyles } from "../../components/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 [isBanning, setIsBanning] = useState(false);
|
|
const [isUnbanning, setIsUnbanning] = useState(false);
|
|
|
|
const formatErrorMessage = (err: unknown): string =>
|
|
err instanceof ApiError
|
|
? `${String(err.status)}: ${err.body}`
|
|
: err instanceof Error
|
|
? err.message
|
|
: String(err);
|
|
|
|
const handleBan = async (): Promise<void> => {
|
|
setFormError(null);
|
|
setFormSuccess(null);
|
|
if (!banIpVal.trim() || !banJail) {
|
|
setFormError("Both IP address and jail are required.");
|
|
return;
|
|
}
|
|
|
|
setIsBanning(true);
|
|
try {
|
|
const ip = banIpVal.trim();
|
|
await onBan(banJail, ip);
|
|
setFormSuccess(`${ip} banned in ${banJail}.`);
|
|
setBanIpVal("");
|
|
} catch (err: unknown) {
|
|
setFormError(formatErrorMessage(err));
|
|
} finally {
|
|
setIsBanning(false);
|
|
}
|
|
};
|
|
|
|
const handleUnban = async (fromAllJails: boolean): Promise<void> => {
|
|
setFormError(null);
|
|
setFormSuccess(null);
|
|
if (!unbanIpVal.trim()) {
|
|
setFormError("IP address is required.");
|
|
return;
|
|
}
|
|
|
|
setIsUnbanning(true);
|
|
try {
|
|
const ip = unbanIpVal.trim();
|
|
const jail = fromAllJails ? undefined : unbanJail || undefined;
|
|
await onUnban(ip, jail);
|
|
const scope = jail ?? "all jails";
|
|
setFormSuccess(`${ip} unbanned from ${scope}.`);
|
|
setUnbanIpVal("");
|
|
setUnbanJail("");
|
|
} catch (err: unknown) {
|
|
setFormError(formatErrorMessage(err));
|
|
} finally {
|
|
setIsUnbanning(false);
|
|
}
|
|
};
|
|
|
|
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={() => {
|
|
void handleBan();
|
|
}} disabled={isBanning}>
|
|
Ban
|
|
</Button>
|
|
</div>
|
|
|
|
<Text size={300} weight="semibold" className={styles.sectionHeadingSpacing}>
|
|
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={() => {
|
|
void handleUnban(false);
|
|
}} disabled={isUnbanning}>
|
|
Unban
|
|
</Button>
|
|
<Button appearance="outline" icon={<LockOpenRegular />} onClick={() => {
|
|
void handleUnban(true);
|
|
}} disabled={isUnbanning}>
|
|
Unban from All Jails
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|