* Add editable display-name field to generic integrations Lets users set a custom name for Proxmox, Docker, AWS, Remote Desktop, Netbird, Cloudflare, Uptime Kuma, and Weather integrations, separate from the host/IP field, mirroring the SSH host rename pattern. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4 * Surface the new-integration name field as a labeled input The name field for new generic integrations was a faint header input with only placeholder text, easy to miss. Move it into the form grid as a proper labeled "Name" field next to the other connection fields. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4 * Add file upload for SSH private key and certificate fields Lets users pick a key file from disk (e.g. ~/.ssh) instead of pasting its contents into the Private Key / OPKSSH Certificate fields. * Fix SSH private key paste corrupting multi-line PEM format Private Key and Certificate fields were single-line <input> elements, which strip newlines on paste and corrupt PEM-formatted keys (causing 'Unsupported key format' errors). Render them as multi-line textareas instead so pasted keys keep their line breaks. * Add JSON-converted bookmark import file for Archnest data import Converts homarr-bookmarks.md into the format expected by /api/data/import. * Auto-populate bookmark icons via favicon service in import JSON Each bookmark now points to Google's favicon endpoint for its domain instead of having no icon at all. * docs: realign design docs with the deployed app, consolidate, rewrite README README.md was badly stale (listed Terminal as "pending/on hold" and only 5 pages, when all 11 pages are built and live). Rewrote it as a detailed, accurate map of the architecture, every page, every backend route, every integration adapter, and the SSH subsystem, written explicitly for this repo's actual audience (the owner + future AI sessions, never the public). Deleted archnest-blueprint.md and glance.md: both were pre-backend mockup specs describing fictional config files (systems.config, infra.config, fail2ban-driven security scoring) and placeholder data that never matched the real implementation, and conflicted with the deployed app's actual page count/nav/data sources. Their still-true content (color palette, dropdown menu shape, card styling) was folded into design-decisions.md. Rewrote design-decisions.md's "Page-Specific Notes" into a full "Page Notes" section covering all 11 pages plus Login/Enrollment (previously only 4 pages had notes, and those didn't reflect later changes like Files/Tunnels/Containers/Host Metrics/Remote Desktop shipping). Each section now states the real data source per page so it can't drift from the code silently again. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4 --------- Co-authored-by: Claude <noreply@anthropic.com>
14 KiB
ArchNest — Design Decisions & Page Reference
Living reference for visual/UX conventions and what's actually built on each page. Apply the Global Rules consistently to any new page. The Page Notes section describes the real, deployed implementation — not a mockup. If you change a page's layout or data source, update its section here in the same PR.
This file replaces the old
archnest-blueprint.md(original 6-page mockup pitch, since deleted — superseded by the real 11-page app below) andglance.md(original Glance spec with fictional config files likesystems.config/infra.configthat were never built — also deleted). Anything from those files still true (color palette, typography, card style, dropdown menu shape) has been folded in below.
Global Rules (apply to every page)
Sidebar
- Expanded width: 200px. Collapsed width: 64px (icons only).
- User can manually collapse/expand via a toggle button (not just responsive breakpoints).
- Main content
margin-leftmust match the sidebar width exactly. - Bottom of sidebar: live "System Status" widget — green dot + "All Systems Operational" or "X Issue(s) Detected", driven by polling integration status (not the fictional ping-sweep config described in the old spec).
Page Title (Top Bar)
- Color: gold
#C8A434— not white. Use inlinestyle={{ color: '#C8A434' }}if a Tailwind class doesn't apply. - Font: 18px, bold, uppercase, tracking-wide.
- No border on the top bar — it blends into the page background.
- Pages with a
pageSubtitlesentry (currently only BookNest) render a larger 28px title + subtitle line instead, and the top bar grows from 56px → 72px — seeTopBar.tsx'spageSubtitlesmap andApp.tsx'stopBarHeightlookup, which must be kept in sync or the layout clips/gaps.
Colors
| Role | Value |
|---|---|
| Background (page) | #0D0E10 |
| Background (cards) | #141518 |
| Background (sidebar) | #0A0B0D |
| Border (cards) | #1E2025 |
| Border/accent (hover/active) | #C8A434 (gold) |
| Success | #2ECC71 |
| Warning | #E67E22 |
| Danger | #E74C3C |
| Text (primary) | #E8E6E0 |
| Text (secondary) | #7A7D85 |
Tailwind v4 @theme custom colors (text-gold, bg-card, etc.) don't always
apply reliably — fall back to inline style={{ color: '#C8A434' }} when a
color isn't rendering, and verify visually after changes.
Typography
- Card titles: 10-11px, uppercase, tracking-[1.5px], secondary color, font-medium.
- Large numbers: 24-28px, bold, primary color.
- Subtitles/labels: 10-11px, secondary color.
- Body text in lists: 13px, primary color.
- Timestamps: 11px, secondary color.
Card Style
- Border radius: 12px. Background
#141518. Border 1px solid#1E2025. - Padding: 16-24px depending on density. No shadows (flat design).
- Hover: border transitions to gold, 0.2s ease.
Content Alignment
- All rows in a page share the same horizontal padding (
px-6at the parent container level) so the hero banner, status cards, and content rows line up left/right.
Animations
- Card hover: border → gold, 0.2s ease.
- Progress ring / sparkline: animate from 0 on load, ~1s.
- Progress bars: fill animation ~0.8s.
Icons
- Lucide React, 14-18px depending on context. Gold for active/accent, secondary
color for inactive. Gold glow on active sidebar items:
shadow-[0_0_6px_rgba(200,164,52,0.5)]. - Gotcha: the installed lucide-react version's TypeScript types list some
brand/wordmark icons (
Github,Gitlab,Linkedin,Youtube) that aren't actually exported at runtime —tsc --noEmitstays clean while the page renders blank with a runtimeSyntaxErroronly visible in the Vite dev log. Verify icon names against the package's actual exports before importing anything brand-flavored.
No Footer
- No page has a fixed footer/status bar. Don't add one unless explicitly requested.
Target Display
- Primary design target: 16-inch / 1920px-wide screen. Don't constrain content width unnecessarily — design should feel spacious.
User Avatar & Dropdown (TopBar, every page)
- 36px circle, gold border + glow, shows uploaded avatar image or initials.
- Adjacent: display name + "Administrator" role label, chevron (rotates on open).
- Dropdown menu: header (name + email) → Profile (
/settings?tab=profile) → Appearance (/settings?tab=appearance) → Security (/settings?tab=security) → Help & Support (/help) → divider → Sign Out (red, danger styling). SeeTopBar.tsx.
Page Notes (what's actually built, page by page)
All pages below consume real backend data via src/lib/api.ts — zero mock
data anywhere in the app. Where a section says "real backend data," it means
an actual SQL-backed or live-polled endpoint, not a config file or static array.
Glance (/)
- Hero banner (image with radial-gradient fade mask) + KPI cards overlapping the bottom edge via negative margin.
- 4 status cards: System Status (% of integrations reachable),
Infrastructure (resource count + health breakdown from
api.listResources()), Integrations (connected/total fromapi.listIntegrations()), Bookmarks (total + favorites fromapi.listBookmarks()). - Middle row (3 columns): Resource Overview (top 6 resources + status),
Recent Activity (5 latest from
api.listEvents(5)), problem/critical resources widget. - Bottom row: Connected Integrations grid + 4 Shortcuts buttons (to Settings, BookNest, Infrastructure).
- No footer. Hides the hero gracefully if the banner image fails to load.
- There is no
systems.config/infra.config/ping-sweep/fail2ban machinery — that was speculative spec from before the backend existed. Health figures are derived directly from each integration adapter'stestConnection()result, polled by the existing/api/integrationslist.
Infrastructure (/infrastructure)
- Hero banner at the
App.tsxlayout level (showHeroincludes this route), extending behind the sticky, transparent-on-hero-routes TopBar. - Sub-tabs: Overview (only enabled tab) and Network (disabled, "Coming soon" — intentional, leave alone unless asked).
- 4 status cards: Total Resources, Healthy, Warnings, Critical — from
api.listResources(). - Middle row: Resource Distribution (donut by integration type) + Node Status (4-col tile grid, one tile per resource — replaced an earlier world-map concept since a map doesn't make sense for a small/single-site setup).
- Bottom row: Integration Health list + Recent Activity (
api.listEvents(4)). - Card backgrounds use a
cardDim+cardVignette(radial-gradient,closest-sidekeyword — a fixed%leaves a visible hard edge on straight sides) combo on the middle row only, per explicit visual preference; bottom row stays plain/regular.
BookNest (/booknest)
- Hero banner reused at layout level with page-specific tuning
(
heroPaddingTop/heroObjectPositionlookup maps inApp.tsx). - Large 28px title + "Your Digital Library" subtitle (the one page using
pageSubtitles), header grows to 72px. - Page stats row (Links / Categories / Favorites) + "Delete All" bulk action.
- Main column: Quick Access (top 5 categories) + bookmark groups grid (4 cols, full CRUD with hover edit/delete/star).
- Right sidebar (spans both grid rows via
gridRow: '1 / span 2'): Favorites, Recently Added (5 newest), Link Health donut, Category Breakdown donut. - Add/edit bookmark modal supports auto-detected favicons via
guessServiceIconUrl()or manual icon entry. - All data from
api.listBookmarks()/api.listBookmarkCategories()with real create/update/delete calls — no local-only state.
Terminal (/terminal)
- Left sidebar: SSH hosts (integrations of type
ssh), click to connect. - Tab bar + 1/2/4-pane split layout, each pane an independent xterm instance.
- Preferences panel (theme: ArchNest Dark/Matrix/Solarized/Midnight Blue, font
size 11-16px, font family) — stored in
localStorage(archnest-terminal-prefs), not synced server-side. - WebSocket to
/api/terminal:connect/input/resize/list_tmux/disconnectmessages; supports attaching to an existing tmux session or starting a new one. - Certificate auth (OPKSSH) shells out to the system
sshbinary under a pty rather than using the JS SSH client. - Session logging to
ARCHNEST_SESSION_LOG_DIRis backend-supported but has no dedicated viewer UI yet.
Tunnels (/tunnels)
- List + create form for local/remote/dynamic (SOCKS5) SSH tunnels.
- Create form fields conditionally show/hide based on mode (no endpoint host/port fields for dynamic mode).
- Status color map: stopped
#7A7D85, connecting#C8A434, retrying#E0A030, connected#2ECC71, error#E74C3C. Polls every 3s. - Backed by
api.listTunnels()/createTunnel()/connectTunnel()/disconnectTunnel()/deleteTunnel().
Files (/files)
- SSH host selector + breadcrumb-navigable SFTP directory browser.
- File editor modal: plain textarea, files up to 50MB (anything larger is download-only). Heuristic binary detection (null bytes/control chars) decides base64 vs. text encoding.
- Inline directory creation, rename, delete, upload, download.
- Host-to-host transfer modal: pick source file + destination host/path,
copy-or-move toggle, live progress bar fed by
api.getTransfer(id)polling.
Containers (/containers)
- Docker host selector (integrations of type
docker) + container list. - Per-container state badge (running/paused/exited/dead) with context-aware action buttons (Start/Stop/Restart/Pause/Unpause/Remove) — buttons disable themselves for invalid transitions (e.g. can't pause a stopped container).
- Live CPU/memory stats polled only for running containers.
- Logs modal (configurable tail count) and an exec modal (interactive shell via
WebSocket to
/api/docker/exec).
Remote Desktop (/remote-desktop)
- Left sidebar: hosts from integrations of type
remote_desktop. - Main area is a Guacamole canvas tunneled over WebSocket to
/api/guacamole, proxying to theguacdsidecar container (RDP/VNC/Telnet). - Requires
ARCHNEST_GUAC_CRYPT_KEY— sessions fail without it (the backend warns on startup if it's missing).
Host Metrics (/host-metrics)
- Left sidebar: SSH hosts. Selecting one starts a 5-second polling loop against
api.getHostMetrics(integrationId), cleared on deselect. - Cards: CPU/Memory/Disk gauges (color thresholds: green <75%, yellow 75-90%, red ≥90%) + Uptime, then Network Interfaces, Processes (top 10), Listening Ports, Firewall Rules (UFW/iptables), Login Activity.
- Backed by 10 sequential SSH-exec collectors under
backend/src/ssh/metrics/(cpu, memory, disk, uptime, network, system, processes, ports, firewall, login-stats) — sequential on purpose, to avoid exceeding OpenSSH'sMaxSessionslimit per host.
Settings (/settings)
- Fixed 200px left nav, 7 sections: Profile, Appearance, Security,
Integrations, Notifications, Data & Backup, About. URL-deep-linkable via
?tab=(useSearchParams) — use this pattern for any future section. - Profile: display name, email, avatar upload (
FileReader.readAsDataURL→ base64 data URL, persisted viaapi.updateMe()— this one is a real backend round-trip, unlike the rest of the page below). - Appearance: accent color swatches — local-state only, doesn't persist
or apply anywhere (see Known Stubs in
HANDOFF.md). - Security: password-change form + 2FA toggle — currently placeholder UI
pending the Phase 2 auth work in
HANDOFF.md. - Integrations: one card per type (Proxmox/Docker/NetBird/Cloudflare/AWS/
Uptime Kuma/Weather/Remote Desktop/SSH), each with type-specific fields
(secrets masked, eye-toggle to reveal, "Test Connection" button). Real CRUD
via
api.createIntegration()/updateIntegration()/deleteIntegration()/testIntegration(). - Notifications: toggles — placeholder, no delivery mechanism (see
Known Stubs in
HANDOFF.md). - Data & Backup: full JSON export/import of integrations + secrets +
bookmarks + tunnels via
backend/src/routes/data.ts— this is the one endpoint that intentionally round-trips decrypted secrets, by design, for backup portability. - About: static version/license/links info.
Help (/help)
- Fully static — no backend calls. 11 guide cards (one per real page) with a short description and tips. Update this page whenever a new page ships.
Login (/login) / Enrollment (/enrollment)
Login.tsx: username/password form →api.login()→AuthContext.Enrollment.tsx: two-step first-run flow — (1) create the admin account viaapi.setup(), gated to only work while theuserstable is empty; (2) optional "Connect Your Services" integration onboarding grid (skippable, same integration form as Settings). Routed to based onGET /api/system/setup-status.
Backend Architecture (current, not aspirational)
backend/— Fastify 5 + TypeScript (ESM,tsxdev /tsc -bbuild),better-sqlite3for storage, deployed as its own Docker container alongside the frontend and aguacdsidecar.- Auth: single-user-schema JWT (
@fastify/jwt) today — seeHANDOFF.mdfor the multi-user/SSO roadmap (Phases 2-4, not yet built). - Integration credentials split across
integrations(non-secret config) andsecrets(AES-256-GCM-encrypted, keyed byARCHNEST_SECRET_KEY) tables — secrets never appear in a generic "list integrations" response by construction, not by remembering to redact a field. - Every integration type has an adapter in
backend/src/integrations/implementingtestConnection()(required) andlistResources()(optional);registry.tsmapsIntegrationType→ adapter. All 9 types (proxmox/docker/netbird/cloudflare/aws/uptime_kuma/weather/ssh/remote_desktop) are real, working adapters — none are stubs anymore. backend/src/ssh/is the shared SSH transport layer powering Terminal, Files, Tunnels, Transfers, and Host Metrics:connect.ts(jump-host chaining, host-key verification, cert auth),sftp.ts(ephemeral SFTP),transfer.ts(host-to-host streamed copy/move with progress + cancel), andmetrics/(the 10 collectors listed above).- Vite dev server proxies
/api→http://localhost:4000; prod routes/apito the backend container via Nginx Proxy Manager.
Future Integration Notes
- AWS/Cloudflare/NetBird/Proxmox/Uptime Kuma adapters are real but currently surface basic resource inventory + health only — deeper cost/pricing/budget data (mentioned in the old blueprint) is not implemented and not currently planned; revisit only if explicitly requested.