refactoring-backend #3
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
52
frontend/src/pages/jails/JailContext.tsx
Normal file
52
frontend/src/pages/jails/JailContext.tsx
Normal 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;
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user