Skip to content

OrbitRepos - UI Design Principles

1. Overview

OrbitRepos's frontend is a React 18 single-page application (SPA) embedded into the Go binary at build time via go:embed. It is served at the root path (/) as a catch-all route, with all other routes handled by the API and format handlers.

Tech stack:

Layer Technology Version
Framework React 18
Language TypeScript
Build tool Vite
UI library Ant Design 5.x
Charts Recharts
Icons react-icons (si, fi), @ant-design/icons
Dates dayjs
HTTP client Axios
Routing React Router v6
Embedding go:embed via web/embed.go

2. Design System & Theming

2.1 Design Philosophy

The UI follows a shadcn/ui-inspired design language — a minimal, flat, professional aesthetic built on top of Ant Design with extensive CSS overrides. Key principles:

  • Flat and borderless: Cards and containers use subtle 1px borders (#e2e8f0) with zero box-shadow
  • Slate color palette: Dark slate (#0f172a) as primary, light slate (#f1f5f9) as secondary
  • Compact density: Smaller font sizes (13px body, 12px headers), tighter padding than Ant Design defaults
  • Consistent radii: Uniform 0.5rem (8px) border-radius everywhere
  • Monochrome accents: Primary actions use dark slate rather than brand blue — color is reserved for format icons and status indicators

2.2 CSS Custom Properties

All design tokens are defined as CSS custom properties in :root (App.css):

:root {
  /* Core colors */
  --background: #ffffff;
  --foreground: #0f172a;         /* Slate 900 */
  --card: #ffffff;
  --card-foreground: #0f172a;
  --popover: #ffffff;
  --popover-foreground: #0f172a;

  /* Brand / Action */
  --primary: #0f172a;            /* Slate 900 */
  --primary-foreground: #f8fafc; /* Slate 50 */

  /* Secondary / Muted */
  --secondary: #f1f5f9;          /* Slate 100 */
  --secondary-foreground: #0f172a;
  --muted: #f1f5f9;              /* Slate 100 */
  --muted-foreground: #64748b;   /* Slate 500 */

  /* Accent (hover states) */
  --accent: #f1f5f9;             /* Slate 100 */
  --accent-foreground: #0f172a;

  /* Destructive */
  --destructive: #ef4444;        /* Red 500 */
  --destructive-foreground: #f8fafc;

  /* Borders & Inputs */
  --border: #e2e8f0;             /* Slate 200 */
  --input: #e2e8f0;
  --ring: #0f172a;               /* Focus ring: Slate 900 */

  /* Layout */
  --radius: 0.5rem;              /* 8px */
  --sidebar-width: 240px;
  --sidebar-width-collapsed: 64px;
  --header-height: 52px;

  /* Typography */
  --font-sans: 'Inter', -apple-system, BlinkMacSystemFont,
               'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}

2.3 Color Palette

 Slate 900  #0f172a  ████  Primary, text, active states, focus rings
 Slate 500  #64748b  ████  Muted text, secondary labels, icons
 Slate 200  #e2e8f0  ████  Borders, dividers, input borders
 Slate 100  #f1f5f9  ████  Backgrounds, hover states, table headers
 Slate 50   #f8fafc  ████  Page background, primary foreground
 White      #ffffff  ████  Cards, sidebar, modals
 Red 500    #ef4444  ████  Destructive actions, negative changes
 Green 600  #16a34a  ████  Positive changes, success states

2.4 Ant Design Theme Overrides

The Ant Design ConfigProvider applies these token overrides (App.tsx):

// Global tokens
{
  colorPrimary: '#0f172a',
  colorBgContainer: '#ffffff',
  colorBorder: '#e2e8f0',
  colorBgLayout: '#f8fafc',
  borderRadius: 6,
  fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', ...",
  fontSize: 14,
  colorText: '#0f172a',
  colorTextSecondary: '#64748b',
  colorBgElevated: '#ffffff',
  controlHeight: 34,
}

// Component-level overrides
Table: { headerBg: '#f1f5f9', headerColor: '#64748b', rowHoverBg: '#f8fafc', borderColor: '#e2e8f0' }
Card:  { paddingLG: 20 }
Menu:  { itemBg: 'transparent', itemColor: '#64748b', itemHoverColor: '#0f172a',
         itemHoverBg: '#f1f5f9', itemSelectedColor: '#0f172a', itemSelectedBg: '#f1f5f9' }
Button: { primaryColor: '#f8fafc' }

2.5 Typography

Element Size Weight Color Notes
Page title 22px 700 --foreground -0.02em letter-spacing
Section header 15px 600 --foreground Used in browse, chart cards
Table header 12px 600 --muted-foreground Uppercase, 0.03em tracking
Body text 13px 500 --foreground Default for tables, sidebar items
Sidebar section 11px 500 --muted-foreground Uppercase, 0.05em tracking
Stat card value 26px 700 --foreground -0.02em letter-spacing
Stat card title 13px 500 --muted-foreground
Tags 11px 500 varies 4px radius
Code blocks 13px 400 #e2e8f0 on #0f172a JetBrains Mono / Fira Code / Consolas

2.6 Spacing Scale

Context Value
Content padding (main area) 24px 32px
Card padding 20px 24px
Card small padding 16px
Grid gap (stat cards, dashboard) 16px
Sidebar item padding 7px 16px, margin 1px 8px
Table cell padding 10px 12px
Modal header padding 16px 24px
Modal body padding 20px 24px

2.7 Scrollbar

Custom styled (WebKit) — thin 6px bars with #cbd5e1 thumb (hover: #94a3b8) on transparent track. Provides a clean, unobtrusive scroll experience.


3. Page Structure & Navigation

3.1 Layout Architecture

graph TB
    subgraph AppLayout["AppLayout Component"]
        direction LR

        subgraph Sidebar["Fixed Sidebar (240px / 64px collapsed)"]
            direction TB
            Logo["Logo + Brand\n'OrbitRepo'\n'Artifact Repository'"]
            Nav["Navigation Sections"]
            UserMenu["User Avatar + Dropdown\nSign Out"]
        end

        subgraph MainArea["Main Content Area"]
            direction TB
            Topbar["Sticky Topbar (52px)\nBreadcrumbs | Search Bar"]
            Content["Scrollable Content\nmax-width: 1400px\npadding: 24px 32px"]
        end
    end

    subgraph NavSections["Navigation Sections"]
        direction TB
        General["GENERAL\n- Dashboard\n- Repositories\n- Artifacts (Browse)"]
        Management["MANAGEMENT (Admin Only)\n- Users\n- Security\n- Audit Log"]
        Account["ACCOUNT\n- API Tokens\n- Settings\n  - Authentication (Admin)"]
    end

    Nav --> NavSections

    style Sidebar fill:#ffffff,stroke:#e2e8f0
    style MainArea fill:#f8fafc,stroke:#e2e8f0
    style NavSections fill:#f1f5f9,stroke:#64748b

3.2 Route Map

graph LR
    subgraph Public
        Login["/login\nLogin Page"]
    end

    subgraph Protected["Protected Routes (Auth Required)"]
        Dashboard["/\nDashboard"]
        Repos["/repositories\nRepository List"]
        RepoDetail["/repositories/:name\nRepository Detail"]
        Browse["/browse/*\nArtifact Browser"]
        Search["/search\nSearch Results"]
        Tokens["/tokens\nAPI Tokens"]
        Settings["/settings\nSystem Info"]
    end

    subgraph AdminOnly["Admin-Only Routes"]
        Users["/users\nUser Management"]
        Security["/security\nGroups & Roles"]
        Audit["/audit\nAudit Log"]
        Auth["/settings/auth\nLDAP & OIDC"]
    end

    Login -->|"JWT Token"| Dashboard
    Dashboard --> Repos --> RepoDetail
    Dashboard --> Browse
    Dashboard --> Search
    Dashboard --> Tokens
    Dashboard --> Settings --> Auth
    Dashboard --> Users
    Dashboard --> Security
    Dashboard --> Audit

    style Public fill:#fff,stroke:#64748b
    style Protected fill:#f1f5f9,stroke:#0f172a
    style AdminOnly fill:#fef2f2,stroke:#ef4444

3.3 Sidebar Behavior

State Width Shows
Expanded 240px Icon + label + section headers + user info
Collapsed 64px Icon only, tooltip on hover, no section headers
  • Toggle via MenuFoldOutlined / MenuUnfoldOutlined button in sidebar header
  • Active item highlighted with --accent background and 600 font weight
  • Settings item has expandable sub-items (Authentication) for admin users
  • Main content area margin adjusts via CSS transition (200ms ease)

3.4 Topbar

Element Position Behavior
Breadcrumbs Left Auto-generated from URL path segments. Capitalized, linked (except last segment). Separator: /
Search Bar Right Input with SearchOutlined icon. Enter key navigates to /search?q=query. Clears after navigation.

3.5 Authentication Flow

App
 └─ ConfigProvider (Ant Design theme)
     └─ AntApp (message/notification context)
         └─ AuthProvider (React Context)
             └─ BrowserRouter
                 └─ AppRoutes
                     ├─ /login → Login (redirect to / if already authed)
                     └─ ProtectedRoute wrapper
                         ├─ checks token in AuthContext
                         ├─ adminOnly flag for restricted routes
                         └─ AppLayout → Outlet → Page

AuthProvider (store/auth.tsx): - Stores JWT token in localStorage - On mount: validates token via GET /api/v1/users/me - Provides: user, token, isAdmin, login(), logout() - Axios interceptor: attaches Authorization: Bearer {token} to all API calls - 401 response interceptor: clears token, redirects to /login


4. Component Patterns

4.1 Tables

All data tables use Ant Design's <Table> component with consistent styling overrides:

Table Header:  Background #f1f5f9, color #64748b, 12px uppercase, 600 weight
Table Body:    13px, 10px 12px padding, #e2e8f0 bottom border
Table Hover:   #f8fafc row highlight

Common table patterns: - Columns defined as ColumnsType<T> array - Sortable columns via Ant Design's built-in sorter - Render functions for: format icons, color-coded tags, date formatting (dayjs), byte formatting, action buttons - Loading state via loading prop connected to useState

4.2 Modals (Create / Edit)

Standard CRUD modal pattern used across pages:

// State management
const [modalVisible, setModalVisible] = useState(false);
const [form] = Form.useForm();

// Modal structure
<Modal
  title="Create Repository"
  open={modalVisible}
  onOk={() => form.submit()}
  onCancel={() => { setModalVisible(false); form.resetFields(); }}
>
  <Form form={form} layout="vertical" onFinish={handleSubmit}>
    <Form.Item name="name" label="Name" rules={[{ required: true }]}>
      <Input />
    </Form.Item>
    {/* ... */}
  </Form>
</Modal>

Styling: - 0.5rem border radius - Header: 16px 24px padding, #e2e8f0 bottom border, 15px title at weight 600 - Body: 20px 24px padding

4.3 Forms

Element Style
Labels 13px, weight 500
Inputs 6px radius, #e2e8f0 border, 13px font
Focus state #0f172a border, 0 0 0 2px rgba(15,23,42,0.08) ring
Selects Same border/radius as inputs
Date pickers Same border/radius as inputs
Switches Used for boolean toggles (enabled/admin/SSL)

Layout: Always layout="vertical" — labels above inputs. Form items stacked vertically.

4.4 Buttons

Variant Style
Primary Background #0f172a, text #f8fafc, hover #1e293b
Default White background, #e2e8f0 border, hover #f1f5f9
Text No border, transparent background
Danger Ant Design's danger variant (red)

All buttons: 6px radius, 34px height, 13px font, weight 500, zero box-shadow.

4.5 Tags

Used extensively for categorization with color coding:

Context Colors
Repository format Format-specific brand colors (see 4.8)
Repository type hosted = green, proxy = blue, group = orange
Permission actions read = green, write = blue, delete = red, admin = purple
Audit actions Color-coded by action type
Auth source local = default, ldap = blue, oidc = purple

Styling: 4px radius, 11px font, weight 500, 1px 8px padding.

4.6 Stat Cards

Dashboard uses a 4-column responsive grid of metric cards:

+------------------+------------------+------------------+------------------+
| Title       Icon |                  |                  |                  |
| 42               |                  |                  |                  |
| +12% from ...    |                  |                  |                  |
+------------------+------------------+------------------+------------------+

Grid breakpoints: - > 1200px: 4 columns - 641px - 1200px: 2 columns - <= 640px: 1 column

Card anatomy: Title (13px muted) + icon top-right, large value (26px bold), optional change indicator (11px, green for positive / red for negative).

4.7 Filter Bars

Horizontal flex row (gap: 8px, flex-wrap: wrap) used on list pages:

[Search Input] [Format Select] [Type Select] [Date Range] [Action Button]

Pattern: <Input.Search> + <Select> components inline. State drives table dataSource filtering or re-fetching.

4.8 Format Icons

Each package format has a dedicated brand icon from react-icons/si with its official brand color:

Format Icon Component Brand Color
Docker SiDocker #2496ED
Maven SiApachemaven #C71A36
npm SiNpm #CB3837
PyPI SiPython #3775A9
NuGet SiNuget #004880
Go SiGo #00ADD8
Helm SiHelm #0F1689
APT SiDebian #A81D33
Raw FiPackage #64748b

Used in: repository tables, browse lists, search results, sidebar (Artifacts page). Default size: 16px. The FormatIcon component and getFormatColor() helper are defined in components/FormatIcon.tsx.

4.9 Charts

Dashboard uses Recharts <BarChart> for "Repositories by Format":

  • Bar colors use format brand colors
  • Responsive container
  • Placed in a .chart-card container (white card with border, 20px 24px padding)

4.10 Code Blocks (Pull Commands)

Used in the Browse page for displaying pull/install commands:

+--------------------------------------------------+
| DOCKER PULL                                  [Copy] |
| ┌──────────────────────────────────────────────┐   |
| │ docker pull localhost:8080/myrepo/nginx:latest│   |
| └──────────────────────────────────────────────┘   |
+--------------------------------------------------+

Styling: - Dark background: #0f172a - Text color: #e2e8f0 - Font: JetBrains Mono / Fira Code / Consolas, 13px - Copy button: absolute positioned top-right, semi-transparent border, hover reveals - Label: 12px uppercase, weight 600, muted foreground


5. UX Patterns

5.1 Data Loading

Every page follows the same loading pattern:

const [data, setData] = useState<T[]>([]);
const [loading, setLoading] = useState(true);

useEffect(() => {
  fetchData();
}, []);

const fetchData = async () => {
  setLoading(true);
  try {
    const res = await apiClient.getData();
    setData(res.data);
  } catch (err) {
    message.error('Failed to load data');
  } finally {
    setLoading(false);
  }
};

// Loading spinner (centered, 400px min-height)
if (loading) return <div className="page-spinner"><Spin size="large" /></div>;

5.2 CRUD Lifecycle

Standard create/read/update/delete flow used on every management page:

1. Page mounts → fetch list → render table
2. User clicks "Create" button → modal opens with empty form
3. User fills form → submits → POST to API
4. On success: message.success('Created'), close modal, re-fetch list
5. On error: message.error('Failed to create')
6. Delete: Ant Design <Popconfirm> or Modal.confirm → DELETE to API → re-fetch

5.3 Pagination

Two pagination strategies are used:

Strategy Where Used Implementation
Cursor-based Artifact lists, search results "Load More" button, cursor + limit params, appends to existing array
Offset-based Audit log, user list Ant Design <Pagination> component, standard page + pageSize params

Cursor-based pagination is used for potentially large datasets (artifact lists can have thousands of items per repository).

5.4 Navigation Patterns

Pattern Implementation
Sidebar click navigate(item.key) via React Router
Breadcrumb links Auto-generated from URL path, clickable via navigate()
Search Enter in topbar → navigate('/search?q=...')
Table row click Navigate to detail page (repos, browse items)
Browse drill-down URL-based multi-level: /browse/browse/{repo}/browse/{repo}/docker/{image}/tags/{tag}
Back navigation Browser back button (React Router history) + breadcrumb links

5.5 Browse Page Navigation Model

The Browse page (Browse.tsx, 1423 lines) implements a multi-level, URL-driven browsing experience:

Level 1: /browse
  └─ Repository list (compact rows with format icons)

Level 2: /browse/{repo}
  ├─ Docker: Image name list
  └─ Others: Artifact name list

Level 3: /browse/{repo}/docker/{image}
  └─ Folder links: "tags" | "manifests"

Level 4:
  ├─ /browse/{repo}/docker/{image}/tags          → Tag table
  ├─ /browse/{repo}/docker/{image}/tags/{tag}    → Tag detail (Docker Hub style)
  ├─ /browse/{repo}/docker/{image}/manifests     → Manifest table
  ├─ /browse/{repo}/docker/{image}/manifests/{d} → Manifest detail
  └─ /browse/{repo}/artifacts/{name}             → Version table (non-Docker)

Each level has: - Breadcrumb trail with clickable segments - Search/filter bar - Compact list or table display - Click-to-drill-down interaction

5.6 Pull Command Generation

The Browse page generates format-specific install/pull commands for all 9 formats:

Format Command Template
Docker docker pull {host}/{repo}/{image}:{tag}
Maven <repository> XML block for pom.xml
npm npm install {package} --registry={url}
PyPI pip install {package} --index-url={url}
NuGet dotnet add package {package} --source={url}
Go GOPROXY={url} go get {module}
Helm helm repo add {repo} {url}
APT deb {url} stable main (sources.list entry)
Raw curl -O {url}/{path} or wget {url}/{path}

Displayed in dark code blocks with one-click copy button.

5.7 Clipboard Operations

Throughout the UI, copy-to-clipboard is used for:

Element Trigger Feedback
Pull commands Click copy button in code block message.success('Copied!')
Docker digests Click digest text message.success()
API tokens Click copy button (shown once after creation) message.success()
SHA256 hashes Click hash text in browse detail message.success()

Implementation: navigator.clipboard.writeText(text) with Ant Design message toast.

5.8 Admin Guards

Access control is enforced at multiple layers in the frontend:

Layer Mechanism
Route level <ProtectedRoute adminOnly> wrapper — redirects non-admins to /
Sidebar visibility adminOnly flag on nav items — filtered via .filter(item => !item.adminOnly \|\| isAdmin)
Button visibility Conditional rendering: {isAdmin && <Button>Create</Button>}
Section visibility Entire sections (cleanup policies, permissions) hidden for non-admin users

5.9 Error Handling

Error Type Handling
401 Unauthorized Axios response interceptor clears token, redirects to /login
API errors message.error('Descriptive message') toast notification
Form validation Ant Design form rules — inline error messages below fields
Empty states Ant Design <Empty> component with description text
Loading failures Catch block shows error toast, loading spinner dismissed

5.10 Responsive Design

Three breakpoint tiers:

Breakpoint Adaptation
> 1400px Full layout, content capped at max-width: 1400px
1024px - 1400px Dashboard grid collapses from 2 columns to 1
641px - 1200px Stat cards grid: 4 → 2 columns
<= 640px Stat cards grid: single column, filter bars wrap

The sidebar does not have a mobile breakpoint — it relies on the collapse toggle to reduce width to 64px. Tables use overflow-x: auto for horizontal scrolling on narrow screens.


6. Page Inventory

Page Component Lines Key Elements
Dashboard Dashboard.tsx 312 4 stat cards, bar chart (Recharts), recent artifacts list, repos by type, top downloaded, tab bar (Overview/Activity/Analytics)
Login Login.tsx 114 Centered card, username/password form, OIDC "Sign in with SSO" button (conditional), auto-redirect if authed
Repositories Repositories.tsx 208 Table with format icons + type tags, search + format + type filters, create modal (format/type/proxy fields/group members), admin-only create/delete
Repository Detail RepositoryDetail.tsx 646 Repo info section, stats (artifact count + storage), artifacts table (cursor pagination), cleanup policies CRUD, group role assignments management
Browse Browse.tsx 1423 Multi-level URL-driven browser, Docker-specific views (tag detail, manifest detail, layers, platforms), pull command modals, breadcrumb navigation
Search Search.tsx 274 Full-text search with URL-persisted query, results table with format icons + version counts, cursor-based "Load More", click → Browse navigation
Users Users.tsx 136 User table (username, email, source, admin flag, last login), create modal, delete with confirmation
API Tokens Tokens.tsx 145 Token table (name, prefix, expires, last used), create modal (name + optional expiry), one-time token display with copy
Security Security.tsx 536 Two tabs: Groups (table with expandable members + role assignments, add member/assign role modals) and Roles (table with permission tags, create role modal with permission checkboxes)
Settings Settings.tsx 46 System info display (version, Go version, OS/arch, CPUs, goroutines, storage type, uptime, memory)
Authentication Authentication.tsx 211 Two cards: LDAP config (10 fields + test connection) and OIDC config (6 fields), save buttons
Audit Log AuditLog.tsx 111 Event table (time, user, action tag, resource, IP, user agent), filters (user search, action select, resource type, date range)