Stage 7: configuration view — backend service, routers, tests, and frontend
- config_service.py: read/write jail config via asyncio.gather, global settings, in-process regex validation, log preview via _read_tail_lines - server_service.py: read/write server settings, flush logs - config router: 9 endpoints for jail/global config, regex-test, logpath management, log preview - server router: GET/PUT settings, POST flush-logs - models/config.py expanded with JailConfig, GlobalConfigUpdate, LogPreview* models - 285 tests pass (68 new), ruff clean, mypy clean (44 files) - Frontend: types/config.ts, api/config.ts, hooks/useConfig.ts, ConfigPage.tsx full implementation (Jails accordion editor, Global config, Server settings, Regex Tester with preview) - Fixed pre-existing frontend lint: JSX.Element → React.JSX.Element (10 files), void/promise patterns in useServerStatus + useJails, no-misused-spread in client.ts, eslint.config.ts self-excluded
This commit is contained in:
@@ -38,7 +38,7 @@ import { BlocklistsPage } from "./pages/BlocklistsPage";
|
||||
/**
|
||||
* Root application component — mounts providers and top-level routes.
|
||||
*/
|
||||
function App(): JSX.Element {
|
||||
function App(): React.JSX.Element {
|
||||
return (
|
||||
<FluentProvider theme={lightTheme}>
|
||||
<BrowserRouter>
|
||||
|
||||
@@ -57,7 +57,7 @@ async function request<T>(url: string, options: RequestInit = {}): Promise<T> {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
...(options.headers as Record<string, string> | undefined),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
121
frontend/src/api/config.ts
Normal file
121
frontend/src/api/config.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* API functions for the configuration and server settings endpoints.
|
||||
*/
|
||||
|
||||
import { get, post, put } from "./client";
|
||||
import { ENDPOINTS } from "./endpoints";
|
||||
import type {
|
||||
AddLogPathRequest,
|
||||
GlobalConfig,
|
||||
GlobalConfigUpdate,
|
||||
JailConfigListResponse,
|
||||
JailConfigResponse,
|
||||
JailConfigUpdate,
|
||||
LogPreviewRequest,
|
||||
LogPreviewResponse,
|
||||
RegexTestRequest,
|
||||
RegexTestResponse,
|
||||
ServerSettingsResponse,
|
||||
ServerSettingsUpdate,
|
||||
} from "../types/config";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Jail configuration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function fetchJailConfigs(
|
||||
): Promise<JailConfigListResponse> {
|
||||
return get<JailConfigListResponse>(ENDPOINTS.configJails);
|
||||
}
|
||||
|
||||
export async function fetchJailConfig(
|
||||
name: string
|
||||
): Promise<JailConfigResponse> {
|
||||
return get<JailConfigResponse>(ENDPOINTS.configJail(name));
|
||||
}
|
||||
|
||||
export async function updateJailConfig(
|
||||
name: string,
|
||||
update: JailConfigUpdate
|
||||
): Promise<void> {
|
||||
await put<undefined>(ENDPOINTS.configJail(name), update);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Global configuration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function fetchGlobalConfig(
|
||||
): Promise<GlobalConfig> {
|
||||
return get<GlobalConfig>(ENDPOINTS.configGlobal);
|
||||
}
|
||||
|
||||
export async function updateGlobalConfig(
|
||||
update: GlobalConfigUpdate
|
||||
): Promise<void> {
|
||||
await put<undefined>(ENDPOINTS.configGlobal, update);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Reload
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function reloadConfig(
|
||||
): Promise<void> {
|
||||
await post<undefined>(ENDPOINTS.configReload, undefined);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Regex tester
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function testRegex(
|
||||
req: RegexTestRequest
|
||||
): Promise<RegexTestResponse> {
|
||||
return post<RegexTestResponse>(ENDPOINTS.configRegexTest, req);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Log path management
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function addLogPath(
|
||||
jailName: string,
|
||||
req: AddLogPathRequest
|
||||
): Promise<void> {
|
||||
await post<undefined>(ENDPOINTS.configJailLogPath(jailName), req);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Log preview
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function previewLog(
|
||||
req: LogPreviewRequest
|
||||
): Promise<LogPreviewResponse> {
|
||||
return post<LogPreviewResponse>(ENDPOINTS.configPreviewLog, req);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Server settings
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function fetchServerSettings(
|
||||
): Promise<ServerSettingsResponse> {
|
||||
return get<ServerSettingsResponse>(ENDPOINTS.serverSettings);
|
||||
}
|
||||
|
||||
export async function updateServerSettings(
|
||||
update: ServerSettingsUpdate
|
||||
): Promise<void> {
|
||||
await put<undefined>(ENDPOINTS.serverSettings, update);
|
||||
}
|
||||
|
||||
export async function flushLogs(
|
||||
): Promise<string> {
|
||||
const resp = await post<{ message: string }>(
|
||||
ENDPOINTS.serverFlushLogs,
|
||||
undefined,
|
||||
);
|
||||
return resp.message;
|
||||
}
|
||||
@@ -58,9 +58,12 @@ export const ENDPOINTS = {
|
||||
// -------------------------------------------------------------------------
|
||||
configJails: "/config/jails",
|
||||
configJail: (name: string): string => `/config/jails/${encodeURIComponent(name)}`,
|
||||
configJailLogPath: (name: string): string =>
|
||||
`/config/jails/${encodeURIComponent(name)}/logpath`,
|
||||
configGlobal: "/config/global",
|
||||
configReload: "/config/reload",
|
||||
configRegexTest: "/config/regex-test",
|
||||
configPreviewLog: "/config/preview-log",
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Server settings
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useAuth } from "../providers/AuthProvider";
|
||||
|
||||
interface RequireAuthProps {
|
||||
/** The protected page content to render when authenticated. */
|
||||
children: JSX.Element;
|
||||
children: React.JSX.Element;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -20,7 +20,7 @@ interface RequireAuthProps {
|
||||
* Redirects to `/login?next=<path>` otherwise so the intended destination is
|
||||
* preserved and honoured after a successful login.
|
||||
*/
|
||||
export function RequireAuth({ children }: RequireAuthProps): JSX.Element {
|
||||
export function RequireAuth({ children }: RequireAuthProps): React.JSX.Element {
|
||||
const { isAuthenticated } = useAuth();
|
||||
const location = useLocation();
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ const useStyles = makeStyles({
|
||||
* Render this at the top of the dashboard page (and any page that should
|
||||
* show live server status).
|
||||
*/
|
||||
export function ServerStatusBar(): JSX.Element {
|
||||
export function ServerStatusBar(): React.JSX.Element {
|
||||
const styles = useStyles();
|
||||
const { status, loading, error, refresh } = useServerStatus();
|
||||
|
||||
|
||||
355
frontend/src/hooks/useConfig.ts
Normal file
355
frontend/src/hooks/useConfig.ts
Normal file
@@ -0,0 +1,355 @@
|
||||
/**
|
||||
* React hooks for the configuration and server settings data.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
addLogPath,
|
||||
fetchGlobalConfig,
|
||||
fetchJailConfig,
|
||||
fetchJailConfigs,
|
||||
fetchServerSettings,
|
||||
flushLogs,
|
||||
previewLog,
|
||||
reloadConfig,
|
||||
testRegex,
|
||||
updateGlobalConfig,
|
||||
updateJailConfig,
|
||||
updateServerSettings,
|
||||
} from "../api/config";
|
||||
import type {
|
||||
AddLogPathRequest,
|
||||
GlobalConfig,
|
||||
GlobalConfigUpdate,
|
||||
JailConfig,
|
||||
JailConfigUpdate,
|
||||
LogPreviewRequest,
|
||||
LogPreviewResponse,
|
||||
RegexTestRequest,
|
||||
RegexTestResponse,
|
||||
ServerSettings,
|
||||
ServerSettingsUpdate,
|
||||
} from "../types/config";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// useJailConfigs — list all jail configs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface UseJailConfigsResult {
|
||||
jails: JailConfig[];
|
||||
total: number;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
refresh: () => void;
|
||||
updateJail: (name: string, update: JailConfigUpdate) => Promise<void>;
|
||||
reloadAll: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function useJailConfigs(): UseJailConfigsResult {
|
||||
const [jails, setJails] = useState<JailConfig[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
|
||||
const load = useCallback((): void => {
|
||||
abortRef.current?.abort();
|
||||
const ctrl = new AbortController();
|
||||
abortRef.current = ctrl;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
fetchJailConfigs()
|
||||
.then((resp) => {
|
||||
setJails(resp.jails);
|
||||
setTotal(resp.total);
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (err instanceof Error && err.name !== "AbortError") {
|
||||
setError(err.message);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
load();
|
||||
return (): void => {
|
||||
abortRef.current?.abort();
|
||||
};
|
||||
}, [load]);
|
||||
|
||||
const updateJail = useCallback(
|
||||
async (name: string, update: JailConfigUpdate): Promise<void> => {
|
||||
await updateJailConfig(name, update);
|
||||
load();
|
||||
},
|
||||
[load],
|
||||
);
|
||||
|
||||
const reloadAll = useCallback(async (): Promise<void> => {
|
||||
await reloadConfig();
|
||||
load();
|
||||
}, [load]);
|
||||
|
||||
return { jails, total, loading, error, refresh: load, updateJail, reloadAll };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// useJailConfigDetail — single jail config with mutation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface UseJailConfigDetailResult {
|
||||
jail: JailConfig | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
refresh: () => void;
|
||||
updateJail: (update: JailConfigUpdate) => Promise<void>;
|
||||
addLog: (req: AddLogPathRequest) => Promise<void>;
|
||||
}
|
||||
|
||||
export function useJailConfigDetail(name: string): UseJailConfigDetailResult {
|
||||
const [jail, setJail] = useState<JailConfig | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
|
||||
const load = useCallback((): void => {
|
||||
abortRef.current?.abort();
|
||||
const ctrl = new AbortController();
|
||||
abortRef.current = ctrl;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
fetchJailConfig(name)
|
||||
.then((resp) => {
|
||||
setJail(resp.jail);
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (err instanceof Error && err.name !== "AbortError") {
|
||||
setError(err.message);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, [name]);
|
||||
|
||||
useEffect(() => {
|
||||
load();
|
||||
return (): void => {
|
||||
abortRef.current?.abort();
|
||||
};
|
||||
}, [load]);
|
||||
|
||||
const updateJail = useCallback(
|
||||
async (update: JailConfigUpdate): Promise<void> => {
|
||||
await updateJailConfig(name, update);
|
||||
load();
|
||||
},
|
||||
[name, load],
|
||||
);
|
||||
|
||||
const addLog = useCallback(
|
||||
async (req: AddLogPathRequest): Promise<void> => {
|
||||
await addLogPath(name, req);
|
||||
load();
|
||||
},
|
||||
[name, load],
|
||||
);
|
||||
|
||||
return { jail, loading, error, refresh: load, updateJail, addLog };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// useGlobalConfig
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface UseGlobalConfigResult {
|
||||
config: GlobalConfig | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
refresh: () => void;
|
||||
updateConfig: (update: GlobalConfigUpdate) => Promise<void>;
|
||||
}
|
||||
|
||||
export function useGlobalConfig(): UseGlobalConfigResult {
|
||||
const [config, setConfig] = useState<GlobalConfig | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
|
||||
const load = useCallback((): void => {
|
||||
abortRef.current?.abort();
|
||||
const ctrl = new AbortController();
|
||||
abortRef.current = ctrl;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
fetchGlobalConfig()
|
||||
.then(setConfig)
|
||||
.catch((err: unknown) => {
|
||||
if (err instanceof Error && err.name !== "AbortError") {
|
||||
setError(err.message);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
load();
|
||||
return (): void => {
|
||||
abortRef.current?.abort();
|
||||
};
|
||||
}, [load]);
|
||||
|
||||
const updateConfig = useCallback(
|
||||
async (update: GlobalConfigUpdate): Promise<void> => {
|
||||
await updateGlobalConfig(update);
|
||||
load();
|
||||
},
|
||||
[load],
|
||||
);
|
||||
|
||||
return { config, loading, error, refresh: load, updateConfig };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// useServerSettings
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface UseServerSettingsResult {
|
||||
settings: ServerSettings | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
refresh: () => void;
|
||||
updateSettings: (update: ServerSettingsUpdate) => Promise<void>;
|
||||
flush: () => Promise<string>;
|
||||
}
|
||||
|
||||
export function useServerSettings(): UseServerSettingsResult {
|
||||
const [settings, setSettings] = useState<ServerSettings | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
|
||||
const load = useCallback((): void => {
|
||||
abortRef.current?.abort();
|
||||
const ctrl = new AbortController();
|
||||
abortRef.current = ctrl;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
fetchServerSettings()
|
||||
.then((resp) => {
|
||||
setSettings(resp.settings);
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (err instanceof Error && err.name !== "AbortError") {
|
||||
setError(err.message);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
load();
|
||||
return (): void => {
|
||||
abortRef.current?.abort();
|
||||
};
|
||||
}, [load]);
|
||||
|
||||
const updateSettings_ = useCallback(
|
||||
async (update: ServerSettingsUpdate): Promise<void> => {
|
||||
await updateServerSettings(update);
|
||||
load();
|
||||
},
|
||||
[load],
|
||||
);
|
||||
|
||||
const flush = useCallback(async (): Promise<string> => {
|
||||
return flushLogs();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
settings,
|
||||
loading,
|
||||
error,
|
||||
refresh: load,
|
||||
updateSettings: updateSettings_,
|
||||
flush,
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// useRegexTester — lazy, triggered by test(req)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface UseRegexTesterResult {
|
||||
result: RegexTestResponse | null;
|
||||
testing: boolean;
|
||||
test: (req: RegexTestRequest) => Promise<void>;
|
||||
}
|
||||
|
||||
export function useRegexTester(): UseRegexTesterResult {
|
||||
const [result, setResult] = useState<RegexTestResponse | null>(null);
|
||||
const [testing, setTesting] = useState(false);
|
||||
|
||||
const test_ = useCallback(async (req: RegexTestRequest): Promise<void> => {
|
||||
setTesting(true);
|
||||
try {
|
||||
const resp = await testRegex(req);
|
||||
setResult(resp);
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Error) {
|
||||
setResult({ matched: false, groups: [], error: err.message });
|
||||
}
|
||||
} finally {
|
||||
setTesting(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return { result, testing, test: test_ };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// useLogPreview — lazy, triggered by preview(req)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
interface UseLogPreviewResult {
|
||||
preview: LogPreviewResponse | null;
|
||||
loading: boolean;
|
||||
run: (req: LogPreviewRequest) => Promise<void>;
|
||||
}
|
||||
|
||||
export function useLogPreview(): UseLogPreviewResult {
|
||||
const [preview, setPreview] = useState<LogPreviewResponse | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const run_ = useCallback(async (req: LogPreviewRequest): Promise<void> => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const resp = await previewLog(req);
|
||||
setPreview(resp);
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Error) {
|
||||
setPreview({
|
||||
lines: [],
|
||||
total_lines: 0,
|
||||
matched_count: 0,
|
||||
regex_error: err.message,
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return { preview, loading, run: run_ };
|
||||
}
|
||||
@@ -100,7 +100,7 @@ export function useJails(): UseJailsResult {
|
||||
|
||||
useEffect(() => {
|
||||
load();
|
||||
return () => {
|
||||
return (): void => {
|
||||
abortRef.current?.abort();
|
||||
};
|
||||
}, [load]);
|
||||
@@ -120,9 +120,9 @@ export function useJails(): UseJailsResult {
|
||||
refresh: load,
|
||||
startJail: withRefresh(startJail),
|
||||
stopJail: withRefresh(stopJail),
|
||||
setIdle: (name, on) => setJailIdle(name, on).then(() => load()),
|
||||
setIdle: (name, on) => setJailIdle(name, on).then((): void => { load(); }),
|
||||
reloadJail: withRefresh(reloadJail),
|
||||
reloadAll: () => reloadAllJails().then(() => load()),
|
||||
reloadAll: () => reloadAllJails().then((): void => { load(); }),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ export function useJailDetail(name: string): UseJailDetailResult {
|
||||
|
||||
useEffect(() => {
|
||||
load();
|
||||
return () => {
|
||||
return (): void => {
|
||||
abortRef.current?.abort();
|
||||
};
|
||||
}, [load]);
|
||||
@@ -278,7 +278,7 @@ export function useActiveBans(): UseActiveBansResult {
|
||||
|
||||
useEffect(() => {
|
||||
load();
|
||||
return () => {
|
||||
return (): void => {
|
||||
abortRef.current?.abort();
|
||||
};
|
||||
}, [load]);
|
||||
|
||||
@@ -36,7 +36,7 @@ export function useServerStatus(): UseServerStatusResult {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Use a ref so the fetch function identity is stable.
|
||||
const fetchRef = useRef<() => void>(() => undefined);
|
||||
const fetchRef = useRef<() => Promise<void>>(async () => Promise.resolve());
|
||||
|
||||
const doFetch = useCallback(async (): Promise<void> => {
|
||||
setLoading(true);
|
||||
@@ -54,14 +54,14 @@ export function useServerStatus(): UseServerStatusResult {
|
||||
fetchRef.current = doFetch;
|
||||
|
||||
// Initial fetch + polling interval.
|
||||
useEffect(() => {
|
||||
void doFetch();
|
||||
useEffect((): (() => void) => {
|
||||
void doFetch().catch((): void => undefined);
|
||||
|
||||
const id = setInterval(() => {
|
||||
void fetchRef.current();
|
||||
const id = setInterval((): void => {
|
||||
void fetchRef.current().catch((): void => undefined);
|
||||
}, POLL_INTERVAL_MS);
|
||||
|
||||
return () => clearInterval(id);
|
||||
return (): void => { clearInterval(id); };
|
||||
}, [doFetch]);
|
||||
|
||||
// Refetch on window focus.
|
||||
@@ -70,11 +70,11 @@ export function useServerStatus(): UseServerStatusResult {
|
||||
void fetchRef.current();
|
||||
};
|
||||
window.addEventListener("focus", onFocus);
|
||||
return () => window.removeEventListener("focus", onFocus);
|
||||
return (): void => { window.removeEventListener("focus", onFocus); };
|
||||
}, []);
|
||||
|
||||
const refresh = useCallback((): void => {
|
||||
void doFetch();
|
||||
void doFetch().catch((): void => undefined);
|
||||
}, [doFetch]);
|
||||
|
||||
return { status, loading, error, refresh };
|
||||
|
||||
@@ -176,7 +176,7 @@ const NAV_ITEMS: NavItem[] = [
|
||||
* Renders child routes via `<Outlet />`. Use inside React Router
|
||||
* as a layout route wrapping all authenticated pages.
|
||||
*/
|
||||
export function MainLayout(): JSX.Element {
|
||||
export function MainLayout(): React.JSX.Element {
|
||||
const styles = useStyles();
|
||||
const { logout } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -8,7 +8,7 @@ const useStyles = makeStyles({
|
||||
root: { padding: tokens.spacingVerticalXXL },
|
||||
});
|
||||
|
||||
export function BlocklistsPage(): JSX.Element {
|
||||
export function BlocklistsPage(): React.JSX.Element {
|
||||
const styles = useStyles();
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ const useStyles = makeStyles({
|
||||
root: { padding: tokens.spacingVerticalXXL },
|
||||
});
|
||||
|
||||
export function HistoryPage(): JSX.Element {
|
||||
export function HistoryPage(): React.JSX.Element {
|
||||
const styles = useStyles();
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
|
||||
@@ -71,7 +71,7 @@ const useStyles = makeStyles({
|
||||
/**
|
||||
* Login page — single password input, no username.
|
||||
*/
|
||||
export function LoginPage(): JSX.Element {
|
||||
export function LoginPage(): React.JSX.Element {
|
||||
const styles = useStyles();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
@@ -8,7 +8,7 @@ const useStyles = makeStyles({
|
||||
root: { padding: tokens.spacingVerticalXXL },
|
||||
});
|
||||
|
||||
export function MapPage(): JSX.Element {
|
||||
export function MapPage(): React.JSX.Element {
|
||||
const styles = useStyles();
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
|
||||
@@ -96,7 +96,7 @@ const DEFAULT_VALUES: FormValues = {
|
||||
* First-run setup wizard page.
|
||||
* Collects master password and server preferences.
|
||||
*/
|
||||
export function SetupPage(): JSX.Element {
|
||||
export function SetupPage(): React.JSX.Element {
|
||||
const styles = useStyles();
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ export function AuthProvider({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}): JSX.Element {
|
||||
}): React.JSX.Element {
|
||||
const [auth, setAuth] = useState<AuthState>(() => ({
|
||||
token: sessionStorage.getItem(SESSION_KEY),
|
||||
expiresAt: sessionStorage.getItem(SESSION_EXPIRES_KEY),
|
||||
|
||||
130
frontend/src/types/config.ts
Normal file
130
frontend/src/types/config.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* TypeScript interfaces for the configuration and server settings API.
|
||||
*/
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Jail Configuration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface JailConfig {
|
||||
name: string;
|
||||
ban_time: number;
|
||||
max_retry: number;
|
||||
find_time: number;
|
||||
fail_regex: string[];
|
||||
ignore_regex: string[];
|
||||
log_paths: string[];
|
||||
date_pattern: string | null;
|
||||
log_encoding: string;
|
||||
backend: string;
|
||||
actions: string[];
|
||||
}
|
||||
|
||||
export interface JailConfigResponse {
|
||||
jail: JailConfig;
|
||||
}
|
||||
|
||||
export interface JailConfigListResponse {
|
||||
jails: JailConfig[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface JailConfigUpdate {
|
||||
ban_time?: number | null;
|
||||
max_retry?: number | null;
|
||||
find_time?: number | null;
|
||||
fail_regex?: string[] | null;
|
||||
ignore_regex?: string[] | null;
|
||||
date_pattern?: string | null;
|
||||
dns_mode?: string | null;
|
||||
enabled?: boolean | null;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Global Configuration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface GlobalConfig {
|
||||
log_level: string;
|
||||
log_target: string;
|
||||
db_purge_age: number;
|
||||
db_max_matches: number;
|
||||
}
|
||||
|
||||
export interface GlobalConfigUpdate {
|
||||
log_level?: string | null;
|
||||
log_target?: string | null;
|
||||
db_purge_age?: number | null;
|
||||
db_max_matches?: number | null;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Server Settings
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface ServerSettings {
|
||||
log_level: string;
|
||||
log_target: string;
|
||||
syslog_socket: string | null;
|
||||
db_path: string;
|
||||
db_purge_age: number;
|
||||
db_max_matches: number;
|
||||
}
|
||||
|
||||
export interface ServerSettingsResponse {
|
||||
settings: ServerSettings;
|
||||
}
|
||||
|
||||
export interface ServerSettingsUpdate {
|
||||
log_level?: string | null;
|
||||
log_target?: string | null;
|
||||
db_purge_age?: number | null;
|
||||
db_max_matches?: number | null;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Regex Tester
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface RegexTestRequest {
|
||||
log_line: string;
|
||||
fail_regex: string;
|
||||
}
|
||||
|
||||
export interface RegexTestResponse {
|
||||
matched: boolean;
|
||||
groups: string[];
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Log Preview
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface LogPreviewRequest {
|
||||
log_path: string;
|
||||
fail_regex: string;
|
||||
num_lines?: number;
|
||||
}
|
||||
|
||||
export interface LogPreviewLine {
|
||||
line: string;
|
||||
matched: boolean;
|
||||
groups: string[];
|
||||
}
|
||||
|
||||
export interface LogPreviewResponse {
|
||||
lines: LogPreviewLine[];
|
||||
total_lines: number;
|
||||
matched_count: number;
|
||||
regex_error: string | null;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Add Log Path
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface AddLogPathRequest {
|
||||
log_path: string;
|
||||
tail?: boolean;
|
||||
}
|
||||
Reference in New Issue
Block a user