diff --git a/Docs/Tasks.md b/Docs/Tasks.md index 9f86cca..5e2cf75 100644 --- a/Docs/Tasks.md +++ b/Docs/Tasks.md @@ -7,3 +7,38 @@ Reference: `Docs/Refactoring.md` for full analysis of each issue. --- ## Open Issues + +### 1. History Screen — Move Jail & IP Address Filters Into the Time Range Bar + +**Goal:** Unify all History-page filters into a single bordered bar so that Jail and IP Address sit inside the same card/border as Time Range and Filter, separated by vertical dividers. + +**Current state:** +- `DashboardFilterBar` (`frontend/src/components/DashboardFilterBar.tsx`) renders a single bordered card (`cardStyles.card`) that contains two groups — **Time Range** (toggle buttons) and **Filter** (origin toggle buttons) — separated by a vertical ``. +- In `HistoryPage` (`frontend/src/pages/HistoryPage.tsx`, lines 476–510) the Jail `` and IP Address `` are rendered **outside** that bar as separate cards, each wrapped in their own `cardStyles.card` div, laid out horizontally via `styles.filterRow` (flexbox row with gap). + +**Desired state:** +- The Jail and IP Address inputs must move **inside** the `DashboardFilterBar` card border (or the equivalent combined container) so the entire filter strip looks like one cohesive section. +- Each new group (Jail, IP Address) is separated from its neighbor by a vertical divider (`|`), using the same `` + `styles.divider` pattern already used between Time Range and Filter. +- Inside each group the label text ("Jail", "IP Address") must appear **to the left** of its input field (i.e. `flexDirection: "row"` with `alignItems: "center"`, not above it). This matches the existing group style where the title text sits to the left of the toolbar buttons. +- The visual order inside the bar is: **Time Range** | **Filter** | **Jail** | **IP Address**. + +**Files to change:** + +1. **`frontend/src/components/DashboardFilterBar.tsx`** + - Accept two new optional props (e.g. `jailSlot?: React.ReactNode` and `ipSlot?: React.ReactNode`, or pass the value+onChange pairs directly). Keep the component reusable — the Dashboard page uses the same component but does not need the Jail/IP inputs, so these slots must be optional. + - After the existing Filter group, conditionally render a `
` + `` followed by a new group for Jail, and repeat for IP Address. + - Each new group should follow the existing `styles.group` layout: a row with the label `` on the left and the `` on the right, separated by `gap: tokens.spacingHorizontalM`. + +2. **`frontend/src/pages/HistoryPage.tsx`** + - Remove the two standalone Jail and IP Address `
` cards (currently wrapped in `styles.filterLabel` + `cardStyles.card`). + - Instead, pass the Jail and IP Address controls into `` via the new props/slots. + - The `styles.filterLabel` style can be removed if no longer used elsewhere. + +**Acceptance criteria:** +- All four filter groups (Time Range, Filter, Jail, IP Address) render inside a single bordered bar. +- Each group is separated by a vertical divider identical to the existing one between Time Range and Filter. +- The labels "Jail" and "IP Address" sit to the **left** of their respective input fields (horizontal layout), not above them. +- The Dashboard page's usage of `DashboardFilterBar` is unaffected (no Jail/IP inputs shown there). +- Existing filter functionality (debounced input, query params, pagination reset) remains unchanged. + +Status: completed diff --git a/frontend/src/components/DashboardFilterBar.tsx b/frontend/src/components/DashboardFilterBar.tsx index 92e663b..b2fbcf4 100644 --- a/frontend/src/components/DashboardFilterBar.tsx +++ b/frontend/src/components/DashboardFilterBar.tsx @@ -8,6 +8,7 @@ import { Divider, + Input, Text, ToggleButton, Toolbar, @@ -35,6 +36,14 @@ export interface DashboardFilterBarProps { originFilter: BanOriginFilter; /** Called when the user selects a different origin filter. */ onOriginFilterChange: (value: BanOriginFilter) => void; + /** Jail filter value (optional). */ + jail?: string; + /** Called when the jail filter text changes (optional). */ + onJailChange?: (value: string) => void; + /** IP address filter value (optional). */ + ip?: string; + /** Called when the IP address filter text changes (optional). */ + onIpChange?: (value: string) => void; } // --------------------------------------------------------------------------- @@ -92,6 +101,10 @@ export function DashboardFilterBar({ onTimeRangeChange, originFilter, onOriginFilterChange, + jail, + onJailChange, + ip, + onIpChange, }: DashboardFilterBarProps): React.JSX.Element { const styles = useStyles(); const cardStyles = useCardStyles(); @@ -146,6 +159,48 @@ export function DashboardFilterBar({ ))}
+ + {onJailChange && ( + <> +
+ +
+
+ + Jail + + { + onJailChange(data.value); + }} + /> +
+ + )} + + {onIpChange && ( + <> +
+ +
+
+ + IP Address + + { + onIpChange(data.value); + }} + /> +
+ + )}
); } diff --git a/frontend/src/components/__tests__/DashboardFilterBar.test.tsx b/frontend/src/components/__tests__/DashboardFilterBar.test.tsx index 512c342..5c49806 100644 --- a/frontend/src/components/__tests__/DashboardFilterBar.test.tsx +++ b/frontend/src/components/__tests__/DashboardFilterBar.test.tsx @@ -125,4 +125,47 @@ describe("DashboardFilterBar", () => { expect(onTimeRangeChange).toHaveBeenCalledOnce(); expect(onTimeRangeChange).toHaveBeenCalledWith("24h"); }); + + it("renders jail and ip input controls when provided", async () => { + const onJailChange = vi.fn(); + const onIpChange = vi.fn(); + + render( + + + , + ); + + expect(screen.getByText(/Jail/i)).toBeInTheDocument(); + expect(screen.getByText(/IP Address/i)).toBeInTheDocument(); + expect(screen.getByPlaceholderText(/e.g. sshd/i)).toBeInTheDocument(); + expect(screen.getByPlaceholderText(/e.g. 192.168/i)).toBeInTheDocument(); + + const jailInput = screen.getByPlaceholderText(/e.g. sshd/i); + const ipInput = screen.getByPlaceholderText(/e.g. 192.168/i); + const user = userEvent.setup(); + + await user.clear(jailInput); + await user.type(jailInput, "x"); + expect(onJailChange).toHaveBeenLastCalledWith("x"); + + await user.clear(ipInput); + await user.type(ipInput, "1"); + expect(onIpChange).toHaveBeenLastCalledWith("1"); + }); + + it("does not render jail or ip inputs when handlers are missing", () => { + renderBar(); + expect(screen.queryByText(/Jail/i)).toBeNull(); + expect(screen.queryByText(/IP Address/i)).toBeNull(); + }); }); diff --git a/frontend/src/pages/HistoryPage.tsx b/frontend/src/pages/HistoryPage.tsx index 3efc1c6..debdb85 100644 --- a/frontend/src/pages/HistoryPage.tsx +++ b/frontend/src/pages/HistoryPage.tsx @@ -16,7 +16,6 @@ import { DataGridHeader, DataGridHeaderCell, DataGridRow, - Input, MessageBar, MessageBarBody, Spinner, @@ -82,11 +81,6 @@ const useStyles = makeStyles({ gap: tokens.spacingHorizontalM, flexWrap: "wrap", }, - filterLabel: { - display: "flex", - flexDirection: "column", - gap: tokens.spacingVerticalXS, - }, tableWrapper: { overflow: "auto", borderRadius: tokens.borderRadiusMedium, @@ -390,7 +384,6 @@ function IpDetailView({ ip, onBack }: IpDetailViewProps): React.JSX.Element { export function HistoryPage(): React.JSX.Element { const styles = useStyles(); - const cardStyles = useCardStyles(); // Filter state const [range, setRange] = useState("24h"); @@ -483,32 +476,15 @@ export function HistoryPage(): React.JSX.Element { onOriginFilterChange={(value) => { setOriginFilter(value); }} + jail={jailFilter} + onJailChange={(value) => { + setJailFilter(value); + }} + ip={ipFilter} + onIpChange={(value) => { + setIpFilter(value); + }} /> - -
- Jail - { - setJailFilter(data.value); - }} - size="small" - /> -
- -
- IP Address - { - setIpFilter(data.value); - }} - size="small" - /> -
- {/* ---------------------------------------------------------------- */}