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/MenuUnfoldOutlinedbutton in sidebar header - Active item highlighted with
--accentbackground and600font 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:
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-cardcontainer (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) |