refactoring-backend #3

Merged
lukas.pupkalipinski merged 403 commits from refactoring-backend into main 2026-05-20 20:23:46 +02:00
5 changed files with 110 additions and 50 deletions
Showing only changes of commit 69a5f0ceb1 - Show all commits

View File

@@ -1,23 +1,3 @@
## 11) Logging semantics are inconsistent across backend modules
- Where found:
- [backend/app/services](backend/app/services)
- [backend/app/tasks](backend/app/tasks)
- Why this is needed:
- Uneven level usage reduces observability quality.
- Goal:
- Standardize logging levels and event naming.
- What to do:
- Define logging conventions.
- Align service/task logging with that convention.
- Possible traps and issues:
- Excessive log volume if levels are set too low globally.
- Docs changes needed:
- Add logging policy and examples.
- Doc references:
- [Docs/Backend-Development.md](Docs/Backend-Development.md)
---
## 12) Prop drilling in jail overview page
- Where found:
- [frontend/src/pages/jails/JailOverviewSection.tsx](frontend/src/pages/jails/JailOverviewSection.tsx)

View File

@@ -266,6 +266,49 @@ If a React Context provider is used by **only one page** (e.g., `DashboardFilter
**Example:** `DashboardFilterProvider` manages dashboard time-range and origin filters. It is instantiated only inside `DashboardPage.tsx` and its sub-components. Therefore, it lives in `pages/DashboardFilterProvider.tsx` (or `pages/dashboard/DashboardFilterProvider.tsx` if the page is split into a subdirectory), not in `providers/`.
### State Ownership & Prop Drilling
When a page uses a hook (e.g., `useJails()`) that provides state and actions, and this state needs to be accessed by multiple child components, **eliminate prop drilling by wrapping the page in a context provider**. This reduces coupling, simplifies refactoring, and keeps prop lists focused on component-specific data.
**When to use context instead of props:**
- A page calls a hook that returns both data and actions (e.g., `useJails()` returns `jails`, `loading`, `refresh`, `startJail`, etc.)
- Two or more child components need access to the same hook's state
- The prop chain would be longer than 2 levels deep
**Pattern:**
1. Create a context and provider in the same directory as the page's components (e.g., `pages/jails/JailContext.tsx`)
2. Wrap the page content with the provider, passing the hook's result as the context value
3. Child components use the context hook instead of receiving props
4. Update tests to wrap components with the provider
**Example:**
```tsx
// pages/jails/JailContext.tsx
const JailContext = createContext<JailContextValue | undefined>(undefined);
export function JailProvider({ children }: { children: ReactNode }): JSX.Element {
const jailState = useJails();
return <JailContext.Provider value={jailState}>{children}</JailContext.Provider>;
}
export function useJailContext(): JailContextValue {
const context = useContext(JailContext);
if (!context) throw new Error("useJailContext must be used within JailProvider");
return context;
}
// pages/JailsPage.tsx
export function JailsPage(): JSX.Element {
return (
<JailProvider>
<JailsPageContent /> {/* Uses useJailContext() */}
</JailProvider>
);
}
```
**Important:** Context should only wrap the minimal subtree that needs access. Do not wrap the entire app with page-specific contexts — keep providers scoped to where they're used.
---
## 5. UI Framework — Fluent UI React (v9)

View File

@@ -3,12 +3,12 @@ import { useJailsPageStyles } from "./jails/jailsPageStyles";
import { JailOverviewSection } from "./jails/JailOverviewSection";
import { BanUnbanForm } from "./jails/BanUnbanForm";
import { IpLookupSection } from "./jails/IpLookupSection";
import { JailProvider, useJailContext } from "./jails/JailContext";
import { useActiveBans } from "../hooks/useActiveBans";
import { useJails } from "../hooks/useJailList";
export function JailsPage(): React.JSX.Element {
function JailsPageContent(): React.JSX.Element {
const styles = useJailsPageStyles();
const { jails, total, loading, error, refresh, startJail, stopJail, setIdle, reloadJail, reloadAll } = useJails();
const { jails } = useJailContext();
const { banIp, unbanIp } = useActiveBans();
const jailNames = jails.map((j) => j.name);
@@ -19,18 +19,7 @@ export function JailsPage(): React.JSX.Element {
Jails
</Text>
<JailOverviewSection
jails={jails}
total={total}
loading={loading}
error={error}
refresh={refresh}
startJail={startJail}
stopJail={stopJail}
setIdle={setIdle}
reloadJail={reloadJail}
reloadAll={reloadAll}
/>
<JailOverviewSection />
<BanUnbanForm jailNames={jailNames} onBan={banIp} onUnban={unbanIp} />
@@ -38,3 +27,11 @@ export function JailsPage(): React.JSX.Element {
</div>
);
}
export function JailsPage(): React.JSX.Element {
return (
<JailProvider>
<JailsPageContent />
</JailProvider>
);
}

View File

@@ -0,0 +1,52 @@
/**
* Context for managing jail state and actions across the jails page.
* Eliminates prop drilling by providing jail data and operations to all descendants.
*/
import { createContext, useContext, useCallback, ReactNode } from "react";
import { useJails } from "../../hooks/useJailList";
import type { JailSummary } from "../../types/jail";
interface JailContextValue {
jails: JailSummary[];
total: number;
loading: boolean;
error: string | null;
refresh: () => void;
startJail: (name: string) => Promise<void>;
stopJail: (name: string) => Promise<void>;
setIdle: (name: string, on: boolean) => Promise<void>;
reloadJail: (name: string) => Promise<void>;
reloadAll: () => Promise<void>;
}
const JailContext = createContext<JailContextValue | undefined>(undefined);
interface JailProviderProps {
children: ReactNode;
}
/**
* Provider component for jail state. Wrap JailsPage and its children with this.
*/
export function JailProvider({ children }: JailProviderProps): React.JSX.Element {
const jailState = useJails();
const value: JailContextValue = useCallback(() => ({
...jailState,
}), [jailState])() as JailContextValue;
return <JailContext.Provider value={value}>{children}</JailContext.Provider>;
}
/**
* Hook to access jail state and actions.
* Must be used within a JailProvider.
*/
export function useJailContext(): JailContextValue {
const context = useContext(JailContext);
if (!context) {
throw new Error("useJailContext must be used within a JailProvider");
}
return context;
}

View File

@@ -27,6 +27,7 @@ import {
} from "@fluentui/react-icons";
import { useCommonSectionStyles } from "../../components/commonStyles";
import { useJailsPageStyles } from "./jailsPageStyles";
import { useJailContext } from "./JailContext";
import type { JailSummary } from "../../types/jail";
const useOverviewStyles = makeStyles({
@@ -47,21 +48,8 @@ const useOverviewStyles = makeStyles({
},
});
interface JailOverviewSectionProps {
jails: JailSummary[];
total: number;
loading: boolean;
error: string | null;
refresh: () => void;
startJail: (name: string) => Promise<void>;
stopJail: (name: string) => Promise<void>;
setIdle: (name: string, on: boolean) => Promise<void>;
reloadJail: (name: string) => Promise<void>;
reloadAll: () => Promise<void>;
}
export function JailOverviewSection(props: JailOverviewSectionProps): React.JSX.Element {
const { jails, total, loading, error, refresh, startJail, stopJail, setIdle, reloadJail, reloadAll } = props;
export function JailOverviewSection(): React.JSX.Element {
const { jails, total, loading, error, refresh, startJail, stopJail, setIdle, reloadJail, reloadAll } = useJailContext();
const pageStyles = useJailsPageStyles();
const overviewStyles = useOverviewStyles();
const sectionStyles = useCommonSectionStyles();