- Implement ban model, service, and router endpoints in backend - Add ban table component and dashboard integration in frontend - Update ban-related types and API endpoints - Add comprehensive tests for ban service and dashboard router - Update documentation (Features, Tasks, Architecture, Web-Design) - Clean up old fail2ban configuration files - Update Makefile with new commands
23 KiB
Frontend Design — Rules & Guidelines
Rules and conventions every designer must follow when working on BanGUI. This document defines the visual language, component usage, and design principles for the entire application. Read this before creating your first mockup or writing your first style.
Design system: Microsoft Fluent UI
1. Design Philosophy
- Clarity over decoration. Every visual element must serve a purpose. If it does not help the user read, navigate, or act — remove it.
- Consistency over creativity. Reuse existing Fluent UI components and patterns before inventing new ones. A familiar interface lowers cognitive load.
- Content first. BanGUI is a data-heavy monitoring tool. Design around the data — tables, charts, status indicators — not around ornamental chrome.
- Quiet until necessary. The default state of the UI should feel calm and neutral. Reserve colour, motion, and elevation for moments that genuinely need attention (errors, warnings, active bans).
- Accessible by default. Every design decision must pass WCAG 2.1 AA. Colour alone must never be the only way to convey information.
2. Theming & Colour
All colour decisions go through the Fluent UI theme system. Never hard-code hex values in components.
Theme Slots
Use semantic colour slots from ISemanticColors wherever possible. Semantic slots describe intent rather than appearance, which means the UI adapts automatically when the theme changes.
| Intent | Semantic slot | Example usage |
|---|---|---|
| Primary action | themePrimary |
Primary buttons, active nav items, links |
| Danger / error | errorText, errorIcon |
Ban warnings, failed connections, validation errors |
| Success | successIcon (extend with custom slot) |
Successful unban confirmation, server online status |
| Warning | Shared colour yellow / yellowDark |
Blocklist import warnings, approaching limits |
| Neutral text | neutralPrimary |
Body copy, table cell text |
| Secondary text | neutralSecondary |
Metadata, timestamps, captions |
| Disabled | disabledText, disabledBackground |
Inactive controls, unavailable jails |
| Surface | bodyBackground, bodyStandoutBackground |
Page canvas, sidebar, card backgrounds |
Custom Theme
BanGUI uses a single custom theme generated with the Fluent UI Theme Designer. The theme is defined once in code and consumed everywhere via ThemeProvider.
- The primary colour must have a contrast ratio of at least 4.5 : 1 against
whitefor text and 3 : 1 for large text and UI elements. - Provide a dark theme variant alongside the default light theme. Both must share the same semantic slot names — only the palette values differ.
- Never reference Fluent UI palette slots (
themeDarker,neutralLight, etc.) directly in components. Always go through semantic slots so theme switching works seamlessly.
Colour Rules
- Never use inline hex or RGB values. Always reference theme tokens (
theme.palette.*ortheme.semanticColors.*). - Use the 9-step theme colour ramp (
themeLighterAltthroughthemeDarker) for subtle tints and shades within the primary hue. - Use the 14-step neutral ramp (
whitethroughblack) for backgrounds, borders, and text hierarchy. - Reserve the shared accent colours (red, yellow, green, blue) strictly for semantic meaning: errors, warnings, success, informational. Never use them decoratively.
- When two adjacent regions need separation, prefer a 1 px
neutralLightborder or a subtle background shift (bodyStandoutBackground) over a heavy line.
3. Typography
Use the Fluent UI type ramp exclusively. Do not introduce custom font sizes.
Type Ramp
| Token | Size | Weight | Usage in BanGUI |
|---|---|---|---|
FontSizes.size28 |
28 px | Semibold (600) | Page titles — "Ban Overview", "Jail Management" |
FontSizes.size20 |
20 px | Semibold (600) | Section headers — "Ban List", "Server Settings" |
FontSizes.size16 |
16 px | Semibold (600) | Card titles, panel headers, sub-section labels |
FontSizes.size14 |
14 px | Regular (400) | Body text, table cell content, form labels, buttons |
FontSizes.size12 |
12 px | Regular (400) | Metadata: timestamps, IP geolocation tags, ban counts, tooltips |
FontSizes.size10 |
10 px | Regular (400) | Badges, disclaimer text, chart axis labels (use sparingly) |
Typography Rules
- Font family — Use the Fluent default (
Segoe UI, with the standard fallback stack). Never override the font family unless a monospace context demands it (log output, regex patterns, IP addresses — useConsolas, "Courier New", monospacefor those). - Weight — Only two weights in regular UI: Regular (400) for body text and Semibold (600) for headings and emphasis. Use Bold (700) only in exceptional data visualisation contexts.
- Line height — Follow the recommended line heights from the Fluent type ramp (e.g., 14 px text → 20 px line-height, 20 px text → 28 px line-height).
- Text colour hierarchy — Use
neutralPrimaryfor primary text,neutralSecondaryfor secondary/supporting text, andneutralTertiaryonly for placeholder text. - Alignment — Left-align all text by default. Centre-align only short labels inside cards or stat badges. Never right-align body text.
- Truncation — Long text (IP addresses, log paths, regex patterns) must truncate with an ellipsis and show the full content in a tooltip on hover.
- Monospace contexts — IP addresses, regex patterns, log paths, code snippets, and jail filter expressions must always render in monospace for readability.
4. Layout & Spacing
Grid
- Use a 12-column responsive grid. In Fluent UI React, prefer CSS Grid or
Stackfor layout rather than Fabric Core grid classes. - The main content area sits next to a fixed-width side navigation (240 px collapsed to 48 px on small screens).
- Maximum content width: 1440 px, centred on ultra-wide monitors.
Breakpoints
Follow the Fluent UI breakpoint scale:
| Name | Range | Layout behaviour |
|---|---|---|
| Small | 320 – 479 px | Single column, collapsed nav, stacked cards |
| Medium | 480 – 639 px | Single column, optional side nav overlay |
| Large | 640 – 1023 px | Two columns, side nav visible |
| Extra large | 1024 – 1365 px | Full layout, side nav + main + optional aside |
| XX-large | 1366 – 1919 px | Comfortable full layout |
| XXX-large | 1920 px + | Max-width content, extra breathing room |
Spacing Scale
Use the Fluent 4 px base unit for all spacing. Common values:
| Token | Value | Usage |
|---|---|---|
s2 |
4 px | Inline spacing between icon and label |
s1 |
8 px | Padding inside compact elements (badges, chips) |
m |
16 px | Standard padding inside cards and panels |
l1 |
20 px | Gap between table rows / list items |
l2 |
32 px | Margin between sections on a page |
xl |
48 px | Top margin for page content below the header |
Spacing Rules
- Never use arbitrary pixel values. Always snap to the 4 px grid (4, 8, 12, 16, 20, 24, 32, 40, 48…).
- Padding inside a card or panel: 16 px on all sides.
- Gap between cards in a grid: 16 px.
- Gap between a page title and the first content block: 24 px.
- Table cell padding: 12 px horizontal, 8 px vertical.
5. Elevation & Depth
Use Fluent UI depth levels to communicate layering. Do not create custom box-shadow values.
| Level | Token | Usage in BanGUI |
|---|---|---|
| Depth 4 | Depths.depth4 |
Cards, grid tiles, stat summaries on the dashboard |
| Depth 8 | Depths.depth8 |
Command bars, dropdown menus, filter popups |
| Depth 16 | Depths.depth16 |
Tooltips, hover cards (IP info popup), teaching callouts |
| Depth 64 | Depths.depth64 |
Modal dialogs (ban/unban confirmation), side panels |
Elevation Rules
- A surface's depth must match its semantic importance. A card sits at depth 4; a dialog demanding user action sits at depth 64.
- The page canvas has zero elevation — it is the baseline.
- Only one modal level may be visible at a time. Never stack dialogs.
- When a dropdown or popup opens, the rest of the interface should receive an overlay scrim (
rgba(0, 0, 0, 0.4)) only for depth-64 surfaces (modals). Lighter popups (depth 8–16) dismiss on outside click without a scrim.
6. Motion & Animation
All animations follow Fluent motion principles. Motion should feel natural, purposeful, and quick.
Timing
| Duration | Token | Usage |
|---|---|---|
| 100 ms | MotionDurations.duration1 |
Micro-interactions: button press, checkbox toggle |
| 200 ms | MotionDurations.duration2 |
Fade in/out of tooltips, small element transitions |
| 300 ms | MotionDurations.duration3 |
Panel slide-in, page transitions, list reordering |
| 400 ms | MotionDurations.duration4 |
Large surface entrance (full-screen modals, first-load hero) |
Easing
| Curve | Token | When to use |
|---|---|---|
| Decelerate | MotionTimings.decelerate |
Elements entering the view (slide in, fade in) |
| Accelerate | MotionTimings.accelerate |
Elements leaving the view (slide out, fade out) |
| Standard | MotionTimings.standard |
Elements that reposition within the view |
| Linear | MotionTimings.linear |
Opacity-only transitions (fade out before a drill-in) |
Animation Patterns
| Interaction | Pattern | Details |
|---|---|---|
| Opening a jail detail panel | Slide right in | Panel slides from right edge, content fades in after 100 ms delay |
| Banning / unbanning an IP | Delete & Slide | Row fades out (300 ms decelerate), remaining rows slide up (300 ms) |
| Switching dashboard tabs | Tabs & Pivots | Active indicator slides (300 ms), outgoing content slides out, incoming slides in with 100 ms delay |
| Navigating to a sub-page | Drill In | Old content fades out (100 ms linear), new content scales down in (300 ms decelerate) |
Motion Rules
- Never animate something the user did not trigger. Auto-refreshing data should appear without animation — just update in place.
- Respect
prefers-reduced-motion. When the OS or browser signals reduced motion, disable all non-essential animations and replace them with instant state changes. - No loading spinners longer than 1 second without explanation. If a network call exceeds 1 s, show a skeleton screen or a contextual loading message, not just a spinner.
- Keep looping animations (pulsing status dots, progress bars) subtle and small-area. Never animate large surfaces continuously.
7. Iconography
- Use the Fluent UI icon set (
@fluentui/react-iconsor the Fabric Core icon font). Do not introduce third-party icon libraries. - Icon size follows the adjacent text: 16 px icons next to 14 px body text, 20 px icons next to 16–20 px headers.
- Icons must always have a text label or an
aria-label. Icon-only buttons are acceptable only in toolbars and compact table rows, and they must have a tooltip. - Use outline icon variants by default. Switch to filled only for active or selected states (e.g., filled star for a favourited jail).
- Icon colour inherits from the text colour of its context. Override only for semantic purposes (red icon for errors, green for success).
8. Component Usage
Use Fluent UI React components as the building blocks. The following mapping shows which component to use for each BanGUI feature area.
Navigation
| Element | Fluent component | Notes |
|---|---|---|
| Side navigation | Nav |
Persistent on large screens, collapsible on small. Groups: Dashboard, Map, Jails, Config, History, Blocklists. |
| Breadcrumbs | Breadcrumb |
Show on detail pages (Jail > sshd, History > IP detail). |
| Page tabs | Pivot |
None currently (previous tabs removed). |
Data Display
| Element | Fluent component | Notes |
|---|---|---|
| Data tables | DetailsList |
All ban tables, jail overviews, history tables. Enable column sorting, selection, and shimmer loading. |
| Stat cards | DocumentCard or custom Stack card |
Dashboard status bar — server status, total bans, active jails. Use Depth 4. |
| Status indicators | Badge / Icon + colour |
Server online/offline, jail running/stopped/idle. |
| Country labels | Monospaced text + flag emoji or icon | Geo data next to IP addresses. |
Forms & Actions
| Element | Fluent component | Notes |
|---|---|---|
| Primary actions | PrimaryButton |
"Ban IP", "Save Configuration", "Run Import Now". |
| Secondary actions | DefaultButton |
"Cancel", "Reset", "Clear Filters". |
| Danger actions | PrimaryButton with danger styling |
"Unban All", "Flush Chain". Red theme override on themePrimary. |
| Text inputs | TextField |
IP address entry, regex pattern input, search fields. |
| Dropdowns | Dropdown |
Jail selector, time-range presets, log level picker. |
| Toggles | Toggle |
Enable/disable jail, enable blocklist source, ignore-self toggle. |
| Confirmations | Dialog |
Confirm ban, confirm unban-all, confirm delete actions. Always require explicit user action. |
Feedback
| Element | Fluent component | Notes |
|---|---|---|
| Success messages | MessageBar (success) |
"IP 1.2.3.4 has been banned in jail sshd." |
| Error messages | MessageBar (error) |
"Failed to connect to fail2ban server." |
| Warning messages | MessageBar (warning) |
"Blocklist import encountered 12 invalid entries." |
| Loading states | Shimmer |
Apply to DetailsList rows and stat cards while data loads. |
| Empty states | Custom illustration + text | "No bans recorded in the last 24 hours." Centre on the content area. |
| Tooltips | Tooltip / TooltipHost |
Full IP info on hover, full regex on truncated text, icon-only button labels. |
9. Tables & Data Grids
Tables are the primary UI element in BanGUI. They must be treated with extreme care.
- Use Fluent UI's
DetailsListfor all tabular data. - Column widths: Give the widest anticipated content enough room. IP addresses need ~140 px, timestamps ~180 px, country codes ~80 px. Use
minWidthandmaxWidthon each column. - Row density: Use
compactmode for tables with many rows (ban lists, history) andnormalmode for tables with fewer rows (jail overview, blocklist sources). - Sorting: Every column with comparable data must be sortable. Default sort: newest first for time-based tables.
- Selection: Table rows for actionable entities (banned IPs, jails) must support single selection that highlights the row and enables contextual actions in a command bar above.
- Shimmer loading: When data is loading, show shimmer placeholder rows matching the expected column layout. Never show a blank table body.
- Empty state: When the table has zero rows, display a centred message and icon inside the table frame (e.g., "No bans in this time range").
- Sticky header: Table headers must stick to the top of the scrollable area so column labels are always visible.
- Zebra striping: Do not use alternating row colours. Fluent UI relies on hover and selection highlights instead.
- Pagination vs. virtual scrolling: For tables that may exceed 100 rows, use virtual scrolling (
DetailsListsupports this natively). Avoid traditional pagination.
10. Cards & Stat Blocks
The dashboard uses cards to display key figures (server status, total bans, active jails).
- Each card is a contained surface at Depth 4 with 16 px padding on all sides.
- Card layout: icon or status dot (left) + large numeric value (
FontSizes.size28, semibold) + label (FontSizes.size12,neutralSecondary). - Cards arrange in a horizontal row using
Stackwithhorizontaltokens and16 pxgap. - On small screens, cards stack vertically in a single column.
- Do not put actions inside stat cards. They are read-only summaries. Actions belong in context menus or command bars.
11. World Map View
- The map renders country outlines only — no fill colours, no satellite imagery, no terrain shading.
- Countries with banned IPs display a count badge centred inside the country polygon. Use
FontSizes.size14semibold,themePrimarycolour. - Countries with zero bans remain completely blank — no label, no tint.
- On hover: country region gets a subtle
neutralLighterAltfill. On click: fill shifts tothemeLighterAltand the companion table below filters to that country. - The map must have a light neutral border (
neutralLight) around its container, at Depth 4. - Time-range selector above the map uses
Pivotwith quick presets (24 h, 7 d, 30 d, 365 d).
12. Forms & Inputs
- Use
TextFieldwith a visible label above the input (not a floating label). - Every text input must show a
descriptionorplaceholderthat illustrates the expected format (e.g.,192.168.1.0/24). - Validation errors appear below the input using the built-in
errorMessageprop — red text, small size (FontSizes.size12). - Group related fields inside a bordered
Stackor aGroupedListwith a section header. - Action buttons sit at the bottom-right of a form group, with primary on the right and secondary (Cancel) on the left.
- For the Regex Tester: use a side-by-side layout — sample log line on the left, regex input on the right, match result highlighted below. Monospace font for both fields.
13. Feedback & Status
Server Status Bar
- A thin horizontal bar at the very top of the content area (below the nav header).
- Online: green dot + "fail2ban running — v0.11.2 — 14 jails — 231 total bans". Use
FontSizes.size12,neutralSecondary. - Offline / unreachable: red dot + "fail2ban server unreachable" in
errorTextcolour. Bar background shifts to a very light red tint. - This bar is non-dismissible and always visible.
Inline Feedback
- After a successful action (ban, unban, save config): show a
MessageBarof typesuccessat the top of the current content area, auto-dismiss after 5 seconds. - After a failed action: show a
MessageBarof typeerror, not auto-dismissed — the user must close it manually. - Warnings (import issues, validation hints) use
MessageBarof typewarning, not auto-dismissed.
14. Dark Theme
- Every screen must look correct in both light and dark themes.
- Design light theme first, then verify dark theme by swapping the palette in the Theme Designer.
- Never assume white backgrounds. Always use
bodyBackgroundorbodyStandoutBackgroundfrom the theme. - Never use black text directly. Always use
neutralPrimary, which adapts to both themes. - Images, illustrations, and the world map must have transparent or theme-aware backgrounds that do not create harsh rectangles in dark mode.
- Test contrast ratios in both themes — a colour that passes AA on white may fail on dark grey.
15. Accessibility
- Colour contrast: All text must meet WCAG 2.1 AA minimums (4.5 : 1 for normal text, 3 : 1 for large text and UI components). Use the Fluent UI colour accessibility guide as reference.
- Keyboard navigation: Every interactive element must be reachable via Tab and operable with Enter or Space. Sidebar navigation, table row selection, dropdown menus — all must work without a mouse.
- Focus indicators: Use the default Fluent UI focus ring (2 px
themePrimaryoutline). Never hide or override focus styles. - Screen readers: Every icon-only button has an
aria-label. Tables usearia-sorton sortable columns. Status indicators havearia-live="polite"regions so screen readers announce changes. - Reduced motion: Wrap all custom animations in a
prefers-reduced-motionmedia query and provide an instant fallback. - Touch targets: All interactive elements have a minimum tap target of 44 x 44 px on touch devices (achieved via padding if the visible element is smaller).
16. Design Tokens & File Organisation
- All theme definitions live in a single file:
theme/appTheme.ts. - All spacing, sizing, and z-index constants live in
theme/tokens.ts. - Components consume tokens via the theme context — never import raw pixel values.
- When a design requires a value not already in the token set, add it to the shared tokens file and document its purpose. Never define a one-off constant inside a component.
// theme/appTheme.ts
import { createTheme } from "@fluentui/react";
export const lightTheme = createTheme({
palette: {
themePrimary: "#0078d4",
themeDarkAlt: "#106ebe",
// ... generated via Theme Designer
},
});
export const darkTheme = createTheme({
palette: {
themePrimary: "#2b88d8",
themeDarkAlt: "#3aa0f3",
// ...
},
});
// theme/tokens.ts
export const spacing = {
xs: 4,
s: 8,
m: 16,
l: 24,
xl: 32,
xxl: 48,
} as const;
export const contentMaxWidth = 1440;
export const sideNavWidth = 240;
export const sideNavCollapsedWidth = 48;
17. Do-Not-Do List
| Do | Do Not |
|---|---|
| Use Fluent UI components for every standard control | Build custom buttons, dropdowns, or modals from scratch |
| Reference theme tokens for all colours | Hard-code hex values like #ff0000 in components |
| Follow the 4 px spacing grid | Use arbitrary pixel values (13 px, 7 px, 19 px) |
| Provide a tooltip for every icon-only button | Leave icons unlabelled and inaccessible |
Use Shimmer for loading states |
Show a blank screen or a standalone spinner with no context |
| Design for both light and dark themes | Default to white backgrounds assuming light mode only |
Use DetailsList for all tabular data |
Use raw HTML <table> elements or a third-party data grid |
Use semantic colour slots (errorText, bodyBackground) |
Use descriptive palette slots (red, neutralLight) directly |
Use prefers-reduced-motion for all custom animation |
Force animation on users with motion sensitivities |
| Test with keyboard and screen reader before signing off | Assume mouse-only usage |