- Help page now scrolls (it sat under a clipped section with no overflow handling). - Connected Integrations on Glance shows 5 per row in a scrollable area with the transparent ghost scrollbar, instead of growing the card unbounded. - System Status KPI ring is now a thicker, vertically centered, multi-segment donut broken down by integration type, each type colored consistently from a shared first-come-first-served palette (src/lib/integrationColors.ts) so e.g. whichever type connects first always gets the same color everywhere it's used. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019hu9pZvJY4BgmcQeAw2ugk
260 lines
12 KiB
TypeScript
260 lines
12 KiB
TypeScript
import {
|
|
LayoutGrid,
|
|
Server,
|
|
Bookmark,
|
|
Terminal,
|
|
Waypoints,
|
|
FolderOpen,
|
|
Box,
|
|
MonitorSmartphone,
|
|
Gauge,
|
|
Settings,
|
|
Search,
|
|
Lightbulb,
|
|
ArrowRightLeft,
|
|
ArrowLeftRight,
|
|
Shuffle,
|
|
Rocket,
|
|
type LucideIcon,
|
|
} from 'lucide-react'
|
|
|
|
const cardBase: React.CSSProperties = {
|
|
backgroundColor: 'rgba(10, 10, 12, 0.92)',
|
|
border: '1px solid rgba(200, 164, 52, 0.08)',
|
|
borderRadius: '12px',
|
|
padding: '22px',
|
|
}
|
|
|
|
interface GuideEntry {
|
|
icon: LucideIcon
|
|
title: string
|
|
description: string
|
|
tips?: string[]
|
|
examples?: { icon?: LucideIcon; label?: string; text: string }[]
|
|
}
|
|
|
|
const guideEntries: GuideEntry[] = [
|
|
{
|
|
icon: LayoutGrid,
|
|
title: 'Glance',
|
|
description:
|
|
'The home dashboard. Shows overall system health, a rollup of connected integrations, recent activity, and shortcuts into the rest of the app.',
|
|
tips: ['Click "Connected Integrations" entries to jump straight to Infrastructure.'],
|
|
examples: [
|
|
{ text: 'First thing in the morning: open Glance to see if anything went offline overnight before digging into any one page.' },
|
|
],
|
|
},
|
|
{
|
|
icon: Server,
|
|
title: 'Infrastructure',
|
|
description:
|
|
'Lists every connected integration (Proxmox, AWS, Docker, NetBird, Cloudflare, Uptime Kuma, Weather, SSH hosts) and the live resources/health each one reports.',
|
|
tips: ['Add new integrations from Settings → Integrations — they show up here automatically.'],
|
|
examples: [
|
|
{ text: 'Connect Proxmox and AWS, then check VM and EC2 health side by side without opening either provider\'s own dashboard.' },
|
|
],
|
|
},
|
|
{
|
|
icon: Bookmark,
|
|
title: 'BookNest',
|
|
description: 'A categorized bookmark manager for the links you use most — internal tools, dashboards, docs, anything.',
|
|
tips: [
|
|
'Icons are auto-detected from the title or URL (e.g. typing "Proxmox" picks up the real Proxmox logo) — pick "Choose manually" if it guesses wrong.',
|
|
'Star a bookmark to pin it to the Favorites panel.',
|
|
],
|
|
examples: [
|
|
{ text: 'Organize your router admin page, NAS UI, and internal wiki into "Network", "Storage", and "Docs" categories so new teammates can find them without asking.' },
|
|
],
|
|
},
|
|
{
|
|
icon: Terminal,
|
|
title: 'Terminal',
|
|
description: 'A full SSH terminal to any host you\'ve added as an integration — supports tabs, split panes, jump hosts, and certificate auth.',
|
|
tips: ['Session output can be logged; theme and font preferences are remembered between visits.'],
|
|
examples: [
|
|
{ text: 'SSH into a public-facing jump host, then open a second tab routed through it to reach a private VM that has no public IP of its own.' },
|
|
],
|
|
},
|
|
{
|
|
icon: Waypoints,
|
|
title: 'Tunnels',
|
|
description:
|
|
'Local, remote, and dynamic (SOCKS5) SSH tunnels, each riding on top of an existing SSH Host integration. Tunnels can be set to auto-start whenever the backend boots, and the app keeps retrying if a connection drops.',
|
|
tips: ['Pick an SSH host from Settings → Integrations → SSH Hosts first — tunnels are created on top of one.'],
|
|
examples: [
|
|
{
|
|
icon: ArrowRightLeft,
|
|
label: 'Local Forward',
|
|
text: 'A database on a remote server only listens on its own localhost. Forward remote 5432 to your local 5432, and connect a DB client as if it were running on your machine.',
|
|
},
|
|
{
|
|
icon: ArrowLeftRight,
|
|
label: 'Remote Forward',
|
|
text: 'You\'re running something on your laptop (e.g. a dev server on :3000) and need a remote server to reach it temporarily — remote forward exposes your local port on the remote side.',
|
|
},
|
|
{
|
|
icon: Shuffle,
|
|
label: 'Dynamic (SOCKS5)',
|
|
text: 'Point your browser\'s proxy settings at the SOCKS5 port and browse as if you were sitting inside the remote network — useful for reaching a whole subnet of internal services through one SSH host, instead of forwarding each port individually.',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
icon: FolderOpen,
|
|
title: 'Files',
|
|
description: 'Browse, edit, upload, and download files over SFTP on any connected SSH host — and transfer files directly between two hosts without round-tripping through your machine.',
|
|
tips: ['Use the "Send to another host" action on a file row to start a host-to-host transfer; progress shows live in the panel at the bottom.'],
|
|
examples: [
|
|
{ text: 'Move a large backup folder from one VPS straight to another, without downloading gigabytes to your laptop first just to re-upload them.' },
|
|
],
|
|
},
|
|
{
|
|
icon: Box,
|
|
title: 'Containers',
|
|
description: 'Manage Docker containers on remote hosts — start, stop, view logs, and exec into a running container. Containers can come from a direct Docker API connection, an SSH host (runs the `docker` CLI for you), or a lightweight monitoring agent.',
|
|
tips: ['No Docker API port to expose? Add the host as an SSH Host integration instead — containers show up the same way, just routed through SSH.'],
|
|
examples: [
|
|
{ text: 'A container crashed on a VM you don\'t want to manually SSH into — pick the host on the Containers page, hit restart, and watch the new logs without leaving the browser.' },
|
|
],
|
|
},
|
|
{
|
|
icon: MonitorSmartphone,
|
|
title: 'Remote Desktop',
|
|
description: 'RDP, VNC, and Telnet sessions to remote machines, streamed through the built-in Guacamole proxy — no separate client needed.',
|
|
examples: [
|
|
{ text: 'Open a VNC session into a headless Ubuntu box to fix a stuck GUI app, entirely inside the browser tab — no VNC viewer install required.' },
|
|
],
|
|
},
|
|
{
|
|
icon: Gauge,
|
|
title: 'Host Metrics',
|
|
description: 'Live CPU, memory, disk, network, listening-port, firewall, process, and login-activity widgets for any SSH-managed host.',
|
|
examples: [
|
|
{ text: 'Catch a host slowly running out of disk space days before it takes a service down with it.' },
|
|
],
|
|
},
|
|
{
|
|
icon: Settings,
|
|
title: 'Settings',
|
|
description: 'Your profile, integrations, appearance, notifications, and full data export/import (back up or migrate every integration, bookmark, and tunnel as a single JSON file).',
|
|
examples: [
|
|
{ text: 'Export everything before reinstalling the OS on the box ArchNest itself runs on, then import it on the fresh install to get back to where you left off.' },
|
|
],
|
|
},
|
|
{
|
|
icon: Search,
|
|
title: 'Search (top bar)',
|
|
description: 'The search box at the top of every page looks across pages, integrations, and bookmarks at once — press Enter to jump to the top match.',
|
|
examples: [
|
|
{ text: 'Type "proxmox" and hit Enter to jump straight to your Proxmox integration, instead of clicking through to Infrastructure first.' },
|
|
],
|
|
},
|
|
]
|
|
|
|
const quickStartSteps = [
|
|
'Add at least one integration in Settings → Integrations — an SSH Host is the easiest first connection, since Terminal, Files, Tunnels, Host Metrics, and Containers can all use it.',
|
|
'Open Terminal or Files to confirm the connection actually works end to end.',
|
|
'If you need to reach something deeper in a private network (a DB, an internal site, a whole subnet), set up a Tunnel for it instead of opening more ports.',
|
|
'Bookmark the dashboards and tools you check often in BookNest, so the next visit is one click instead of a search.',
|
|
]
|
|
|
|
export default function Help() {
|
|
return (
|
|
<div className="scrollbar-ghost h-full overflow-y-auto p-8" style={{ maxWidth: '1100px' }}>
|
|
<div style={{ marginBottom: '24px' }}>
|
|
<h1 style={{ fontSize: '22px', color: '#E8E6E0', fontWeight: 700, marginBottom: '6px' }}>How ArchNest works</h1>
|
|
<p style={{ fontSize: '13px', color: '#7A7D85' }}>
|
|
A quick tour of every page and what it's for, with real examples for each. Use the sidebar to navigate, or the
|
|
search bar at the top to jump straight to something.
|
|
</p>
|
|
</div>
|
|
|
|
<div style={{ ...cardBase, marginBottom: '24px', borderColor: 'rgba(200,164,52,0.25)' }}>
|
|
<div className="flex items-center gap-3" style={{ marginBottom: '12px' }}>
|
|
<div
|
|
className="flex items-center justify-center"
|
|
style={{
|
|
width: '36px',
|
|
height: '36px',
|
|
borderRadius: '8px',
|
|
backgroundColor: 'rgba(200,164,52,0.14)',
|
|
border: '1px solid rgba(200,164,52,0.3)',
|
|
flexShrink: 0,
|
|
}}
|
|
>
|
|
<Rocket size={18} style={{ color: '#C8A434' }} />
|
|
</div>
|
|
<h3 style={{ fontSize: '14px', color: '#E8E6E0', fontWeight: 600 }}>New here? Start in this order</h3>
|
|
</div>
|
|
<ol style={{ paddingLeft: '20px', display: 'flex', flexDirection: 'column', gap: '6px' }}>
|
|
{quickStartSteps.map((step, i) => (
|
|
<li key={i} style={{ fontSize: '12.5px', color: '#A8A6A0', lineHeight: 1.6 }}>
|
|
{step}
|
|
</li>
|
|
))}
|
|
</ol>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
{guideEntries.map((entry) => {
|
|
const Icon = entry.icon
|
|
return (
|
|
<div key={entry.title} style={cardBase}>
|
|
<div className="flex items-center gap-3" style={{ marginBottom: '10px' }}>
|
|
<div
|
|
className="flex items-center justify-center"
|
|
style={{
|
|
width: '36px',
|
|
height: '36px',
|
|
borderRadius: '8px',
|
|
backgroundColor: 'rgba(200,164,52,0.1)',
|
|
border: '1px solid rgba(200,164,52,0.18)',
|
|
flexShrink: 0,
|
|
}}
|
|
>
|
|
<Icon size={18} style={{ color: '#C8A434' }} />
|
|
</div>
|
|
<h3 style={{ fontSize: '14px', color: '#E8E6E0', fontWeight: 600 }}>{entry.title}</h3>
|
|
</div>
|
|
<p style={{ fontSize: '12.5px', color: '#A8A6A0', lineHeight: 1.6 }}>{entry.description}</p>
|
|
{entry.tips && entry.tips.length > 0 && (
|
|
<ul style={{ marginTop: '10px', paddingLeft: '16px', display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
|
{entry.tips.map((tip, i) => (
|
|
<li key={i} style={{ fontSize: '11.5px', color: '#7A7D85', lineHeight: 1.5 }}>
|
|
{tip}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
{entry.examples && entry.examples.length > 0 && (
|
|
<div style={{ marginTop: '12px', display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
{entry.examples.map((ex, i) => {
|
|
const ExIcon = ex.icon ?? Lightbulb
|
|
return (
|
|
<div
|
|
key={i}
|
|
className="flex items-start gap-2"
|
|
style={{
|
|
padding: '8px 10px',
|
|
borderRadius: '8px',
|
|
backgroundColor: 'rgba(200,164,52,0.05)',
|
|
border: '1px solid rgba(200,164,52,0.1)',
|
|
}}
|
|
>
|
|
<ExIcon size={13} style={{ color: '#C8A434', flexShrink: 0, marginTop: '1px' }} />
|
|
<p style={{ fontSize: '11.5px', color: '#A8A6A0', lineHeight: 1.5 }}>
|
|
{ex.label && <span style={{ color: '#C8A434', fontWeight: 600 }}>{ex.label}: </span>}
|
|
{ex.text}
|
|
</p>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|