Add tutorial pages integrated into /docs

- New /docs page with 'Step-by-Step Tutorials' section featuring card grid
- 6 individual tutorial pages: getting-started, theme-customization, viewing-logs, quick-search, backup-restore, stats-monitoring
- Shared TutorialLayout component with consistent styling, screenshots, and troubleshooting
- Tutorials linked from main docs page with thumbnails, icons, and read times
- Next/prev navigation between tutorials
- All 30+ screenshots now served from public/tutorials/screenshots/
This commit is contained in:
Krystie
2026-05-01 21:50:14 -07:00
parent 3792c9c22d
commit 00aa123f73
8 changed files with 763 additions and 151 deletions

View File

@@ -0,0 +1,178 @@
import Link from 'next/link';
import Image from 'next/image';
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
interface TutorialStep {
title: string;
body: string;
screenshot?: string;
screenshotAlt?: string;
}
interface TroubleshootingItem {
issue: string;
solution: string;
}
interface TutorialLayoutProps {
title: string;
description: string;
icon: string;
heroScreenshot?: string;
steps: TutorialStep[];
troubleshooting?: TroubleshootingItem[];
prevTutorial?: { slug: string; title: string };
nextTutorial?: { slug: string; title: string };
}
export default function TutorialLayout({
title,
description,
icon,
heroScreenshot,
steps,
troubleshooting,
prevTutorial,
nextTutorial,
}: TutorialLayoutProps) {
return (
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
<Navbar />
{/* Hero */}
<section className="relative py-12 sm:py-16">
<div className="absolute inset-0 -z-10">
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 h-64 bg-brand-500/10 rounded-full blur-3xl opacity-30" />
</div>
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
<div className="flex items-center gap-3 mb-4">
<span className="text-3xl">{icon}</span>
<span className="text-surface-400 text-sm">
<Link href="/docs" className="hover:text-brand-400 transition-colors">Docs</Link>
{' / '}
<span className="text-surface-300">{title}</span>
</span>
</div>
<h1 className="text-3xl sm:text-4xl font-bold mb-4">{title}</h1>
<p className="text-lg text-surface-300 max-w-2xl">{description}</p>
</div>
</section>
{/* Hero Screenshot */}
{heroScreenshot && (
<section className="py-4">
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
<div className="rounded-xl overflow-hidden border border-surface-700/50">
<Image
src={heroScreenshot}
alt={title}
width={1200}
height={675}
className="w-full h-auto"
unoptimized
/>
</div>
</div>
</section>
)}
{/* Steps */}
<section className="py-8">
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
<div className="space-y-12">
{steps.map((step, i) => (
<div key={i} className="flex flex-col sm:flex-row gap-6">
<div className="flex-shrink-0 flex items-start gap-4 sm:flex-col sm:items-center sm:w-12">
<div className="flex-shrink-0 w-8 h-8 rounded-full bg-brand-500/20 text-brand-400 flex items-center justify-center font-bold text-sm sm:w-10 sm:h-10 sm:text-base">
{i + 1}
</div>
{i < steps.length - 1 && (
<div className="hidden sm:block w-px flex-1 bg-surface-700/50 min-h-[2rem]" />
)}
</div>
<div className="flex-1 pb-8">
<h2 className="text-xl font-semibold text-surface-50 mb-3">{step.title}</h2>
<p className="text-surface-300 leading-relaxed">{step.body}</p>
{step.screenshot && (
<div className="mt-4 rounded-lg overflow-hidden border border-surface-700/50">
<Image
src={step.screenshot}
alt={step.screenshotAlt || step.title}
width={900}
height={506}
className="w-full h-auto"
unoptimized
/>
</div>
)}
</div>
</div>
))}
</div>
</div>
</section>
{/* Troubleshooting */}
{troubleshooting && troubleshooting.length > 0 && (
<section className="py-12 bg-surface-900/30">
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
<h2 className="text-2xl font-bold text-surface-50 mb-6">Common Issues</h2>
<div className="space-y-4">
{troubleshooting.map(({ issue, solution }) => (
<div key={issue} className="p-5 rounded-lg border border-surface-700/40 bg-surface-800/20">
<div className="font-semibold text-surface-50 mb-2">{issue}</div>
<div className="text-surface-300 text-sm">{solution}</div>
</div>
))}
</div>
</div>
</section>
)}
{/* Navigation */}
<section className="py-8">
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
<div className="flex justify-between gap-4">
{prevTutorial ? (
<Link
href={`/docs/${prevTutorial.slug}`}
className="flex items-center gap-2 px-4 py-3 rounded-lg border border-surface-700/50 bg-surface-800/30 hover:border-brand-500/50 transition-colors text-surface-300 hover:text-brand-400"
>
<span></span>
<span>{prevTutorial.title}</span>
</Link>
) : (
<div />
)}
{nextTutorial ? (
<Link
href={`/docs/${nextTutorial.slug}`}
className="flex items-center gap-2 px-4 py-3 rounded-lg border border-surface-700/50 bg-surface-800/30 hover:border-brand-500/50 transition-colors text-surface-300 hover:text-brand-400"
>
<span>{nextTutorial.title}</span>
<span></span>
</Link>
) : (
<div />
)}
</div>
</div>
</section>
{/* Back to Docs */}
<section className="pb-12">
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
<Link
href="/docs"
className="text-brand-400 hover:text-brand-300 transition-colors text-sm"
>
Back to all documentation
</Link>
</div>
</section>
<Footer />
</div>
);
}

View File

@@ -0,0 +1,61 @@
import TutorialLayout from '../_components/TutorialLayout';
const backupRestore = {
title: 'Backup & Restore',
icon: '💾',
description: 'Protect your configuration with full backups. Export and restore services, credentials, themes, and all settings in one file.',
heroScreenshot: '/tutorials/screenshots/06-backup-restore/light/02-backup-modal-open.png',
steps: [
{
title: 'Access the Backup Modal',
body: 'The Backup feature is found in the TOOLS section of the dashboard. Navigate to TOOLS and click on Backup to open the Backup & Restore modal.',
screenshot: '/tutorials/screenshots/06-backup-restore/light/01-dashboard.png',
},
{
title: 'The Backup Modal Interface',
body: 'The Backup modal opens with three tabs: Manual, Automated, and History. The modal displays a title "💾 Backup & Restore" and a description: "Full backup of your entire DashCaddy setup — server config, credentials, themes, and browser preferences in one file."',
screenshot: '/tutorials/screenshots/06-backup-restore/light/02-backup-modal-open.png',
},
{
title: 'Create a Manual Backup',
body: 'The Manual tab is selected by default. Click "⬇️ Download Full Backup" to download a complete backup of your configuration. The backup file includes all service configurations, Caddy reverse proxy configuration, encrypted credentials, encryption keys, custom themes, and browser preferences. Save this file in a safe location.',
},
{
title: 'Restore from Backup',
body: 'The 📥 Restore Backup section lets you restore from a previously saved backup file. Click the upload/restore button, select your backup file from your computer, and confirm the restore operation. Warning: restoring a backup will replace your current configuration, so export your current config first if needed.',
},
{
title: 'Set Up Automated Backups',
body: 'The Automated tab lets you schedule regular backups so you always have recent restore points. Configure the backup frequency (daily, weekly, etc.) and retention settings. Automated backups ensure you can recover from configuration errors without losing recent changes.',
},
],
troubleshooting: [
{
issue: 'Download button not working',
solution: 'Check browser popup blocker settings. Try a different browser if downloads aren\'t working.',
},
{
issue: 'Restore fails',
solution: 'Verify the backup file is complete and not corrupted. Check that the file was created on the same DashCaddy version.',
},
{
issue: 'Forgotten encryption key',
solution: 'Without the encryption key from backup, some credentials may not restore. Always keep backup files and encryption keys together.',
},
],
};
export default function BackupRestorePage() {
return (
<TutorialLayout
title={backupRestore.title}
description={backupRestore.description}
icon={backupRestore.icon}
heroScreenshot={backupRestore.heroScreenshot}
steps={backupRestore.steps}
troubleshooting={backupRestore.troubleshooting}
prevTutorial={{ slug: 'quick-search', title: 'Quick Search' }}
nextTutorial={{ slug: 'stats-monitoring', title: 'Stats Monitoring' }}
/>
);
}

View File

@@ -0,0 +1,58 @@
import TutorialLayout from '../_components/TutorialLayout';
const gettingStarted = {
title: 'Getting Started',
icon: '🚀',
description: 'First-time setup: TOTP login, timezone configuration, and deployment mode selection. Get your dashboard ready in minutes.',
heroScreenshot: '/tutorials/screenshots/01-getting-started/light/01-initial-dashboard.png',
steps: [
{
title: 'Access the Dashboard',
body: 'Navigate to your DashCaddy instance URL (e.g., https://test.dashcaddy.net/). You\'ll be greeted with a TOTP authentication overlay asking for a 6-digit code. Generate your TOTP code using your authenticator app and enter the 6 digits. The inputs auto-advance as you type each digit.',
screenshot: '/tutorials/screenshots/01-getting-started/light/02-timezone-selected.png',
},
{
title: 'Select Your Timezone',
body: 'After successful login, the Setup Wizard appears. The first step asks for your timezone. Scroll or search for your timezone in the dropdown (e.g., "America/Los_Angeles" for Pacific time). Click to select it.',
screenshot: '/tutorials/screenshots/01-getting-started/light/02-timezone-selected.png',
},
{
title: 'Choose a Deployment Mode',
body: 'Click Continue to proceed to the deployment mode selection. Choose the mode that matches your setup: Simple (IP:Port only, no DNS), Professional Home Lab (custom TLD + private DNS + internal CA), Public Server (real domain + Let\'s Encrypt), or Custom (full control). Click Continue or skip setup to access the dashboard.',
screenshot: '/tutorials/screenshots/01-getting-started/light/03-deployment-mode-selection.png',
},
{
title: 'Complete or Skip Setup',
body: 'Depending on your chosen mode, you may see additional configuration screens. You can fill in the requested information and click Continue, or click Skip Setup to postpone configuration and access the dashboard immediately. After completing setup, you\'ll see the main dashboard with weather widget, clock, status cards, and navigation sections (STATUS, TOOLS, ADMIN).',
screenshot: '/tutorials/screenshots/01-getting-started/light/05-after-simple-select.png',
},
],
troubleshooting: [
{
issue: 'TOTP code rejected',
solution: 'Ensure your device clock is synchronized (TOTP is time-based). Try regenerating the code — they expire every 30 seconds.',
},
{
issue: 'Setup wizard shows no apps',
solution: 'The wizard is for initial configuration. Apps are added via TOOLS → App Selector after setup is complete.',
},
{
issue: '"Continue" button not clickable',
solution: 'Try scrolling down — the button may be below the viewport on smaller screens.',
},
],
};
export default function GettingStartedPage() {
return (
<TutorialLayout
title={gettingStarted.title}
description={gettingStarted.description}
icon={gettingStarted.icon}
heroScreenshot={gettingStarted.heroScreenshot}
steps={gettingStarted.steps}
troubleshooting={gettingStarted.troubleshooting}
nextTutorial={{ slug: 'theme-customization', title: 'Theme Customization' }}
/>
);
}

View File

@@ -1,9 +1,189 @@
'use client';
import Link from 'next/link';
import Image from 'next/image';
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
const tutorials = [
{
slug: 'getting-started',
icon: '🚀',
title: 'Getting Started',
desc: 'First-time setup: TOTP login, timezone, and deployment mode selection. Get your dashboard ready in minutes.',
screenshot: '/tutorials/screenshots/01-getting-started/light/02-timezone-selected.png',
readTime: '5 min',
},
{
slug: 'theme-customization',
icon: '🎨',
title: 'Theme Customization',
desc: 'Customize the dashboard with 10 built-in presets (Dark, Nord, Dracula, Ocean, and more) or build your own color scheme.',
screenshot: '/tutorials/screenshots/03-theme-customization/light/04-theme-builder-open.png',
readTime: '5 min',
},
{
slug: 'viewing-logs',
icon: '📋',
title: 'Viewing Logs',
desc: 'Monitor your services with real-time log streaming. Filter by line count, pause live updates, and search through logs.',
screenshot: '/tutorials/screenshots/04-viewing-logs/light/02-logs-modal-open.png',
readTime: '3 min',
},
{
slug: 'quick-search',
icon: '🔍',
title: 'Quick Search',
desc: 'Find services and settings instantly with keyboard shortcuts. Press Ctrl+K to open, arrow keys to navigate.',
screenshot: '/tutorials/screenshots/05-quick-search/light/02-quick-search-open.png',
readTime: '2 min',
},
{
slug: 'backup-restore',
icon: '💾',
title: 'Backup & Restore',
desc: 'Protect your configuration with full backups. Export and restore services, credentials, themes, and all settings.',
screenshot: '/tutorials/screenshots/06-backup-restore/light/02-backup-modal-open.png',
readTime: '4 min',
},
{
slug: 'stats-monitoring',
icon: '📊',
title: 'Stats Monitoring',
desc: 'Track CPU, memory, network, and disk usage in real-time. View live stats, 24h summaries, and set up alerts.',
screenshot: '/tutorials/screenshots/07-stats-monitoring/light/02-stats-modal-open.png',
readTime: '4 min',
},
];
const deploymentSteps = [
{
step: '1',
title: 'Choose an App',
desc: 'Browse by category or search. Each template includes the Docker image, port mappings, volume mounts, and secrets configuration.',
},
{
step: '2',
title: 'Configure Routing',
desc: 'Pick subdomain mode (plex.yourdomain.com) or subdirectory mode (yourdomain.com/sonarr). Subdomain needs DNS; subdirectory works immediately.',
},
{
step: '3',
title: 'Set Secrets',
desc: 'DashCaddy auto-generates secure passwords for admin accounts and API keys. You can also provide your own.',
},
{
step: '4',
title: 'Deploy',
desc: "DashCaddy pulls the image, creates the container, configures Caddy for reverse proxy, and issues a Let's Encrypt SSL certificate. Your app is live in minutes.",
},
];
const features = [
{ icon: '🚀', title: 'Deploy 75+ Apps', desc: 'Plex, Nextcloud, Jellyfin, Sonarr, and more' },
{ icon: '⚡', title: 'Automatic SSL', desc: 'Every app gets a valid HTTPS certificate' },
{ icon: '🌐', title: 'Private DNS', desc: 'Technitium DNS for .sami and .home zones' },
{ icon: '🔒', title: 'One-Click CA', desc: 'Install internal root cert on all devices' },
{ icon: '📺', title: 'Media Automation', desc: 'ARR Smart Connect: Plex + Radarr + Sonarr in one click' },
{ icon: '🐝', title: 'Docker Swarm', desc: 'Deploy across a cluster of servers (Premium)' },
];
const featureBlocks = [
{
icon: '⚡', title: 'Automatic SSL',
body: "Caddy handles HTTPS automatically via Let's Encrypt. Every app gets a valid certificate — no manual certbot, no renewals, no expired certs.",
},
{
icon: '🌐', title: 'Private DNS Zones',
body: 'Deploy Technitium DNS Server to manage .sami or .home zones. plex.sami, vaultwarden.sami, jellyfin.sami — all resolve internally on your network.',
},
{
icon: '🔒', title: 'DashCA — One-Click CA',
body: 'Deploy once, visit http://ca.sami from any device, install the root certificate. All your internal *.sami domains show as fully trusted. Works on Windows, macOS, Linux, iOS, Android.',
},
{
icon: '📺', title: 'ARR Smart Connect',
body: 'One-click setup connecting Plex + Radarr + Sonarr + Prowlarr + Seerr. Detects your Plex libraries, wires up Radarr/Sonarr automatically, configures indexers. Minutes instead of days.',
},
{
icon: '🔐', title: 'TOTP 2FA',
body: 'The dashboard itself supports optional TOTP two-factor authentication. Enable it in Settings to protect admin access.',
},
{
icon: '🐝', title: 'Docker Swarm (Premium)',
body: 'Scale beyond one server. Deploy apps across a cluster of multiple servers from a single DashCaddy control plane.',
},
{
icon: '🍯', title: 'Recipes (Premium)',
body: 'One-click app stacks — deploy a complete "media server" or "homelab" in a single action instead of deploying apps one by one.',
},
{
icon: '🔑', title: 'SSO (Premium)',
body: 'Connect Authentik as your identity provider. One login for DashCaddy, Nextcloud, Jellyfin, Gitea, and any OAuth2/OIDC-compatible app.',
},
];
const appCategories = [
{ cat: 'Media Streaming', icon: '🎬', apps: 'Plex, Jellyfin, Emby, Navidrome, Calibre-Web, Kavita, Komga, Audiobookshelf, Airsonic' },
{ cat: 'Media Automation', icon: '📺', apps: 'Sonarr, Radarr, Prowlarr, Bazarr, Lidarr, Readarr, Tautulli, Seerr' },
{ cat: 'Downloads', icon: '⬇️', apps: 'qBittorrent, Transmission, SABnzbd, NZBGet, JDownloader 2' },
{ cat: 'DNS & Networking', icon: '🌐', apps: 'Technitium DNS, BIND9, PowerDNS, CoreDNS, Pi-hole, WireGuard VPN' },
{ cat: 'Security', icon: '🔒', apps: 'DashCA, Vaultwarden, Authentik, CrowdSec' },
{ cat: 'DevOps', icon: '⚙️', apps: 'Gitea, Jenkins, Drone CI, VS Code Server' },
{ cat: 'Productivity', icon: '📋', apps: 'Nextcloud, Outline, BookStack, Standard Notes, Trilium, Mealie, Excalidraw, Actual Budget' },
{ cat: 'Database', icon: '🗄️', apps: 'PostgreSQL, Redis, MongoDB, Adminer' },
{ cat: 'Photos', icon: '📷', apps: 'Immich, PhotoPrism' },
{ cat: 'Home Automation', icon: '🏠', apps: 'Home Assistant, Node-RED' },
{ cat: 'Communication', icon: '💬', apps: 'Rocket.Chat, Matrix Synapse, Roundcube, Docker Mailserver' },
{ cat: 'Gaming', icon: '🎮', apps: 'Minecraft Server, Valheim Server' },
];
const troubleshooting = [
{
q: "App shows 'Configuring' after deployment",
a: "Check the app's logs in Dozzle or Portainer. Verify the port isn't already in use. Make sure volume mounts have correct permissions.",
},
{
q: 'SSL certificate not issued',
a: 'Verify your DNS is pointed to your server\'s IP. Check that ports 80 and 443 are open. Give Caddy 1-2 minutes after first deploy.',
},
{
q: "Can't access app after deployment",
a: 'Try the subdomain directly (DNS may need to propagate). Check Caddy routing rules in the dashboard. Verify the container is running in Portainer.',
},
{
q: "License won't activate",
a: 'Make sure the machine is connected to the internet. Check the machine\'s clock is set correctly. Try deactivating the old machine first if moving to a new server.',
},
{
q: 'Subscriptions not activating after payment',
a: 'The Stripe webhook can take a few minutes to arrive. If it doesn\'t arrive within 10 minutes, contact support with your Stripe payment reference.',
},
];
const integrations = [
{
title: 'Technitium DNS Server', icon: '🌐',
desc: 'Self-hosted DNS with web UI. Manage private zones (.sami, .home) on your local network. Supports DNSSEC, DNS-over-HTTPS, and DNS-over-TLS.',
setup: 'Deploy the template → Access at https://dns1.sami → Create your zone → Add A records → Point your router to your DNS server',
},
{
title: 'Pi-hole', icon: '🛡️',
desc: 'Network-wide ad blocking DNS sinkhole. All DNS queries on your network pass through Pi-hole — ads and trackers get blocked at the DNS level.',
setup: 'Deploy in one click. Point your router or devices to the Pi-hole IP. No configuration needed.',
},
{
title: 'WireGuard VPN', icon: '🔐',
desc: 'Fast, modern VPN tunnel. Deploy the server, generate client config files, share with family or teammates. Full VPN access from anywhere.',
setup: 'Deploy the template → Download client configs → Share with users → Connect from any device',
},
{
title: 'DashCA (Internal CA)', icon: '🔒',
desc: 'Built-in certificate authority. Install the root certificate once, all your internal *.sami domains are trusted on every device on your network.',
setup: 'Deploy DashCA → Visit http://ca.sami from any device → Click Install Certificate → Follow your OS instructions',
},
];
export default function DocsPage() {
return (
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
@@ -24,20 +204,52 @@ export default function DocsPage() {
</div>
</section>
{/* Tutorials Section */}
<section className="py-12 sm:py-16 bg-surface-900/30">
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
<h2 className="text-3xl font-bold text-surface-50 mb-2">Step-by-Step Tutorials</h2>
<p className="text-surface-400 mb-8">
Learn the dashboard by following along. Each guide walks through a real workflow with screenshots.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{tutorials.map((t) => (
<Link
key={t.slug}
href={`/docs/${t.slug}`}
className="group block rounded-xl border border-surface-700/50 bg-surface-800/50 overflow-hidden hover:border-brand-500/50 transition-colors"
>
<div className="relative h-36 bg-surface-700/30 overflow-hidden">
<Image
src={t.screenshot}
alt={t.title}
fill
className="object-cover opacity-80 group-hover:opacity-100 transition-opacity"
unoptimized
/>
<div className="absolute top-2 right-2 text-xs bg-surface-900/80 text-surface-300 px-2 py-0.5 rounded">
{t.readTime} read
</div>
</div>
<div className="p-4">
<div className="flex items-center gap-2 mb-1">
<span className="text-lg">{t.icon}</span>
<h3 className="font-semibold text-surface-50 group-hover:text-brand-400 transition-colors">{t.title}</h3>
</div>
<p className="text-surface-400 text-sm leading-relaxed">{t.desc}</p>
</div>
</Link>
))}
</div>
</div>
</section>
{/* Table of Contents */}
<section className="py-8">
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
<div className="rounded-lg border border-surface-700/50 bg-surface-800/50 p-8">
<h2 className="text-2xl font-bold text-surface-50 mb-6">What You Can Do</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{[
{ icon: '🚀', title: 'Deploy 75+ Apps', desc: 'Plex, Nextcloud, Jellyfin, Sonarr, and more' },
{ icon: '⚡', title: 'Automatic SSL', desc: 'Every app gets a valid HTTPS certificate' },
{ icon: '🌐', title: 'Private DNS', desc: 'Technitium DNS for .sami and .home zones' },
{ icon: '🔒', title: 'One-Click CA', desc: 'Install internal root cert on all devices' },
{ icon: '📺', title: 'Media Automation', desc: 'ARR Smart Connect: Plex + Radarr + Sonarr in one click' },
{ icon: '🐝', title: 'Docker Swarm', desc: 'Deploy across a cluster of servers (Premium)' },
].map(({ icon, title, desc }) => (
{features.map(({ icon, title, desc }) => (
<div key={title} className="flex gap-3 p-4 rounded-lg bg-surface-700/30">
<span className="text-2xl">{icon}</span>
<div>
@@ -55,20 +267,7 @@ export default function DocsPage() {
<section className="py-12 sm:py-16">
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
<h2 className="text-3xl font-bold text-surface-50 mb-8">75+ App Templates</h2>
{[
{ cat: 'Media Streaming', icon: '🎬', apps: 'Plex, Jellyfin, Emby, Navidrome, Calibre-Web, Kavita, Komga, Audiobookshelf, Airsonic' },
{ cat: 'Media Automation', icon: '📺', apps: 'Sonarr, Radarr, Prowlarr, Bazarr, Lidarr, Readarr, Tautulli, Seerr' },
{ cat: 'Downloads', icon: '⬇️', apps: 'qBittorrent, Transmission, SABnzbd, NZBGet, JDownloader 2' },
{ cat: 'DNS & Networking', icon: '🌐', apps: 'Technitium DNS, BIND9, PowerDNS, CoreDNS, Pi-hole, WireGuard VPN' },
{ cat: 'Security', icon: '🔒', apps: 'DashCA, Vaultwarden, Authentik, CrowdSec' },
{ cat: 'DevOps', icon: '⚙️', apps: 'Gitea, Jenkins, Drone CI, VS Code Server' },
{ cat: 'Productivity', icon: '📋', apps: 'Nextcloud, Outline, BookStack, Standard Notes, Trilium, Mealie, Excalidraw, Actual Budget' },
{ cat: 'Database', icon: '🗄️', apps: 'PostgreSQL, Redis, MongoDB, Adminer' },
{ cat: 'Photos', icon: '📷', apps: 'Immich, PhotoPrism' },
{ cat: 'Home Automation', icon: '🏠', apps: 'Home Assistant, Node-RED' },
{ cat: 'Communication', icon: '💬', apps: 'Rocket.Chat, Matrix Synapse, Roundcube, Docker Mailserver' },
{ cat: 'Gaming', icon: '🎮', apps: 'Minecraft Server, Valheim Server' },
].map(({ cat, icon, apps }) => (
{appCategories.map(({ cat, icon, apps }) => (
<div key={cat} className="mb-6 p-5 rounded-lg border border-surface-700/40 bg-surface-800/20">
<div className="flex items-center gap-2 mb-2">
<span>{icon}</span>
@@ -84,30 +283,8 @@ export default function DocsPage() {
<section className="py-12 sm:py-16 bg-surface-900/30">
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
<h2 className="text-3xl font-bold text-surface-50 mb-8">How App Deployment Works</h2>
<div className="space-y-6">
{[
{
step: '1',
title: 'Choose an App',
desc: 'Browse by category or search. Each template includes the Docker image, port mappings, volume mounts, and secrets configuration.',
},
{
step: '2',
title: 'Configure Routing',
desc: 'Pick subdomain mode (plex.yourdomain.com) or subdirectory mode (yourdomain.com/sonarr). Subdomain needs DNS; subdirectory works immediately.',
},
{
step: '3',
title: 'Set Secrets',
desc: 'DashCaddy auto-generates secure passwords for admin accounts and API keys. You can also provide your own.',
},
{
step: '4',
title: 'Deploy',
desc: 'DashCaddy pulls the image, creates the container, configures Caddy for reverse proxy, and issues a Let\'s Encrypt SSL certificate. Your app is live in minutes.',
},
].map(({ step, title, desc }) => (
{deploymentSteps.map(({ step, title, desc }) => (
<div key={step} className="flex gap-5">
<div className="flex-shrink-0 w-10 h-10 rounded-full bg-brand-500/20 text-brand-400 flex items-center justify-center font-bold text-lg">
{step}
@@ -126,50 +303,8 @@ export default function DocsPage() {
<section className="py-12 sm:py-16">
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
<h2 className="text-3xl font-bold text-surface-50 mb-8">Platform Features</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{[
{
icon: '⚡',
title: 'Automatic SSL',
body: 'Caddy handles HTTPS automatically via Let\'s Encrypt. Every app gets a valid certificate — no manual certbot, no renewals, no expired certs.',
},
{
icon: '🌐',
title: 'Private DNS Zones',
body: 'Deploy Technitium DNS Server to manage .sami or .home zones. plex.sami, vaultwarden.sami, jellyfin.sami — all resolve internally on your network.',
},
{
icon: '🔒',
title: 'DashCA — One-Click CA',
body: 'Deploy once, visit http://ca.sami from any device, install the root certificate. All your internal *.sami domains show as fully trusted. Works on Windows, macOS, Linux, iOS, Android.',
},
{
icon: '📺',
title: 'ARR Smart Connect',
body: 'One-click setup connecting Plex + Radarr + Sonarr + Prowlarr + Seerr. Detects your Plex libraries, wires up Radarr/Sonarr automatically, configures indexers. Minutes instead of days.',
},
{
icon: '🔐',
title: 'TOTP 2FA',
body: 'The dashboard itself supports optional TOTP two-factor authentication. Enable it in Settings to protect admin access.',
},
{
icon: '🐝',
title: 'Docker Swarm (Premium)',
body: 'Scale beyond one server. Deploy apps across a cluster of multiple servers from a single DashCaddy control plane.',
},
{
icon: '🍯',
title: 'Recipes (Premium)',
body: 'One-click app stacks — deploy a complete "media server" or "homelab" in a single action instead of deploying apps one by one.',
},
{
icon: '🔑',
title: 'SSO (Premium)',
body: 'Connect Authentik as your identity provider. One login for DashCaddy, Nextcloud, Jellyfin, Gitea, and any OAuth2/OIDC-compatible app.',
},
].map(({ icon, title, body }) => (
{featureBlocks.map(({ icon, title, body }) => (
<div key={title} className="p-6 rounded-lg border border-surface-700/40 bg-surface-800/20">
<div className="flex items-center gap-3 mb-3">
<span className="text-2xl">{icon}</span>
@@ -186,7 +321,6 @@ export default function DocsPage() {
<section className="py-12 sm:py-16 bg-surface-900/30">
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
<h2 className="text-3xl font-bold text-surface-50 mb-8">Pricing & Licensing</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
{/* Free */}
<div className="rounded-xl border border-surface-600 bg-surface-800/40 p-8">
@@ -201,7 +335,7 @@ export default function DocsPage() {
'Subdomain + subdirectory routing',
'Built-in DNS integration',
'TOTP 2FA on dashboard',
].map(f => (
].map((f) => (
<li key={f} className="flex items-start gap-2 text-surface-300 text-sm">
<span className="text-green-400 mt-0.5"></span>
{f}
@@ -222,22 +356,14 @@ export default function DocsPage() {
<div className="text-3xl font-bold text-surface-50 mb-1">$25 <span className="text-lg font-normal text-surface-400">/ month</span></div>
<p className="text-surface-400 text-sm mb-4">or $99/year all premium features</p>
<ul className="space-y-3 mb-6">
{[
'Everything in Core',
'SSO — Authentik single sign-on',
'Recipes — one-click app stacks',
'Docker Swarm cluster management',
'Priority support',
].map(f => (
{['Everything in Core', 'SSO — Authentik single sign-on', 'Recipes — one-click app stacks', 'Docker Swarm cluster management', 'Priority support'].map((f) => (
<li key={f} className="flex items-start gap-2 text-surface-300 text-sm">
<span className="text-brand-400 mt-0.5"></span>
{f}
</li>
))}
{['premium_1m: $25/mo', 'premium_3m: $50', 'premium_6m: $65', 'premium_12m: $99/yr'].map((p, i) => (
<li key={p} className="flex items-start gap-2 text-surface-400 text-xs ml-4">
{p}
</li>
{['premium_1m: $25/mo', 'premium_3m: $50', 'premium_6m: $65', 'premium_12m: $99/yr'].map((p) => (
<li key={p} className="flex items-start gap-2 text-surface-400 text-xs ml-4">{p}</li>
))}
</ul>
<Link href="/pricing" className="block text-center py-3 px-6 rounded-lg bg-brand-500 hover:bg-brand-400 text-white font-semibold transition-colors">
@@ -245,7 +371,6 @@ export default function DocsPage() {
</Link>
</div>
</div>
<div className="p-4 rounded-lg bg-surface-800/40 border border-surface-700/40 text-sm text-surface-300">
<strong className="text-surface-100">License model:</strong> One machine at a time. Machine-bound fingerprint. Subscriptions cancel at period end. 7-day grace period on failed payments. Deactivate anytime to move to a new machine. No free trial.
</div>
@@ -256,33 +381,7 @@ export default function DocsPage() {
<section className="py-12 sm:py-16">
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
<h2 className="text-3xl font-bold text-surface-50 mb-8">Infrastructure Integrations</h2>
{[
{
title: 'Technitium DNS Server',
icon: '🌐',
desc: 'Self-hosted DNS with web UI. Manage private zones (.sami, .home) on your local network. Supports DNSSEC, DNS-over-HTTPS, and DNS-over-TLS.',
setup: 'Deploy the template → Access at https://dns1.sami → Create your zone → Add A records → Point your router to your DNS server',
},
{
title: 'Pi-hole',
icon: '🛡️',
desc: 'Network-wide ad blocking DNS sinkhole. All DNS queries on your network pass through Pi-hole — ads and trackers get blocked at the DNS level.',
setup: 'Deploy in one click. Point your router or devices to the Pi-hole IP. No configuration needed.',
},
{
title: 'WireGuard VPN',
icon: '🔐',
desc: 'Fast, modern VPN tunnel. Deploy the server, generate client config files, share with family or teammates. Full VPN access from anywhere.',
setup: 'Deploy the template → Download client configs → Share with users → Connect from any device',
},
{
title: 'DashCA (Internal CA)',
icon: '🔒',
desc: 'Built-in certificate authority. Install the root certificate once, all your internal *.sami domains are trusted on every device on your network.',
setup: 'Deploy DashCA → Visit http://ca.sami from any device → Click Install Certificate → Follow your OS instructions',
},
].map(({ title, icon, desc, setup }) => (
{integrations.map(({ title, icon, desc, setup }) => (
<div key={title} className="mb-8 p-6 rounded-lg border border-surface-700/40 bg-surface-800/20">
<div className="flex items-center gap-3 mb-3">
<span className="text-2xl">{icon}</span>
@@ -303,28 +402,7 @@ export default function DocsPage() {
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
<h2 className="text-3xl font-bold text-surface-50 mb-8">Troubleshooting</h2>
<div className="space-y-4">
{[
{
q: 'App shows "Configuring" after deployment',
a: 'Check the app\'s logs in Dozzle or Portainer. Verify the port isn\'t already in use. Make sure volume mounts have correct permissions.',
},
{
q: 'SSL certificate not issued',
a: 'Verify your DNS is pointed to your server\'s IP. Check that ports 80 and 443 are open. Give Caddy 1-2 minutes after first deploy.',
},
{
q: "Can't access app after deployment",
a: 'Try the subdomain directly (DNS may need to propagate). Check Caddy routing rules in the dashboard. Verify the container is running in Portainer.',
},
{
q: 'License won\'t activate',
a: 'Make sure the machine is connected to the internet. Check the machine\'s clock is set correctly. Try deactivating the old machine first if moving to a new server.',
},
{
q: 'Subscriptions not activating after payment',
a: 'The Stripe webhook can take a few minutes to arrive. If it doesn\'t arrive within 10 minutes, contact support with your Stripe payment reference.',
},
].map(({ q, a }) => (
{troubleshooting.map(({ q, a }) => (
<div key={q} className="p-5 rounded-lg border border-surface-700/40 bg-surface-800/20">
<div className="font-semibold text-surface-50 mb-2">{q}</div>
<div className="text-surface-300 text-sm">{a}</div>

View File

@@ -0,0 +1,58 @@
import TutorialLayout from '../_components/TutorialLayout';
const quickSearch = {
title: 'Quick Search',
icon: '🔍',
description: 'Find services and settings instantly with keyboard shortcuts. Press Ctrl+K to open, arrow keys to navigate.',
heroScreenshot: '/tutorials/screenshots/05-quick-search/light/02-quick-search-open.png',
steps: [
{
title: 'Open Quick Search',
body: 'Quick Search is accessed via a modal. Press Ctrl+K (or Cmd+K on Mac) to open it. Alternatively, you can access it from the dashboard header or menu if available.',
screenshot: '/tutorials/screenshots/05-quick-search/light/02-quick-search-open.png',
},
{
title: 'The Quick Search Interface',
body: 'When opened, the Quick Search modal appears centered on your screen with a search input field at the top. Below the input, keyboard shortcut hints are displayed: ESC to close, ↑↓ to navigate, Enter to select, and Esc to close again.',
screenshot: '/tutorials/screenshots/05-quick-search/light/02-quick-search-open.png',
},
{
title: 'Search for Services or Settings',
body: 'Click on the search input field and start typing. Quick Search supports fuzzy matching and shows results as you type. Search for service names (Plex, Gitea, Nextcloud), settings (backup, theme, notifications), or dashboard features (logs, stats, docker).',
screenshot: '/tutorials/screenshots/05-quick-search/light/03-quick-search-typed.png',
},
{
title: 'Navigate and Select Results',
body: 'Use the keyboard to navigate through search results: ↑/↓ arrows move the selection highlight up or down, Enter selects and opens the highlighted item, and ESC closes the Quick Search modal without selecting anything. Result types include services, settings pages, dashboard features, and documentation links.',
},
],
troubleshooting: [
{
issue: "Quick Search doesn't open",
solution: 'Try the keyboard shortcut (Ctrl+K or Cmd+K). If it still doesn\'t open, the feature may not be available in your version.',
},
{
issue: 'No results appearing',
solution: 'Try shorter search terms or check your spelling. Results depend on what services and settings are configured.',
},
{
issue: "Can't select with Enter",
solution: 'Make sure a result is highlighted (has keyboard focus) before pressing Enter.',
},
],
};
export default function QuickSearchPage() {
return (
<TutorialLayout
title={quickSearch.title}
description={quickSearch.description}
icon={quickSearch.icon}
heroScreenshot={quickSearch.heroScreenshot}
steps={quickSearch.steps}
troubleshooting={quickSearch.troubleshooting}
prevTutorial={{ slug: 'viewing-logs', title: 'Viewing Logs' }}
nextTutorial={{ slug: 'backup-restore', title: 'Backup & Restore' }}
/>
);
}

View File

@@ -0,0 +1,60 @@
import TutorialLayout from '../_components/TutorialLayout';
const statsMonitoring = {
title: 'Stats Monitoring',
icon: '📊',
description: 'Track CPU, memory, network, and disk usage in real-time. View live stats, 24h summaries, and set up alerts.',
heroScreenshot: '/tutorials/screenshots/07-stats-monitoring/light/02-stats-modal-open.png',
steps: [
{
title: 'Access the Resource Monitor',
body: 'The Stats monitoring is found in the STATUS section of the dashboard. Navigate to STATUS and click on Stats to open the Resource Monitor.',
screenshot: '/tutorials/screenshots/07-stats-monitoring/light/01-dashboard.png',
},
{
title: 'The Stats Modal Interface',
body: 'The Stats modal opens showing the 📊 Resource Monitor with a description: "Real-time and historical CPU, memory, network, and disk usage for containers." The modal has three tabs: Live Stats (real-time metrics), 24h Summary (aggregated over 24 hours), and Alerts (notifications).',
screenshot: '/tutorials/screenshots/07-stats-monitoring/light/02-stats-modal-open.png',
},
{
title: 'View Live Stats',
body: 'The Live Stats tab shows real-time resource usage for each container: CPU usage percentage, memory usage (used vs. available), network I/O rates, and disk I/O rates. The display auto-refreshes every 5 seconds. Use the 🔄 Refresh Now button to manually trigger a refresh. High CPU (>80%) sustained may indicate performance issues.',
},
{
title: 'Check 24h Summary',
body: 'The 24h Summary tab shows aggregated statistics over the past 24 hours: peak CPU usage, average memory usage, total network traffic, and disk usage trends. This view helps identify resource usage patterns and plan for capacity needs.',
},
{
title: 'View Alerts',
body: 'The Alerts tab shows any resource-related alerts: high CPU warnings, memory pressure alerts, disk space warnings, and network issues. Alerts help you stay on top of problems before they cause service disruptions. Configure alert thresholds in Settings if available.',
},
],
troubleshooting: [
{
issue: 'Stats show "Loading container stats..."',
solution: 'No containers are running, or Docker is not responding. Check that Docker is running and containers are active.',
},
{
issue: 'Auto-refresh not working',
solution: 'Click "🔄 Refresh Now" manually. Check that the Docker daemon is responding.',
},
{
issue: 'Missing container in stats',
solution: 'Container may have stopped. Check container status in Portainer or via the Docker CLI.',
},
],
};
export default function StatsMonitoringPage() {
return (
<TutorialLayout
title={statsMonitoring.title}
description={statsMonitoring.description}
icon={statsMonitoring.icon}
heroScreenshot={statsMonitoring.heroScreenshot}
steps={statsMonitoring.steps}
troubleshooting={statsMonitoring.troubleshooting}
prevTutorial={{ slug: 'backup-restore', title: 'Backup & Restore' }}
/>
);
}

View File

@@ -0,0 +1,62 @@
import TutorialLayout from '../_components/TutorialLayout';
const themeCustomization = {
title: 'Theme Customization',
icon: '🎨',
description: 'Customize the dashboard with 10 built-in presets (Dark, Nord, Dracula, Ocean, and more) or build your own color scheme from scratch.',
heroScreenshot: '/tutorials/screenshots/03-theme-customization/light/04-theme-builder-open.png',
steps: [
{
title: 'Open the Theme Builder',
body: 'Locate the theme button in the top bar of the dashboard. It\'s represented by a 🎨 icon with the text "Light" (showing your current theme). Click the 🎨 Light button or the Customize text to open the Theme Builder modal.',
screenshot: '/tutorials/screenshots/03-theme-customization/light/01-light-theme-dashboard.png',
},
{
title: 'Explore the Theme Builder',
body: 'The Theme Builder modal opens with several sections: a preset list (Dark, Light, Blue, Black, Nord, Dracula, Solarized Dark, Solarized Light, Taxi, Ocean), a preview card showing secondary text and accent button samples, and status indicators (Online/Offline). You can also rename your theme and see its current configuration.',
screenshot: '/tutorials/screenshots/03-theme-customization/light/04-theme-builder-open.png',
},
{
title: 'Select a Base Theme',
body: 'Click on one of the preset themes (e.g., "Dark") to start from that base. The preview card updates to reflect the selected preset\'s colors and styling. This gives you a starting point that you can then fine-tune.',
screenshot: '/tutorials/screenshots/03-theme-customization/dark/05-dark-preset-selected.png',
},
{
title: 'Customize Colors (Optional)',
body: 'Below the presets, find granular color controls organized into sections: Base Colors (Background, Card, Text, Muted Text, Border), Accent Colors (Accent, Accent Strong), and Status Colors (OK Background, OK Text, Error Bg, Error Text). Each field is editable — click on a color swatch or enter a hex value.',
},
{
title: 'Save Your Theme',
body: 'When satisfied with your customizations, click Save Theme at the bottom of the modal. The theme is applied to your dashboard immediately. Click Cancel to discard changes, or ✕ to close the modal without saving. You can also use Import and Export to backup or share themes.',
},
],
troubleshooting: [
{
issue: 'Theme builder button not visible',
solution: 'Scroll to top of dashboard — the button is in the top bar next to the settings gear.',
},
{
issue: 'Preset click not working',
solution: 'Try clicking directly on the preset name text, or scroll to ensure the preset list is fully visible.',
},
{
issue: 'Theme not saving',
solution: 'Ensure you click "Save Theme" before closing the modal. If the issue persists, try a different browser.',
},
],
};
export default function ThemeCustomizationPage() {
return (
<TutorialLayout
title={themeCustomization.title}
description={themeCustomization.description}
icon={themeCustomization.icon}
heroScreenshot={themeCustomization.heroScreenshot}
steps={themeCustomization.steps}
troubleshooting={themeCustomization.troubleshooting}
prevTutorial={{ slug: 'getting-started', title: 'Getting Started' }}
nextTutorial={{ slug: 'viewing-logs', title: 'Viewing Logs' }}
/>
);
}

View File

@@ -0,0 +1,57 @@
import TutorialLayout from '../_components/TutorialLayout';
const viewingLogs = {
title: 'Viewing Logs',
icon: '📋',
description: 'Monitor your services with real-time log streaming. Filter by line count, pause live updates, and search through logs.',
heroScreenshot: '/tutorials/screenshots/04-viewing-logs/light/02-logs-modal-open.png',
steps: [
{
title: 'Access the Logs Viewer',
body: 'The Logs viewer is found in the TOOLS section of the dashboard. Look for a "Logs" button (📋 Logs) in the TOOLS section. Click on it to open the logs viewer modal.',
screenshot: '/tutorials/screenshots/04-viewing-logs/light/01-dashboard.png',
},
{
title: 'The Logs Modal Interface',
body: 'The logs modal displays: a DNS Logs title, a "Show" dropdown to control line count (25, 50, 100, or 200 lines), live streaming controls (📡 Live and ⏸️ Pause buttons), and a close button (✕). The main content area shows the actual log entries with timestamps, service names, and messages.',
screenshot: '/tutorials/screenshots/04-viewing-logs/light/02-logs-modal-open.png',
},
{
title: 'Use Live Log Streaming',
body: 'Click the 📡 Live button to enable real-time log streaming. New log entries appear automatically at the top or bottom of the list. Click ⏸️ Pause to stop auto-updating so you can scroll through logs without new entries pushing content around.',
},
{
title: 'Control Log Volume',
body: 'Use the "Show" dropdown to control how many log lines are displayed at once. Choose 25 for a quick overview, 50 for the default view, 100 for more context, or 200 for detailed analysis. Fewer lines load faster and are easier to read.',
},
],
troubleshooting: [
{
issue: 'Logs show "Loading logs..."',
solution: 'No services are running, or services haven\'t generated logs yet. Check that your services are actually running in Docker.',
},
{
issue: 'Log entries not appearing',
solution: 'Live streaming may be paused — click 📡 Live to resume. Also check that the service is producing logs.',
},
{
issue: "Can't scroll through logs",
solution: 'Make sure pause (⏸️ Pause) is enabled to prevent new entries from continuously pushing content.',
},
],
};
export default function ViewingLogsPage() {
return (
<TutorialLayout
title={viewingLogs.title}
description={viewingLogs.description}
icon={viewingLogs.icon}
heroScreenshot={viewingLogs.heroScreenshot}
steps={viewingLogs.steps}
troubleshooting={viewingLogs.troubleshooting}
prevTutorial={{ slug: 'theme-customization', title: 'Theme Customization' }}
nextTutorial={{ slug: 'quick-search', title: 'Quick Search' }}
/>
);
}