Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fbd4f7be3 | ||
|
|
e780e5aa60 | ||
|
|
00aa123f73 | ||
|
|
3792c9c22d | ||
|
|
fbe1a7c95c | ||
|
|
4f03b8cf47 | ||
|
|
bb69b96816 | ||
|
|
aedaf4b4b5 | ||
|
|
f5104578c9 |
@@ -1,16 +1,7 @@
|
||||
# Stripe Configuration
|
||||
# Get your keys from https://dashboard.stripe.com/apikeys
|
||||
STRIPE_SECRET_KEY=sk_test_your_secret_key_here
|
||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_your_publishable_key_here
|
||||
|
||||
# Stripe Webhook Secret
|
||||
# Get this from https://dashboard.stripe.com/webhooks after creating an endpoint
|
||||
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret_here
|
||||
|
||||
# Stripe Price IDs
|
||||
# Create these in your Stripe Dashboard under Products > Pricing
|
||||
STRIPE_PRICE_MONTHLY=price_your_monthly_price_id
|
||||
STRIPE_PRICE_YEARLY=price_your_yearly_price_id
|
||||
|
||||
# App URL
|
||||
# DashCaddy website configuration
|
||||
NEXT_PUBLIC_APP_URL=https://dashcaddy.net
|
||||
NEXT_PUBLIC_LICENSE_SERVER_URL=https://licenses.dashcaddy.net
|
||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_your_publishable_key_here
|
||||
|
||||
# The website no longer owns Stripe secret or webhook handling.
|
||||
# Stripe Checkout sessions and webhook processing live in dashcaddy-license-server.
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
| `CLAUDE.md` | Project instructions for Claude |
|
||||
| `FILELIST.md` | This file |
|
||||
| `README.md` | Project overview and setup guide |
|
||||
| `STRIPE_SETUP.md` | Step-by-step Stripe configuration guide |
|
||||
| `STRIPE_SETUP.md` | Billing architecture and license-server setup guide |
|
||||
| `eslint.config.mjs` | ESLint configuration |
|
||||
| `next-env.d.ts` | Next.js TypeScript declarations |
|
||||
| `next.config.ts` | Next.js configuration |
|
||||
@@ -53,8 +53,8 @@
|
||||
|
||||
| File | Endpoint | Purpose |
|
||||
|------|----------|---------|
|
||||
| `src/app/api/checkout/route.ts` | `POST /api/checkout` | Creates Stripe Checkout session with 14-day trial |
|
||||
| `src/app/api/webhooks/stripe/route.ts` | `POST /api/webhooks/stripe` | Handles Stripe subscription lifecycle events |
|
||||
| `src/app/api/checkout/route.ts` | `POST /api/checkout` | Disabled compatibility stub; production checkout lives in dashcaddy-license-server |
|
||||
| `src/app/api/webhooks/stripe/route.ts` | `POST /api/webhooks/stripe` | Disabled compatibility stub; production webhook lives in dashcaddy-license-server |
|
||||
|
||||
## Components (`src/components/`)
|
||||
|
||||
|
||||
@@ -1,63 +1,63 @@
|
||||
# DashCaddy.net - Marketing Website
|
||||
|
||||
Marketing and sales website for [DashCaddy](https://git.dashcaddy.net/sami7777/dashcaddy), the self-hosted Docker dashboard with automatic SSL, DNS, and reverse proxy configuration.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Next.js 16** with App Router and TypeScript
|
||||
- **Tailwind CSS** for styling
|
||||
- **Stripe** for subscription payments ($20/mo or $99/yr)
|
||||
|
||||
## Getting Started
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Copy environment file and configure
|
||||
cp .env.example .env.local
|
||||
|
||||
# Run development server
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) to see the site.
|
||||
|
||||
## Pages
|
||||
|
||||
| Route | Description |
|
||||
|-------|-------------|
|
||||
| `/` | Landing page with hero, features, app showcase |
|
||||
| `/features` | Detailed feature breakdown |
|
||||
| `/pricing` | Pricing plans with Stripe Checkout |
|
||||
| `/docs` | Getting started guide and documentation |
|
||||
| `/about` | About page and company info |
|
||||
| `/success` | Post-checkout success page |
|
||||
|
||||
## Stripe Integration
|
||||
|
||||
See [STRIPE_SETUP.md](./STRIPE_SETUP.md) for detailed setup instructions.
|
||||
|
||||
**API Routes:**
|
||||
- `POST /api/checkout` - Creates Stripe Checkout session
|
||||
- `POST /api/webhooks/stripe` - Handles Stripe webhook events
|
||||
|
||||
## Logo Setup
|
||||
|
||||
Place your `samiahmed7777-logo.png` file in `public/images/` for the footer copyright branding.
|
||||
|
||||
## Deployment
|
||||
|
||||
```bash
|
||||
# Build for production
|
||||
npm run build
|
||||
|
||||
# Start production server
|
||||
npm start
|
||||
```
|
||||
|
||||
The site can be deployed to Vercel, your own server (with Node.js), or any platform that supports Next.js.
|
||||
|
||||
## License
|
||||
|
||||
Proprietary - DashCaddy
|
||||
# DashCaddy.net - Marketing Website
|
||||
|
||||
Marketing and sales website for [DashCaddy](https://git.dashcaddy.net/sami7777/dashcaddy), the self-hosted Docker dashboard with automatic SSL, DNS, and reverse proxy configuration.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Next.js 16** with App Router and TypeScript
|
||||
- **Tailwind CSS** for styling
|
||||
- **Stripe** for subscription payments ($20/mo or $99/yr)
|
||||
|
||||
## Getting Started
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Copy environment file and configure
|
||||
cp .env.example .env.local
|
||||
|
||||
# Run development server
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) to see the site.
|
||||
|
||||
## Pages
|
||||
|
||||
| Route | Description |
|
||||
|-------|-------------|
|
||||
| `/` | Landing page with hero, features, app showcase |
|
||||
| `/features` | Detailed feature breakdown |
|
||||
| `/pricing` | Pricing plans with Stripe Checkout |
|
||||
| `/docs` | Getting started guide and documentation |
|
||||
| `/about` | About page and company info |
|
||||
| `/success` | Post-checkout success page |
|
||||
|
||||
## Stripe Integration
|
||||
|
||||
See [STRIPE_SETUP.md](./STRIPE_SETUP.md) for the current billing and license-server setup.
|
||||
|
||||
**API Routes:**
|
||||
- Website pricing uses the external DashCaddy license server for checkout initiation.
|
||||
- Website-local Stripe routes are disabled compatibility stubs and are not the production billing path.
|
||||
|
||||
## Logo Setup
|
||||
|
||||
Place your `samiahmed7777-logo.png` file in `public/images/` for the footer copyright branding.
|
||||
|
||||
## Deployment
|
||||
|
||||
```bash
|
||||
# Build for production
|
||||
npm run build
|
||||
|
||||
# Start production server
|
||||
npm start
|
||||
```
|
||||
|
||||
The site can be deployed to Vercel, your own server (with Node.js), or any platform that supports Next.js.
|
||||
|
||||
## License
|
||||
|
||||
Proprietary - DashCaddy
|
||||
|
||||
@@ -1,83 +1,65 @@
|
||||
# Stripe Setup Guide for DashCaddy.net
|
||||
|
||||
## 1. Create a Stripe Account
|
||||
|
||||
Go to [stripe.com](https://stripe.com) and create an account (or log in).
|
||||
|
||||
## 2. Create Your Product and Prices
|
||||
|
||||
In the Stripe Dashboard:
|
||||
|
||||
1. Go to **Products** > **Add Product**
|
||||
2. Name: `DashCaddy Premium`
|
||||
3. Description: `Premium license for DashCaddy - Self-hosting dashboard`
|
||||
4. Create two prices:
|
||||
- **Monthly**: $20.00 USD / month (recurring)
|
||||
- **Yearly**: $99.00 USD / year (recurring)
|
||||
5. Note down both **Price IDs** (they look like `price_1234...`)
|
||||
|
||||
## 3. Get Your API Keys
|
||||
|
||||
1. Go to **Developers** > **API Keys**
|
||||
2. Copy your **Publishable key** (`pk_test_...` or `pk_live_...`)
|
||||
3. Copy your **Secret key** (`sk_test_...` or `sk_live_...`)
|
||||
|
||||
## 4. Set Up Webhooks
|
||||
|
||||
1. Go to **Developers** > **Webhooks**
|
||||
2. Click **Add endpoint**
|
||||
3. URL: `https://dashcaddy.net/api/webhooks/stripe`
|
||||
4. Select these events:
|
||||
- `checkout.session.completed`
|
||||
- `customer.subscription.updated`
|
||||
- `customer.subscription.deleted`
|
||||
- `invoice.payment_failed`
|
||||
5. Copy the **Webhook signing secret** (`whsec_...`)
|
||||
|
||||
## 5. Configure Environment Variables
|
||||
|
||||
Copy `.env.example` to `.env.local` and fill in your values:
|
||||
|
||||
```bash
|
||||
cp .env.example .env.local
|
||||
```
|
||||
|
||||
Edit `.env.local`:
|
||||
|
||||
```env
|
||||
STRIPE_SECRET_KEY=sk_live_your_actual_secret_key
|
||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_your_actual_publishable_key
|
||||
STRIPE_WEBHOOK_SECRET=whsec_your_actual_webhook_secret
|
||||
STRIPE_PRICE_MONTHLY=price_your_monthly_price_id
|
||||
STRIPE_PRICE_YEARLY=price_your_yearly_price_id
|
||||
NEXT_PUBLIC_APP_URL=https://dashcaddy.net
|
||||
```
|
||||
|
||||
## 6. Test with Stripe CLI (Optional)
|
||||
|
||||
For local development, use Stripe CLI to forward webhooks:
|
||||
|
||||
```bash
|
||||
stripe listen --forward-to localhost:3000/api/webhooks/stripe
|
||||
```
|
||||
|
||||
Use test card `4242 4242 4242 4242` with any future date and any CVC.
|
||||
|
||||
## 7. License Key Delivery
|
||||
|
||||
The webhook handler at `src/app/api/webhooks/stripe/route.ts` has TODO comments
|
||||
where you need to implement:
|
||||
|
||||
1. **Generate license key** using the same format as DashCaddy's license-keygen
|
||||
(DC-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX)
|
||||
2. **Store it** in a database (Stripe metadata can also hold it)
|
||||
3. **Email it** to the customer (use Stripe's receipt email or a service like
|
||||
SendGrid/Resend)
|
||||
4. **Link it** to the Stripe subscription ID so you can manage renewals/cancellations
|
||||
|
||||
## Pricing Strategy Notes
|
||||
|
||||
- **Monthly ($20/mo)**: Positioned as the flexibility option
|
||||
- **Yearly ($99/yr)**: ~$8.25/mo — 58% savings, this will be the primary seller
|
||||
- **14-day free trial**: Enabled on both plans via `trial_period_days: 14`
|
||||
- **Promotion codes**: Enabled via `allow_promotion_codes: true`
|
||||
# DashCaddy Billing Setup
|
||||
|
||||
DashCaddy website no longer processes Stripe webhooks locally.
|
||||
|
||||
Current architecture:
|
||||
- `dashcaddy.net` handles pricing UI and starts checkout by calling the external license server
|
||||
- `dashcaddy-license-server` owns Stripe secret usage, webhook handling, subscription state, and license generation
|
||||
- DashCaddy app instances validate/deactivate against the external license server
|
||||
|
||||
## Required website environment
|
||||
|
||||
```env
|
||||
NEXT_PUBLIC_APP_URL=https://dashcaddy.net
|
||||
NEXT_PUBLIC_LICENSE_SERVER_URL=https://licenses.dashcaddy.net
|
||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_your_publishable_key_here
|
||||
```
|
||||
|
||||
## Required license server environment
|
||||
|
||||
```env
|
||||
PORT=3010
|
||||
APP_BASE_URL=https://licenses.dashcaddy.net
|
||||
DASHCADDY_WEBSITE_URL=https://dashcaddy.net
|
||||
STRIPE_SECRET_KEY=sk_live_...
|
||||
STRIPE_PUBLISHABLE_KEY=pk_live_...
|
||||
STRIPE_WEBHOOK_SECRET=whsec_...
|
||||
```
|
||||
|
||||
## Stripe product model
|
||||
|
||||
One Premium tier, four subscription cadences:
|
||||
- 1 month — $25
|
||||
- 3 months — $50
|
||||
- 6 months — $65
|
||||
- 12 months — $99
|
||||
|
||||
No free trial.
|
||||
7-day grace period.
|
||||
Cancel at period end.
|
||||
One active machine at a time.
|
||||
Premium features only: `sso`, `recipes`, `swarm`.
|
||||
|
||||
## Stripe webhook target
|
||||
|
||||
Configure Stripe to send events to the license server, not the website.
|
||||
|
||||
Recommended webhook endpoint:
|
||||
|
||||
```text
|
||||
https://licenses.dashcaddy.net/api/stripe/webhook
|
||||
```
|
||||
|
||||
Recommended events:
|
||||
- `checkout.session.completed`
|
||||
- `customer.subscription.created`
|
||||
- `customer.subscription.updated`
|
||||
- `customer.subscription.deleted`
|
||||
- `invoice.payment_failed`
|
||||
|
||||
## Website behavior
|
||||
|
||||
The website pricing page should start checkout via:
|
||||
- `POST /api/checkout/session` on the external license server
|
||||
|
||||
The website-local routes under `src/app/api/checkout` and `src/app/api/webhooks/stripe` are intentionally disabled compatibility stubs and should not be used for production billing.
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# DashCaddy Tutorial Project Summary
|
||||
|
||||
## Workflows Covered
|
||||
|
||||
1. **Getting Started** - Setup wizard, timezone selection, deployment mode selection
|
||||
2. **Theme Customization** - Theme builder modal, preset themes, color customization
|
||||
3. **Viewing Logs** - Log viewer modal, live streaming, log filtering
|
||||
4. **Quick Search** - Quick search modal, keyboard shortcuts, navigation
|
||||
5. **Backup & Restore** - Manual backup, automated backup, restore workflow
|
||||
6. **Stats Monitoring** - Resource monitor, live stats, alerts view
|
||||
|
||||
## Themes Captured
|
||||
|
||||
- **Light theme**: Full coverage across all workflows
|
||||
- **Dark theme**: Partial - theme builder modal captured, but quick theme toggle button doesn't switch themes (it's display-only)
|
||||
|
||||
## Screenshots
|
||||
|
||||
**Total screenshots: 30+**
|
||||
|
||||
Organized by workflow:
|
||||
- `01-getting-started/` - 5 screenshots (light)
|
||||
- `02-deploy-app/` - 5 screenshots (light, but app selector was empty)
|
||||
- `03-theme-customization/` - 5 screenshots (2 dark, 3 light)
|
||||
- `04-viewing-logs/` - 4 screenshots (light)
|
||||
- `05-quick-search/` - 3 screenshots (light)
|
||||
- `06-backup-restore/` - 2 screenshots (light)
|
||||
- `07-stats-monitoring/` - 2 screenshots (light)
|
||||
- Plus additional test/diagnostic screenshots
|
||||
|
||||
## Files Added to dashcaddy.net Repo
|
||||
|
||||
### Source Tutorials (`src/tutorials/`)
|
||||
- 01-getting-started.md
|
||||
- 02-theme-customization.md
|
||||
- 03-viewing-logs.md
|
||||
- 04-quick-search.md
|
||||
- 05-backup-restore.md
|
||||
- 06-stats-monitoring.md
|
||||
|
||||
### Public Files (`public/tutorials/`)
|
||||
- dashcaddy-getting-started.html (self-contained HTML guide)
|
||||
- dashcaddy-getting-started.md (markdown version)
|
||||
- issues.md (test instance notes)
|
||||
- screenshots/ (all workflow screenshots)
|
||||
|
||||
## Known Gaps
|
||||
|
||||
1. **App Selector Empty** - The test instance App Selector modal shows no apps. Unable to document the "Deploy Your First App" workflow.
|
||||
|
||||
2. **Dark Theme Toggle** - The "🎨 Light" button in the top bar displays current theme but doesn't toggle. Theme switching only works through Theme Builder modal.
|
||||
|
||||
3. **Menu Navigation** - Some menu items in the TOOLS/STATUS/ADMIN sections weren't directly clickable in headless Chrome. Had to use JavaScript workarounds to open modals.
|
||||
|
||||
4. **Live Data** - Logs and Stats show "Loading..." because the test instance doesn't have actual container data.
|
||||
|
||||
5. **Mobile Screenshots** - Not captured (viewport 1920x1080 only).
|
||||
|
||||
6. **Delete/Cleanup Workflows** - Not fully explored (would need running services).
|
||||
|
||||
7. **Smart Arr Setup** - Not explored.
|
||||
|
||||
8. **Tailscale Integration** - Not explored.
|
||||
|
||||
9. **Docker Resources** - Not explored in detail.
|
||||
|
||||
## PR Information
|
||||
|
||||
**Branch:** `tutorials/dashcaddy-getting-started-2026-05-01`
|
||||
**Base Branch:** `master`
|
||||
**PR/MR URL:** https://git.dashcaddy.net/sami7777/dashcaddy.net/pulls/new/tutorials/dashcaddy-getting-started-2026-05-01
|
||||
|
||||
## Questions for User
|
||||
|
||||
1. Should tutorials be integrated into the `/docs` page, or kept as standalone downloadable content?
|
||||
2. Should I integrate the tutorials into the Next.js app router (create pages at `/tutorials/*`) or keep them as static markdown files?
|
||||
3. The app selector is empty in the test instance - should I skip the "Deploy First App" tutorial or mark it as needing backend configuration?
|
||||
4. Is the 11MB tarball size acceptable, or should screenshots be optimized/compressed?
|
||||
5. Should I try to capture mobile (390x844) screenshots, or is 1920x1080 sufficient?
|
||||
|
||||
## Local Backup
|
||||
|
||||
Tarball location: `/root/dashcaddy-tutorial.tar.gz` (11MB)
|
||||
@@ -0,0 +1,385 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DashCaddy Getting Started Guide</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #1a1a2e;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.8rem;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #1a1a2e;
|
||||
border-bottom: 2px solid #ddd;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.3rem;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.intro {
|
||||
font-size: 1.1rem;
|
||||
color: #666;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.toc {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.toc h2 {
|
||||
margin-top: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.toc ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.toc li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.toc a {
|
||||
color: #0066cc;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.toc a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.feature-card h3 {
|
||||
margin-top: 0;
|
||||
color: #0066cc;
|
||||
}
|
||||
|
||||
.feature-card p {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.feature-link {
|
||||
display: inline-block;
|
||||
margin-top: 0.5rem;
|
||||
color: #0066cc;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.feature-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin-left: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #f0f0f0;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 3px;
|
||||
font-family: 'Monaco', 'Consolas', monospace;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
kbd {
|
||||
background: #e0e0e0;
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 3px;
|
||||
font-family: 'Monaco', 'Consolas', monospace;
|
||||
font-size: 0.85rem;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 0.75rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #f5f5f5;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 4px solid #0066cc;
|
||||
padding-left: 1rem;
|
||||
margin: 1rem 0;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.shortcut-table td:first-child {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
margin-top: 3rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid #ddd;
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>DashCaddy Getting Started Guide</h1>
|
||||
<p class="intro">A practical guide to getting started with DashCaddy — the self-hosted app dashboard.</p>
|
||||
|
||||
<div class="toc">
|
||||
<h2>Table of Contents</h2>
|
||||
<ul>
|
||||
<li><a href="#getting-started">Getting Started</a> — First-time setup and configuration</li>
|
||||
<li><a href="#theme-customization">Theme Customization</a> — Customize the dashboard appearance</li>
|
||||
<li><a href="#viewing-logs">Viewing Logs</a> — Monitor service logs in real-time</li>
|
||||
<li><a href="#quick-search">Quick Search</a> — Fast access to services and settings</li>
|
||||
<li><a href="#backup-restore">Backup & Restore</a> — Protect your configuration</li>
|
||||
<li><a href="#monitoring">Monitoring Resources</a> — Track CPU, memory, and disk usage</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 id="getting-started">Getting Started</h2>
|
||||
<div class="feature-card">
|
||||
<h3>First-Time Setup</h3>
|
||||
<p>Learn how to log in, configure your timezone, and choose a deployment mode.</p>
|
||||
<p><strong>What you'll learn:</strong></p>
|
||||
<ul>
|
||||
<li>How to log in with TOTP authentication</li>
|
||||
<li>How to select your timezone</li>
|
||||
<li>How to choose a deployment mode (Simple, Home Lab, Public, Custom)</li>
|
||||
<li>How to complete or skip the initial setup wizard</li>
|
||||
</ul>
|
||||
<a class="feature-link" href="tutorials/01-getting-started.html">Read full tutorial →</a>
|
||||
</div>
|
||||
|
||||
<h2 id="theme-customization">Theme Customization</h2>
|
||||
<div class="feature-card">
|
||||
<h3>Customize the Dashboard</h3>
|
||||
<p>Make DashCaddy your own with custom themes and colors.</p>
|
||||
<p><strong>What you'll learn:</strong></p>
|
||||
<ul>
|
||||
<li>How to open the Theme Builder</li>
|
||||
<li>How to use preset themes (Dark, Light, Nord, Dracula, etc.)</li>
|
||||
<li>How to customize colors for backgrounds, text, accents, and status indicators</li>
|
||||
<li>How to save, import, and export themes</li>
|
||||
</ul>
|
||||
<a class="feature-link" href="tutorials/02-theme-customization.html">Read full tutorial →</a>
|
||||
</div>
|
||||
|
||||
<h2 id="viewing-logs">Viewing Logs</h2>
|
||||
<div class="feature-card">
|
||||
<h3>Monitor Service Logs</h3>
|
||||
<p>Monitor your services with real-time log streaming.</p>
|
||||
<p><strong>What you'll learn:</strong></p>
|
||||
<ul>
|
||||
<li>How to access the logs viewer</li>
|
||||
<li>How to navigate and filter log entries</li>
|
||||
<li>How to use live log streaming</li>
|
||||
<li>How to control the number of displayed log lines</li>
|
||||
</ul>
|
||||
<a class="feature-link" href="tutorials/03-viewing-logs.html">Read full tutorial →</a>
|
||||
</div>
|
||||
|
||||
<h2 id="quick-search">Quick Search</h2>
|
||||
<div class="feature-card">
|
||||
<h3>Fast Access to Everything</h3>
|
||||
<p>Find services and settings instantly.</p>
|
||||
<p><strong>What you'll learn:</strong></p>
|
||||
<ul>
|
||||
<li>How to open Quick Search (<kbd>Ctrl+K</kbd> / <kbd>Cmd+K</kbd>)</li>
|
||||
<li>How to search for services and settings</li>
|
||||
<li>How to navigate and select results with keyboard shortcuts</li>
|
||||
</ul>
|
||||
<a class="feature-link" href="tutorials/04-quick-search.html">Read full tutorial →</a>
|
||||
</div>
|
||||
|
||||
<h2 id="backup-restore">Backup & Restore</h2>
|
||||
<div class="feature-card">
|
||||
<h3>Protect Your Configuration</h3>
|
||||
<p>Create full backups of your entire DashCaddy setup.</p>
|
||||
<p><strong>What you'll learn:</strong></p>
|
||||
<ul>
|
||||
<li>How to create a manual backup</li>
|
||||
<li>What's included in a full backup (services, credentials, themes)</li>
|
||||
<li>How to restore from a backup file</li>
|
||||
<li>How to set up automated backups</li>
|
||||
<li>How to view backup history</li>
|
||||
</ul>
|
||||
<a class="feature-link" href="tutorials/05-backup-restore.html">Read full tutorial →</a>
|
||||
</div>
|
||||
|
||||
<h2 id="monitoring">Monitoring Resources</h2>
|
||||
<div class="feature-card">
|
||||
<h3>Track System Performance</h3>
|
||||
<p>Monitor CPU, memory, network, and disk usage in real-time.</p>
|
||||
<p><strong>What you'll learn:</strong></p>
|
||||
<ul>
|
||||
<li>How to access the Resource Monitor</li>
|
||||
<li>How to interpret live stats and historical summaries</li>
|
||||
<li>How to set up alerts for resource thresholds</li>
|
||||
<li>Understanding CPU, memory, network, and disk metrics</li>
|
||||
</ul>
|
||||
<a class="feature-link" href="tutorials/06-stats-monitoring.html">Read full tutorial →</a>
|
||||
</div>
|
||||
|
||||
<h2>Keyboard Shortcuts</h2>
|
||||
<table class="shortcut-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Shortcut</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><kbd>Ctrl+K</kbd> / <kbd>Cmd+K</kbd></td>
|
||||
<td>Open Quick Search</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>↑</kbd> / <kbd>↓</kbd></td>
|
||||
<td>Navigate in Quick Search</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>Enter</kbd></td>
|
||||
<td>Select in Quick Search</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>ESC</kbd></td>
|
||||
<td>Close modal / Cancel</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Common Tasks</h2>
|
||||
|
||||
<h3>Adding Your First App</h3>
|
||||
<blockquote>
|
||||
<strong>Note:</strong> App deployment requires the app catalog to be configured with your DashCaddy instance.
|
||||
</blockquote>
|
||||
<ol>
|
||||
<li>Navigate to <strong>TOOLS</strong> → <strong>App Selector</strong></li>
|
||||
<li>Browse available apps or use Quick Search</li>
|
||||
<li>Select an app template</li>
|
||||
<li>Configure the app settings (URL, subdomain, credentials)</li>
|
||||
<li>Deploy and verify</li>
|
||||
</ol>
|
||||
|
||||
<h3>Checking Service Health</h3>
|
||||
<ol>
|
||||
<li>Navigate to <strong>STATUS</strong> → <strong>Health Status</strong></li>
|
||||
<li>View the health overview for all services</li>
|
||||
<li>Check individual service status cards</li>
|
||||
<li>Review any active incidents</li>
|
||||
</ol>
|
||||
|
||||
<h3>Updating Services</h3>
|
||||
<ol>
|
||||
<li>Navigate to <strong>ADMIN</strong> → <strong>Updates</strong></li>
|
||||
<li>View available updates</li>
|
||||
<li>Review changelogs and release notes</li>
|
||||
<li>Apply updates to individual services or all at once</li>
|
||||
</ol>
|
||||
|
||||
<h2>Troubleshooting</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Issue</th>
|
||||
<th>Solution</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Can't log in with TOTP</td>
|
||||
<td>Ensure your device clock is synchronized (TOTP is time-based). Try regenerating the code — they expire every 30 seconds.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dashboard shows no services</td>
|
||||
<td>Services need to be added via the App Selector. Check that Docker is running on your server.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Logs show "Loading..."</td>
|
||||
<td>The service may not have generated any logs yet. Check that the service is actually running.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Theme changes not saving</td>
|
||||
<td>Click "Save Theme" before closing the Theme Builder. Try a different browser if issues persist.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Next Steps</h2>
|
||||
<ul>
|
||||
<li>Complete the initial setup wizard</li>
|
||||
<li>Add your first service via App Selector</li>
|
||||
<li>Customize the theme to your liking</li>
|
||||
<li>Set up automated backups</li>
|
||||
<li>Explore the Admin section for advanced settings</li>
|
||||
<li>Join the community for tips and support</li>
|
||||
</ul>
|
||||
|
||||
<footer>
|
||||
<p>This guide was created for DashCaddy version as deployed on test.dashcaddy.net</p>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,175 @@
|
||||
# DashCaddy Getting Started Guide
|
||||
|
||||
A practical guide to getting started with DashCaddy — the self-hosted app dashboard.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Getting Started](#getting-started) — First-time setup and configuration
|
||||
2. [Theme Customization](#theme-customization) — Customize the dashboard appearance
|
||||
3. [Viewing Logs](#viewing-logs) — Monitor service logs in real-time
|
||||
4. [Quick Search](#quick-search) — Fast access to services and settings
|
||||
5. [Backup & Restore](#backup--restore) — Protect your configuration
|
||||
6. [Monitoring Resources](#monitoring-resources) — Track CPU, memory, and disk usage
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
Learn how to log in, configure your timezone, and choose a deployment mode.
|
||||
|
||||
[Read full tutorial](tutorials/01-getting-started.md)
|
||||
|
||||
**What you'll learn:**
|
||||
- How to log in with TOTP authentication
|
||||
- How to select your timezone
|
||||
- How to choose a deployment mode (Simple, Home Lab, Public, Custom)
|
||||
- How to complete or skip the initial setup wizard
|
||||
|
||||
---
|
||||
|
||||
## Theme Customization
|
||||
|
||||
Make DashCaddy your own with custom themes and colors.
|
||||
|
||||
[Read full tutorial](tutorials/02-theme-customization.md)
|
||||
|
||||
**What you'll learn:**
|
||||
- How to open the Theme Builder
|
||||
- How to use preset themes (Dark, Light, Nord, Dracula, etc.)
|
||||
- How to customize colors for backgrounds, text, accents, and status indicators
|
||||
- How to save, import, and export themes
|
||||
|
||||
---
|
||||
|
||||
## Viewing Logs
|
||||
|
||||
Monitor your services with real-time log streaming.
|
||||
|
||||
[Read full tutorial](tutorials/03-viewing-logs.md)
|
||||
|
||||
**What you'll learn:**
|
||||
- How to access the logs viewer
|
||||
- How to navigate and filter log entries
|
||||
- How to use live log streaming
|
||||
- How to control the number of displayed log lines
|
||||
|
||||
---
|
||||
|
||||
## Quick Search
|
||||
|
||||
Find services and settings instantly.
|
||||
|
||||
[Read full tutorial](tutorials/04-quick-search.md)
|
||||
|
||||
**What you'll learn:**
|
||||
- How to open Quick Search (Ctrl+K / Cmd+K)
|
||||
- How to search for services and settings
|
||||
- How to navigate and select results with keyboard shortcuts
|
||||
- Keyboard shortcuts: ESC, ↑↓ Navigate, Enter Select
|
||||
|
||||
---
|
||||
|
||||
## Backup & Restore
|
||||
|
||||
Protect your configuration with full backups.
|
||||
|
||||
[Read full tutorial](tutorials/05-backup-restore.md)
|
||||
|
||||
**What you'll learn:**
|
||||
- How to create a manual backup
|
||||
- What's included in a full backup (services, credentials, themes)
|
||||
- How to restore from a backup file
|
||||
- How to set up automated backups
|
||||
- How to view backup history
|
||||
|
||||
---
|
||||
|
||||
## Monitoring Resources
|
||||
|
||||
Track CPU, memory, network, and disk usage in real-time.
|
||||
|
||||
[Read full tutorial](tutorials/06-stats-monitoring.md)
|
||||
|
||||
**What you'll learn:**
|
||||
- How to access the Resource Monitor
|
||||
- How to interpret live stats and historical summaries
|
||||
- How to set up alerts for resource thresholds
|
||||
- Understanding CPU, memory, network, and disk metrics
|
||||
|
||||
---
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Adding Your First App
|
||||
|
||||
> **Note:** App deployment requires the app catalog to be configured with your DashCaddy instance.
|
||||
|
||||
1. Navigate to **TOOLS** → **App Selector**
|
||||
2. Browse available apps or use Quick Search
|
||||
3. Select an app template
|
||||
4. Configure the app settings (URL, subdomain, credentials)
|
||||
5. Deploy and verify
|
||||
|
||||
### Checking Service Health
|
||||
|
||||
1. Navigate to **STATUS** → **Health Status**
|
||||
2. View the health overview for all services
|
||||
3. Check individual service status cards
|
||||
4. Review any active incidents
|
||||
|
||||
### Updating Services
|
||||
|
||||
1. Navigate to **ADMIN** → **Updates**
|
||||
2. View available updates
|
||||
3. Review changelogs and release notes
|
||||
4. Apply updates to individual services or all at once
|
||||
|
||||
---
|
||||
|
||||
## Keyboard Shortcuts
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `Ctrl+K` / `Cmd+K` | Open Quick Search |
|
||||
| `↑` / `↓` | Navigate in Quick Search |
|
||||
| `Enter` | Select in Quick Search |
|
||||
| `ESC` | Close modal / Cancel |
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Can't log in with TOTP
|
||||
- Ensure your device clock is synchronized (TOTP is time-based)
|
||||
- Try regenerating the code — they expire every 30 seconds
|
||||
- Contact your administrator if the code is consistently rejected
|
||||
|
||||
### Dashboard shows no services
|
||||
- Services need to be added via the App Selector
|
||||
- Check that Docker is running on your server
|
||||
- Verify network connectivity to your services
|
||||
|
||||
### Logs show "Loading..."
|
||||
- The service may not have generated any logs yet
|
||||
- Check that the service is actually running
|
||||
- Verify the logging system is configured correctly
|
||||
|
||||
### Theme changes not saving
|
||||
- Click "Save Theme" before closing the Theme Builder
|
||||
- Check browser console for JavaScript errors
|
||||
- Try a different browser if issues persist
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [ ] Complete the initial setup wizard
|
||||
- [ ] Add your first service via App Selector
|
||||
- [ ] Customize the theme to your liking
|
||||
- [ ] Set up automated backups
|
||||
- [ ] Explore the Admin section for advanced settings
|
||||
- [ ] Join the community for tips and support
|
||||
|
||||
---
|
||||
|
||||
*This guide was created for DashCaddy version as deployed on test.dashcaddy.net*
|
||||
@@ -0,0 +1,69 @@
|
||||
# Issues and Notes
|
||||
|
||||
## Test Instance Observations
|
||||
|
||||
### App Selector Empty
|
||||
The App Selector modal opens but shows no apps in the grid. The "Choose an App" message appears with just a Cancel button. This could mean:
|
||||
- The test instance doesn't have app templates loaded
|
||||
- The app catalog API isn't responding
|
||||
- The apps need to be fetched from a backend that's not configured
|
||||
|
||||
**Impact:** Could not complete "Deploy Your First App" workflow
|
||||
|
||||
**Workaround:** App selector needs backend/app catalog to be configured
|
||||
|
||||
### Menu Navigation Issues
|
||||
The TOOLS, STATUS, and ADMIN sections in the dashboard have expandable menus. Clicking on section headers (like "TOOLS") didn't expand the menu via standard click methods in headless Chrome.
|
||||
|
||||
**Impact:** Could not navigate to some features through normal menu interaction
|
||||
|
||||
**Workaround:** Used JavaScript to directly show modals by manipulating CSS display property
|
||||
|
||||
### Theme Switching Not Working Properly
|
||||
The "🎨 Light" button in the top bar appears to be a display of current theme rather than a toggle. Clicking it doesn't switch themes.
|
||||
|
||||
**Impact:** Theme workflow only partially captured - theme builder modal opens but theme switching via the quick button doesn't work
|
||||
|
||||
**Workaround:** Use Theme Builder modal to change themes
|
||||
|
||||
### Logs/Services Show "Loading..."
|
||||
Some services show "Loading logs..." or "Loading container stats..." which suggests the backend APIs aren't returning data in the test instance.
|
||||
|
||||
**Impact:** Could see the UI for these features but not actual data
|
||||
|
||||
**Workaround:** Document UI even when no data is present
|
||||
|
||||
## Technical Observations
|
||||
|
||||
### Puppeteer/Chromium Issues
|
||||
- Background Chromium processes were being killed by SIGKILL
|
||||
- Some clickable elements weren't being recognized as clickable by puppeteer
|
||||
- Had to use JavaScript clicks (`element.click()` via `page.evaluate()`) for some interactions
|
||||
|
||||
### Session Management
|
||||
- TOTP codes expire every 30 seconds
|
||||
- Had to generate fresh TOTP for each session
|
||||
- Used `process.exit(0)` to avoid graceful cleanup issues that triggered process kills
|
||||
|
||||
## Screenshots Captured
|
||||
|
||||
### Workflows Documented:
|
||||
1. **Getting Started** - Setup wizard, timezone, deployment mode (partial)
|
||||
2. **Theme Customization** - Theme builder modal, presets (partial)
|
||||
3. **Viewing Logs** - Complete workflow
|
||||
4. **Quick Search** - Complete workflow
|
||||
5. **Backup & Restore** - Complete workflow (UI)
|
||||
6. **Stats Monitoring** - Complete workflow (UI)
|
||||
|
||||
### Workflows Not Fully Documented:
|
||||
1. **Deploy First App** - App selector empty, couldn't complete
|
||||
2. **Dark Theme Toggle** - Theme builder works but quick toggle doesn't
|
||||
3. **Service Status Cards** - Not deeply explored
|
||||
4. **Smart Arr Setup** - Not explored
|
||||
|
||||
## Recommendations
|
||||
|
||||
1. App Selector needs app catalog/backend to show templates
|
||||
2. Menu expansion should be tested with real user interaction
|
||||
3. Test instance should have some sample services running to show actual data in logs/stats
|
||||
4. Consider adding a "demo mode" with fake data for tutorial purposes
|
||||
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 176 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 128 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 86 KiB |
@@ -108,26 +108,10 @@ export default function AboutPage() {
|
||||
</h2>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
||||
{[
|
||||
{
|
||||
name: "Caddy",
|
||||
role: "Reverse Proxy & SSL",
|
||||
icon: "🔒",
|
||||
},
|
||||
{
|
||||
name: "Docker",
|
||||
role: "Container Runtime",
|
||||
icon: "🐳",
|
||||
},
|
||||
{
|
||||
name: "Node.js",
|
||||
role: "API Backend",
|
||||
icon: "🟢",
|
||||
},
|
||||
{
|
||||
name: "Technitium",
|
||||
role: "DNS Server",
|
||||
icon: "🌐",
|
||||
},
|
||||
{ name: "Caddy", role: "Reverse Proxy & SSL", icon: "🔒" },
|
||||
{ name: "Docker", role: "Container Runtime", icon: "🐳" },
|
||||
{ name: "Node.js", role: "API Backend", icon: "🟢" },
|
||||
{ name: "Technitium", role: "DNS Server", icon: "🌐" },
|
||||
].map((tech) => (
|
||||
<div
|
||||
key={tech.name}
|
||||
@@ -147,8 +131,7 @@ export default function AboutPage() {
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<h2 className="text-2xl font-bold text-white mb-6">Get In Touch</h2>
|
||||
<p className="text-surface-300 mb-8">
|
||||
Have questions, feedback, or want to contribute? We'd love to
|
||||
hear from you.
|
||||
Have questions, feedback, or want to contribute? We'd love to hear from you.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<a
|
||||
@@ -158,13 +141,17 @@ export default function AboutPage() {
|
||||
Email Us
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
href="https://discord.gg/dashcaddy"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="px-6 py-3 rounded-lg border border-surface-700 hover:border-surface-500 text-surface-300 font-medium transition-colors"
|
||||
>
|
||||
Join Discord
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
href="https://github.com/dashcaddy"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="px-6 py-3 rounded-lg border border-surface-700 hover:border-surface-500 text-surface-300 font-medium transition-colors"
|
||||
>
|
||||
GitHub
|
||||
@@ -194,4 +181,4 @@ export default function AboutPage() {
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,74 +1,10 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import Stripe from "stripe";
|
||||
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
||||
apiVersion: "2026-03-25.dahlia",
|
||||
});
|
||||
|
||||
const PRICE_IDS: Record<string, string | undefined> = {
|
||||
monthly: process.env.STRIPE_PRICE_MONTHLY,
|
||||
yearly: process.env.STRIPE_PRICE_YEARLY,
|
||||
};
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { plan, email } = body;
|
||||
|
||||
if (!plan || !PRICE_IDS[plan]) {
|
||||
return NextResponse.json(
|
||||
{ error: "Invalid plan. Choose 'monthly' or 'yearly'." },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const priceId = PRICE_IDS[plan];
|
||||
if (!priceId) {
|
||||
return NextResponse.json(
|
||||
{ error: "Price not configured. Please contact support." },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
const appUrl = process.env.NEXT_PUBLIC_APP_URL || "https://dashcaddy.net";
|
||||
|
||||
const sessionParams: Stripe.Checkout.SessionCreateParams = {
|
||||
mode: "subscription",
|
||||
payment_method_types: ["card"],
|
||||
line_items: [
|
||||
{
|
||||
price: priceId,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
success_url: `${appUrl}/success?session_id={CHECKOUT_SESSION_ID}`,
|
||||
cancel_url: `${appUrl}/pricing`,
|
||||
allow_promotion_codes: true,
|
||||
billing_address_collection: "required",
|
||||
subscription_data: {
|
||||
trial_period_days: 14,
|
||||
metadata: {
|
||||
plan,
|
||||
source: "dashcaddy-website",
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
plan,
|
||||
},
|
||||
};
|
||||
|
||||
// Pre-fill email if provided
|
||||
if (email) {
|
||||
sessionParams.customer_email = email;
|
||||
}
|
||||
|
||||
const session = await stripe.checkout.sessions.create(sessionParams);
|
||||
|
||||
return NextResponse.json({ url: session.url });
|
||||
} catch (error) {
|
||||
console.error("Stripe checkout error:", error);
|
||||
const message =
|
||||
error instanceof Error ? error.message : "Internal server error";
|
||||
return NextResponse.json({ error: message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function POST() {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "Website-local checkout is disabled. Use the external DashCaddy license service."
|
||||
},
|
||||
{ status: 410 }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,99 +1,10 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import Stripe from "stripe";
|
||||
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
||||
apiVersion: "2026-03-25.dahlia",
|
||||
});
|
||||
|
||||
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const body = await request.text();
|
||||
const signature = request.headers.get("stripe-signature");
|
||||
|
||||
if (!signature) {
|
||||
return NextResponse.json(
|
||||
{ error: "Missing stripe-signature header" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
let event: Stripe.Event;
|
||||
|
||||
try {
|
||||
event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : "Unknown error";
|
||||
console.error(`Webhook signature verification failed: ${message}`);
|
||||
return NextResponse.json({ error: message }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
switch (event.type) {
|
||||
case "checkout.session.completed": {
|
||||
const session = event.data.object as Stripe.Checkout.Session;
|
||||
console.log("Checkout completed:", {
|
||||
sessionId: session.id,
|
||||
customerEmail: session.customer_email,
|
||||
plan: session.metadata?.plan,
|
||||
subscriptionId: session.subscription,
|
||||
});
|
||||
|
||||
// TODO: Generate and deliver license key to customer
|
||||
// This is where you'd:
|
||||
// 1. Generate a DC-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX license code
|
||||
// 2. Store it in your database
|
||||
// 3. Email it to the customer
|
||||
// 4. Associate it with the Stripe subscription ID
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "customer.subscription.updated": {
|
||||
const subscription = event.data.object as Stripe.Subscription;
|
||||
console.log("Subscription updated:", {
|
||||
subscriptionId: subscription.id,
|
||||
status: subscription.status,
|
||||
});
|
||||
|
||||
// TODO: Update license expiration based on subscription status
|
||||
break;
|
||||
}
|
||||
|
||||
case "customer.subscription.deleted": {
|
||||
const subscription = event.data.object as Stripe.Subscription;
|
||||
console.log("Subscription cancelled:", {
|
||||
subscriptionId: subscription.id,
|
||||
status: subscription.status,
|
||||
});
|
||||
|
||||
// TODO: Deactivate/expire the license key
|
||||
// The DashCaddy instance will gracefully downgrade to free tier
|
||||
break;
|
||||
}
|
||||
|
||||
case "invoice.payment_failed": {
|
||||
const invoice = event.data.object as Stripe.Invoice;
|
||||
console.log("Payment failed:", {
|
||||
invoiceId: invoice.id,
|
||||
customerEmail: invoice.customer_email,
|
||||
});
|
||||
|
||||
// TODO: Notify customer about failed payment
|
||||
// Consider a grace period before deactivating license
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
console.log(`Unhandled event type: ${event.type}`);
|
||||
}
|
||||
|
||||
return NextResponse.json({ received: true });
|
||||
} catch (error) {
|
||||
console.error("Webhook handler error:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Webhook handler failed" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function POST() {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: "Website-local Stripe webhooks are disabled. Use the external DashCaddy license service."
|
||||
},
|
||||
{ status: 410 }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,389 +1,418 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import Navbar from '@/components/Navbar';
|
||||
import Footer from '@/components/Footer';
|
||||
|
||||
export default function DocsPage() {
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
|
||||
<Navbar />
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="relative py-16 sm:py-20 lg:py-24">
|
||||
<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-96 bg-brand-500/20 rounded-full blur-3xl opacity-30 animate-pulse" />
|
||||
</div>
|
||||
|
||||
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold mb-6">
|
||||
Getting <span className="text-brand-400">Started</span>
|
||||
</h1>
|
||||
<p className="text-xl text-surface-300 max-w-2xl">
|
||||
Get DashCaddy up and running on your server in just a few minutes.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Main Content */}
|
||||
<section className="relative py-12 sm:py-16 lg:py-20">
|
||||
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||
{/* Table of Contents */}
|
||||
<div className="mb-16 rounded-lg border border-surface-700/50 bg-surface-800/50 p-8">
|
||||
<h2 className="text-2xl font-bold text-surface-50 mb-6">Table of Contents</h2>
|
||||
<ul className="space-y-3">
|
||||
<li>
|
||||
<a href="#prerequisites" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
|
||||
<span>→</span> Prerequisites
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#installation" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
|
||||
<span>→</span> Installation
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#configuration" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
|
||||
<span>→</span> Configuration
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#first-run" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
|
||||
<span>→</span> First Run
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#troubleshooting" className="text-brand-400 hover:text-brand-300 transition-colors flex items-center gap-2">
|
||||
<span>→</span> Troubleshooting
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Prerequisites Section */}
|
||||
<section id="prerequisites" className="mb-16">
|
||||
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
|
||||
<span className="text-brand-400">📋</span> Prerequisites
|
||||
</h2>
|
||||
<p className="text-surface-300 mb-6 leading-relaxed">
|
||||
Before you begin, ensure you have the following installed on your server:
|
||||
</p>
|
||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6 space-y-4">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="text-xl flex-shrink-0">🐳</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-surface-50 mb-1">Docker</h3>
|
||||
<p className="text-sm text-surface-400">Version 20.10+ required. <a href="https://docs.docker.com/get-docker/" className="text-brand-400 hover:text-brand-300">Install Docker</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="text-xl flex-shrink-0">⚙️</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-surface-50 mb-1">Caddy</h3>
|
||||
<p className="text-sm text-surface-400">Version 2.7+ required. <a href="https://caddyserver.com/docs/install" className="text-brand-400 hover:text-brand-300">Install Caddy</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="text-xl flex-shrink-0">🟢</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-surface-50 mb-1">Node.js</h3>
|
||||
<p className="text-sm text-surface-400">Version 18+ required. <a href="https://nodejs.org/" className="text-brand-400 hover:text-brand-300">Install Node.js</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="text-xl flex-shrink-0">🔧</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-surface-50 mb-1">Git</h3>
|
||||
<p className="text-sm text-surface-400">For cloning the repository. <a href="https://git-scm.com/" className="text-brand-400 hover:text-brand-300">Install Git</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-t border-surface-700/30 pt-4 mt-4">
|
||||
<h3 className="font-semibold text-surface-50 mb-2">Optional</h3>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="text-xl flex-shrink-0">🌐</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-surface-50 mb-1">Technitium DNS</h3>
|
||||
<p className="text-sm text-surface-400">For automatic DNS management. If not installed, you can still manage DNS manually.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Installation Section */}
|
||||
<section id="installation" className="mb-16">
|
||||
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
|
||||
<span className="text-brand-400">📦</span> Installation
|
||||
</h2>
|
||||
<p className="text-surface-300 mb-8 leading-relaxed">
|
||||
Follow these steps to install DashCaddy on your server:
|
||||
</p>
|
||||
|
||||
{/* Step 1 */}
|
||||
<div className="mb-10">
|
||||
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 1: Clone the Repository</h3>
|
||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
|
||||
<code className="text-sm text-green-400 font-mono block">
|
||||
git clone https://github.com/dashcaddy/dashcaddy.git
|
||||
<br />
|
||||
cd dashcaddy
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step 2 */}
|
||||
<div className="mb-10">
|
||||
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 2: Install Dependencies</h3>
|
||||
<p className="text-surface-300 mb-4">Install Node.js dependencies:</p>
|
||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
|
||||
<code className="text-sm text-green-400 font-mono block">
|
||||
npm install
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step 3 */}
|
||||
<div className="mb-10">
|
||||
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 3: Configure Environment Variables</h3>
|
||||
<p className="text-surface-300 mb-4">Copy the example environment file and customize it:</p>
|
||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4 mb-4">
|
||||
<code className="text-sm text-green-400 font-mono block">
|
||||
cp .env.example .env
|
||||
</code>
|
||||
</div>
|
||||
<p className="text-surface-300 mb-4">Then edit <code className="bg-surface-800 px-2 py-1 rounded text-brand-400">.env</code> with your configuration:</p>
|
||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
|
||||
<code className="text-sm text-surface-300 font-mono whitespace-pre-wrap">
|
||||
{`# Server
|
||||
PORT=3000
|
||||
NODE_ENV=production
|
||||
|
||||
# Database (optional - defaults to SQLite)
|
||||
DATABASE_URL=sqlite:./data/dashcaddy.db
|
||||
|
||||
# Security
|
||||
JWT_SECRET=your-secure-random-secret-here
|
||||
SESSION_SECRET=another-secure-random-secret
|
||||
|
||||
# Caddy
|
||||
CADDY_PORT=80
|
||||
CADDY_HTTPS_PORT=443
|
||||
CADDY_ADMIN_LISTEN=localhost:2019
|
||||
|
||||
# Technitium DNS (optional)
|
||||
TECHNITIUM_API_URL=http://localhost:5380/api
|
||||
TECHNITIUM_API_KEY=your-api-key
|
||||
|
||||
# Email (optional - for notifications)
|
||||
SMTP_HOST=smtp.example.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=your-email@example.com
|
||||
SMTP_PASSWORD=your-password`}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Step 4 */}
|
||||
<div className="mb-10">
|
||||
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 4: Build and Start</h3>
|
||||
<p className="text-surface-300 mb-4">Build the application:</p>
|
||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4 mb-6">
|
||||
<code className="text-sm text-green-400 font-mono block">
|
||||
npm run build
|
||||
</code>
|
||||
</div>
|
||||
<p className="text-surface-300 mb-4">Start DashCaddy:</p>
|
||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
|
||||
<code className="text-sm text-green-400 font-mono block">
|
||||
npm start
|
||||
</code>
|
||||
</div>
|
||||
<p className="text-surface-300 mt-4 text-sm">
|
||||
The application will be available at <code className="bg-surface-800 px-2 py-1 rounded text-brand-400">http://localhost:3000</code>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Step 5 */}
|
||||
<div className="mb-10">
|
||||
<h3 className="text-xl font-semibold text-surface-50 mb-4">Step 5: Configure Caddy</h3>
|
||||
<p className="text-surface-300 mb-4">Update your Caddy configuration to proxy requests to DashCaddy:</p>
|
||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-4">
|
||||
<code className="text-sm text-surface-300 font-mono whitespace-pre-wrap">
|
||||
{`dashcaddy.local {
|
||||
reverse_proxy localhost:3000
|
||||
|
||||
# Enable automatic HTTPS
|
||||
encode gzip
|
||||
|
||||
# Security headers
|
||||
header Strict-Transport-Security "max-age=31536000"
|
||||
header X-Content-Type-Options "nosniff"
|
||||
header X-Frame-Options "DENY"
|
||||
}`}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Configuration Section */}
|
||||
<section id="configuration" className="mb-16">
|
||||
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
|
||||
<span className="text-brand-400">⚙️</span> Configuration
|
||||
</h2>
|
||||
<p className="text-surface-300 mb-8 leading-relaxed">
|
||||
Key environment variables for DashCaddy configuration:
|
||||
</p>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
||||
<h3 className="font-semibold text-brand-400 mb-2 font-mono">PORT</h3>
|
||||
<p className="text-surface-300 text-sm mb-2">The port DashCaddy runs on. Default: <code className="bg-surface-800 px-1 rounded">3000</code></p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
||||
<h3 className="font-semibold text-brand-400 mb-2 font-mono">JWT_SECRET</h3>
|
||||
<p className="text-surface-300 text-sm mb-2">Secret key for JWT tokens. Generate a secure random string:</p>
|
||||
<code className="bg-surface-800 px-2 py-1 rounded text-brand-400 text-xs">openssl rand -hex 32</code>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
||||
<h3 className="font-semibold text-brand-400 mb-2 font-mono">DATABASE_URL</h3>
|
||||
<p className="text-surface-300 text-sm">Connection string for your database. Defaults to SQLite if not provided.</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
||||
<h3 className="font-semibold text-brand-400 mb-2 font-mono">CADDY_ADMIN_LISTEN</h3>
|
||||
<p className="text-surface-300 text-sm mb-2">Caddy admin API endpoint. Default: <code className="bg-surface-800 px-1 rounded">localhost:2019</code></p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
||||
<h3 className="font-semibold text-brand-400 mb-2 font-mono">TECHNITIUM_API_URL</h3>
|
||||
<p className="text-surface-300 text-sm">URL to your Technitium DNS API. Optional for DNS management features.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* First Run Section */}
|
||||
<section id="first-run" className="mb-16">
|
||||
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
|
||||
<span className="text-brand-400">🚀</span> First Run
|
||||
</h2>
|
||||
<div className="rounded-lg border border-brand-500/30 bg-brand-950/50 p-8">
|
||||
<ol className="space-y-4 list-decimal list-inside text-surface-300">
|
||||
<li>Access the dashboard at <code className="bg-surface-800 px-2 py-1 rounded text-brand-400">http://dashcaddy.local</code></li>
|
||||
<li>Create your admin account with a strong password</li>
|
||||
<li>Enable TOTP 2FA for enhanced security</li>
|
||||
<li>Configure your Technitium DNS API key (optional)</li>
|
||||
<li>Deploy your first application from the app templates library</li>
|
||||
<li>Monitor your apps in real-time from the dashboard</li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Troubleshooting Section */}
|
||||
<section id="troubleshooting" className="mb-16">
|
||||
<h2 className="text-3xl font-bold text-surface-50 mb-6 flex items-center gap-3">
|
||||
<span className="text-brand-400">🔧</span> Troubleshooting
|
||||
</h2>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
||||
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
|
||||
<span className="text-red-400">❌</span> Docker daemon not running
|
||||
</h3>
|
||||
<p className="text-surface-300 text-sm mb-3">Make sure the Docker daemon is started:</p>
|
||||
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">sudo systemctl start docker</code>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
||||
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
|
||||
<span className="text-red-400">❌</span> Port 3000 already in use
|
||||
</h3>
|
||||
<p className="text-surface-300 text-sm mb-3">Change the PORT in your .env file or stop the process using that port:</p>
|
||||
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">lsof -i :3000</code>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
||||
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
|
||||
<span className="text-red-400">❌</span> Cannot connect to Caddy admin API
|
||||
</h3>
|
||||
<p className="text-surface-300 text-sm mb-3">Verify Caddy is running and the admin API is accessible:</p>
|
||||
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">curl http://localhost:2019/config/</code>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
||||
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
|
||||
<span className="text-red-400">❌</span> Database connection errors
|
||||
</h3>
|
||||
<p className="text-surface-300 text-sm mb-3">Check that your DATABASE_URL is correct and the database is accessible. For SQLite, ensure the data directory exists:</p>
|
||||
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">mkdir -p ./data</code>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-surface-700/50 bg-surface-900/50 p-6">
|
||||
<h3 className="font-semibold text-surface-50 mb-3 flex items-center gap-2">
|
||||
<span className="text-red-400">❌</span> SSL certificate issues
|
||||
</h3>
|
||||
<p className="text-surface-300 text-sm mb-3">DashCaddy uses Caddy's internal CA for certificate generation. If you have issues, check the Caddy logs:</p>
|
||||
<code className="bg-surface-800 px-2 py-1 rounded text-green-400 text-xs block">journalctl -u caddy -f</code>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Next Steps */}
|
||||
<section className="mb-16">
|
||||
<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">Next Steps</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Link
|
||||
href="/features"
|
||||
className="flex items-start gap-4 p-4 rounded-lg border border-brand-500/20 bg-brand-950/30 hover:bg-brand-950/50 transition-colors"
|
||||
>
|
||||
<span className="text-2xl flex-shrink-0">✨</span>
|
||||
<div>
|
||||
<h3 className="font-semibold text-surface-50 mb-1">Explore Features</h3>
|
||||
<p className="text-sm text-surface-400">Learn about all the powerful features DashCaddy offers.</p>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<a
|
||||
href="#"
|
||||
className="flex items-start gap-4 p-4 rounded-lg border border-surface-700/50 bg-surface-800/50 hover:bg-surface-800 transition-colors"
|
||||
>
|
||||
<span className="text-2xl flex-shrink-0">📚</span>
|
||||
<div>
|
||||
<h3 className="font-semibold text-surface-50 mb-1">API Reference</h3>
|
||||
<p className="text-sm text-surface-400">Full API documentation for developers.</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<Link
|
||||
href="/pricing"
|
||||
className="flex items-start gap-4 p-4 rounded-lg border border-surface-700/50 bg-surface-800/50 hover:bg-surface-800 transition-colors"
|
||||
>
|
||||
<span className="text-2xl flex-shrink-0">💎</span>
|
||||
<div>
|
||||
<h3 className="font-semibold text-surface-50 mb-1">View Pricing</h3>
|
||||
<p className="text-sm text-surface-400">Check out our free and premium plans.</p>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<a
|
||||
href="mailto:support@dashcaddy.net"
|
||||
className="flex items-start gap-4 p-4 rounded-lg border border-surface-700/50 bg-surface-800/50 hover:bg-surface-800 transition-colors"
|
||||
>
|
||||
<span className="text-2xl flex-shrink-0">💬</span>
|
||||
<div>
|
||||
<h3 className="font-semibold text-surface-50 mb-1">Get Support</h3>
|
||||
<p className="text-sm text-surface-400">Contact our support team for help.</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
'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">
|
||||
<Navbar />
|
||||
|
||||
{/* Hero */}
|
||||
<section className="relative py-16 sm:py-20 lg:py-24">
|
||||
<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-96 bg-brand-500/20 rounded-full blur-3xl opacity-30 animate-pulse" />
|
||||
</div>
|
||||
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8">
|
||||
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold mb-6">
|
||||
How to Use <span className="text-brand-400">DashCaddy</span>
|
||||
</h1>
|
||||
<p className="text-xl text-surface-300 max-w-2xl">
|
||||
Self-host Docker apps in minutes. DNS, SSL, and routing handled automatically.
|
||||
</p>
|
||||
</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">
|
||||
{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>
|
||||
<div className="font-semibold text-surface-50">{title}</div>
|
||||
<div className="text-sm text-surface-400">{desc}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* App Templates */}
|
||||
<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>
|
||||
{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>
|
||||
<span className="font-semibold text-brand-400">{cat}</span>
|
||||
</div>
|
||||
<p className="text-surface-300 text-sm">{apps}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* How Deployment Works */}
|
||||
<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">
|
||||
{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}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold text-surface-50 mb-2">{title}</h3>
|
||||
<p className="text-surface-300">{desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Key Features */}
|
||||
<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">
|
||||
{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>
|
||||
<h3 className="text-lg font-semibold text-surface-50">{title}</h3>
|
||||
</div>
|
||||
<p className="text-surface-300 text-sm leading-relaxed">{body}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Pricing */}
|
||||
<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">
|
||||
<div className="text-surface-400 text-sm font-medium mb-1">Free</div>
|
||||
<div className="text-3xl font-bold text-surface-50 mb-4">$0 <span className="text-lg font-normal text-surface-400">forever</span></div>
|
||||
<ul className="space-y-3">
|
||||
{[
|
||||
'Deploy unlimited apps',
|
||||
'75+ app templates',
|
||||
'Automatic SSL certificates',
|
||||
'Docker management dashboard',
|
||||
'Subdomain + subdirectory routing',
|
||||
'Built-in DNS integration',
|
||||
'TOTP 2FA on dashboard',
|
||||
].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}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Link href="/pricing" className="mt-6 block text-center py-3 px-6 rounded-lg bg-surface-700 hover:bg-surface-600 text-surface-200 font-medium transition-colors">
|
||||
Get Started Free
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Premium */}
|
||||
<div className="rounded-xl border-2 border-brand-500/60 bg-surface-800/60 p-8 relative">
|
||||
<div className="absolute -top-3 left-6 px-3 py-1 bg-brand-500 rounded-full text-xs font-bold text-white">
|
||||
Premium
|
||||
</div>
|
||||
<div className="text-surface-400 text-sm font-medium mb-1">Premium</div>
|
||||
<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) => (
|
||||
<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) => (
|
||||
<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">
|
||||
Get Premium
|
||||
</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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Infrastructure Integrations */}
|
||||
<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>
|
||||
{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>
|
||||
<h3 className="text-xl font-semibold text-surface-50">{title}</h3>
|
||||
</div>
|
||||
<p className="text-surface-300 mb-3">{desc}</p>
|
||||
<div className="text-sm">
|
||||
<span className="text-surface-400">Setup: </span>
|
||||
<span className="text-brand-400">{setup}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Troubleshooting */}
|
||||
<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">Troubleshooting</h2>
|
||||
<div className="space-y-4">
|
||||
{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>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,280 +1,169 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import Navbar from '@/components/Navbar';
|
||||
import Footer from '@/components/Footer';
|
||||
|
||||
export default function PricingPage() {
|
||||
const [isAnnual, setIsAnnual] = useState(false);
|
||||
|
||||
const plans = [
|
||||
{
|
||||
name: 'Free',
|
||||
price: '0',
|
||||
period: 'forever',
|
||||
description: 'Perfect for getting started with self-hosting',
|
||||
features: [
|
||||
'Dashboard & monitoring',
|
||||
'Up to 10 services',
|
||||
'50+ app templates',
|
||||
'Automatic SSL & DNS',
|
||||
'TOTP 2FA',
|
||||
'Community support',
|
||||
],
|
||||
cta: {
|
||||
text: 'Get Started Free',
|
||||
href: '/docs',
|
||||
},
|
||||
highlighted: false,
|
||||
},
|
||||
{
|
||||
name: 'Premium',
|
||||
price: isAnnual ? '99' : '20',
|
||||
period: isAnnual ? 'per year' : 'per month',
|
||||
savings: isAnnual ? 'Save 58%' : null,
|
||||
description: 'For power users and production deployments',
|
||||
features: [
|
||||
'Everything in Free, plus:',
|
||||
'Unlimited services',
|
||||
'Auto-Login SSO for deployed apps',
|
||||
'Recipes (multi-container stack deployment)',
|
||||
'Docker Swarm (multi-node cluster orchestration)',
|
||||
'Priority email support',
|
||||
'Early access to new features',
|
||||
],
|
||||
cta: {
|
||||
text: 'Start 14-Day Free Trial',
|
||||
href: '/api/checkout?plan=premium',
|
||||
},
|
||||
highlighted: true,
|
||||
},
|
||||
];
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: 'Can I try Premium for free?',
|
||||
answer: 'Yes! Premium includes a 14-day free trial. No credit card required. You can cancel anytime.',
|
||||
},
|
||||
{
|
||||
question: 'What happens when my subscription ends?',
|
||||
answer: 'Your subscription gracefully downgrades to the Free tier. All your data remains intact—no data loss. You can resubscribe at any time.',
|
||||
},
|
||||
{
|
||||
question: 'Can I self-host the license server?',
|
||||
answer: 'Coming soon! We\'re working on a self-hosted license server option for enterprise deployments.',
|
||||
},
|
||||
{
|
||||
question: 'Do you offer refunds?',
|
||||
answer: 'Yes, we offer a 30-day money-back guarantee. If you\'re not satisfied with Premium, contact support for a full refund.',
|
||||
},
|
||||
{
|
||||
question: 'Is my data safe?',
|
||||
answer: '100% self-hosted means your data never leaves your server. DashCaddy runs entirely on your infrastructure. We have no access to your applications, configurations, or data.',
|
||||
},
|
||||
];
|
||||
|
||||
const [expandedFaq, setExpandedFaq] = useState<number | null>(0);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
|
||||
<Navbar />
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="relative py-16 sm:py-20 lg:py-24">
|
||||
<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-96 bg-brand-500/20 rounded-full blur-3xl opacity-30 animate-pulse" />
|
||||
</div>
|
||||
|
||||
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
|
||||
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold mb-6">
|
||||
Simple, Transparent <span className="text-brand-400">Pricing</span>
|
||||
</h1>
|
||||
<p className="text-xl text-surface-300 mb-8 max-w-2xl mx-auto">
|
||||
Start free. Upgrade when you need advanced features. No surprises, no lock-in.
|
||||
</p>
|
||||
|
||||
{/* Toggle for Monthly/Yearly */}
|
||||
<div className="flex items-center justify-center gap-4 mb-12">
|
||||
<span className={`text-sm font-medium ${!isAnnual ? 'text-surface-50' : 'text-surface-400'}`}>
|
||||
Monthly
|
||||
</span>
|
||||
<button
|
||||
onClick={() => setIsAnnual(!isAnnual)}
|
||||
className={`relative inline-flex h-8 w-14 items-center rounded-full transition-colors ${
|
||||
isAnnual ? 'bg-brand-500' : 'bg-surface-700'
|
||||
}`}
|
||||
aria-label="Toggle annual pricing"
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-6 w-6 transform rounded-full bg-white transition-transform ${
|
||||
isAnnual ? 'translate-x-7' : 'translate-x-1'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
<span className={`text-sm font-medium ${isAnnual ? 'text-surface-50' : 'text-surface-400'}`}>
|
||||
Annual
|
||||
</span>
|
||||
{isAnnual && (
|
||||
<span className="ml-2 inline-block rounded-full bg-brand-500/20 px-3 py-1 text-sm font-semibold text-brand-300">
|
||||
Best Value
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Pricing Cards */}
|
||||
<section className="relative py-12 sm:py-16 lg:py-20">
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-5xl mx-auto">
|
||||
{plans.map((plan, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className={`relative rounded-2xl border transition-all duration-300 ${
|
||||
plan.highlighted
|
||||
? 'border-brand-500/50 bg-gradient-to-br from-surface-800 to-surface-900 shadow-2xl shadow-brand-500/20 scale-105 md:scale-105'
|
||||
: 'border-surface-700/50 bg-surface-800/50 hover:border-surface-700 hover:bg-surface-800/80'
|
||||
}`}
|
||||
>
|
||||
{/* Popular Badge */}
|
||||
{plan.highlighted && (
|
||||
<div className="absolute -top-4 left-1/2 -translate-x-1/2">
|
||||
<span className="inline-block rounded-full bg-brand-500 px-4 py-1 text-xs font-bold uppercase tracking-wide text-white">
|
||||
Most Popular
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="p-8 sm:p-10">
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<h3 className="text-2xl font-bold text-surface-50 mb-2">{plan.name}</h3>
|
||||
<p className="text-surface-400 text-sm mb-6">{plan.description}</p>
|
||||
|
||||
{/* Price */}
|
||||
<div className="flex items-baseline gap-2 mb-2">
|
||||
<span className="text-5xl font-bold text-surface-50">${plan.price}</span>
|
||||
<span className="text-surface-400">/{plan.period}</span>
|
||||
</div>
|
||||
{plan.savings && (
|
||||
<p className="text-sm text-brand-400 font-semibold">{plan.savings}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* CTA Button */}
|
||||
<Link
|
||||
href={plan.cta.href}
|
||||
className={`block w-full rounded-lg px-6 py-3 text-center font-semibold transition-all duration-200 mb-8 ${
|
||||
plan.highlighted
|
||||
? 'bg-brand-500 text-white hover:bg-brand-600 hover:shadow-lg hover:shadow-brand-500/30'
|
||||
: 'border border-surface-700 bg-surface-700/50 text-surface-50 hover:border-brand-400 hover:bg-surface-700 hover:text-brand-400'
|
||||
}`}
|
||||
>
|
||||
{plan.cta.text}
|
||||
</Link>
|
||||
|
||||
{/* Features List */}
|
||||
<div className="border-t border-surface-700/50 pt-8">
|
||||
<ul className="space-y-4">
|
||||
{plan.features.map((feature, featureIdx) => (
|
||||
<li key={featureIdx} className="flex items-start gap-3">
|
||||
{feature.startsWith('Everything in') ? (
|
||||
<span className="text-sm font-semibold text-surface-300">{feature}</span>
|
||||
) : (
|
||||
<>
|
||||
<svg className="h-5 w-5 flex-shrink-0 text-green-400 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<span className="text-surface-300 text-sm">{feature}</span>
|
||||
</>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* FAQ Section */}
|
||||
<section className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-950 to-surface-900">
|
||||
<div className="mx-auto max-w-3xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-12 text-center">
|
||||
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4">
|
||||
Frequently Asked <span className="text-brand-400">Questions</span>
|
||||
</h2>
|
||||
<p className="text-lg text-surface-400">
|
||||
Have a question? We've got answers.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* FAQ Accordion */}
|
||||
<div className="space-y-4">
|
||||
{faqs.map((faq, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="rounded-lg border border-surface-700/50 bg-surface-800/50 overflow-hidden transition-all duration-200 hover:border-surface-700"
|
||||
>
|
||||
<button
|
||||
onClick={() => setExpandedFaq(expandedFaq === idx ? null : idx)}
|
||||
className="w-full px-6 py-4 flex items-center justify-between hover:bg-surface-800/70 transition-colors"
|
||||
>
|
||||
<h3 className="text-lg font-semibold text-surface-50 text-left">{faq.question}</h3>
|
||||
<svg
|
||||
className={`h-6 w-6 flex-shrink-0 text-brand-400 transition-transform duration-200 ${
|
||||
expandedFaq === idx ? 'rotate-180' : ''
|
||||
}`}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={2}
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{expandedFaq === idx && (
|
||||
<div className="border-t border-surface-700/50 bg-surface-900/50 px-6 py-4">
|
||||
<p className="text-surface-300 leading-relaxed">{faq.answer}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Final CTA */}
|
||||
<section className="relative py-16 sm:py-20 lg:py-24 bg-surface-950">
|
||||
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
|
||||
<h2 className="text-3xl sm:text-4xl font-bold mb-6">
|
||||
Ready to Get Started?
|
||||
</h2>
|
||||
<p className="text-xl text-surface-300 mb-8 max-w-2xl mx-auto">
|
||||
Try DashCaddy free forever or upgrade to Premium for advanced features.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link
|
||||
href="/docs"
|
||||
className="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-8 py-3 text-base font-semibold text-white hover:bg-brand-600 transition-all duration-200 hover:shadow-lg hover:shadow-brand-500/30"
|
||||
>
|
||||
View Documentation
|
||||
</Link>
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-flex items-center justify-center gap-2 rounded-lg border border-surface-700 bg-surface-800/50 px-8 py-3 text-base font-semibold text-surface-50 hover:border-brand-400 hover:bg-surface-800 transition-colors"
|
||||
>
|
||||
Back to Home
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import PricingCards from '@/components/PricingCards';
|
||||
import Link from 'next/link';
|
||||
import Navbar from '@/components/Navbar';
|
||||
import Footer from '@/components/Footer';
|
||||
|
||||
export default function PricingPage() {
|
||||
const [isAnnual, setIsAnnual] = useState(false);
|
||||
|
||||
const faqs = [
|
||||
{
|
||||
question: 'Can I try Premium for free?',
|
||||
answer: 'There is no free trial. Premium features unlock when you start a paid subscription.',
|
||||
},
|
||||
{
|
||||
question: 'What happens when my subscription ends?',
|
||||
answer: 'If your subscription ends, DashCaddy falls back to the free tier. Your data remains intact.',
|
||||
},
|
||||
{
|
||||
question: 'Can I self-host the license server?',
|
||||
answer: 'Coming soon! We\'re working on a self-hosted license server option for enterprise deployments.',
|
||||
},
|
||||
{
|
||||
question: 'Do you offer refunds?',
|
||||
answer: 'Yes, we offer a 30-day money-back guarantee. If you\'re not satisfied with Premium, contact support for a full refund.',
|
||||
},
|
||||
{
|
||||
question: 'Is my data safe?',
|
||||
answer: 'DashCaddy remains self-hosted for your infrastructure. Only billing and license validation touch the hosted licensing service.',
|
||||
},
|
||||
];
|
||||
|
||||
const [expandedFaq, setExpandedFaq] = useState<number | null>(0);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen bg-surface-950 text-surface-50">
|
||||
<Navbar />
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="relative py-16 sm:py-20 lg:py-24">
|
||||
<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-96 bg-brand-500/20 rounded-full blur-3xl opacity-30 animate-pulse" />
|
||||
</div>
|
||||
|
||||
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
|
||||
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold mb-6">
|
||||
Simple, Transparent <span className="text-brand-400">Pricing</span>
|
||||
</h1>
|
||||
<p className="text-xl text-surface-300 mb-8 max-w-2xl mx-auto">
|
||||
Start free. Upgrade when you need advanced features. No surprises, no lock-in.
|
||||
</p>
|
||||
|
||||
{/* Toggle for Monthly/Yearly */}
|
||||
<div className="flex items-center justify-center gap-4 mb-12">
|
||||
<span className={`text-sm font-medium ${!isAnnual ? 'text-surface-50' : 'text-surface-400'}`}>
|
||||
Monthly
|
||||
</span>
|
||||
<button
|
||||
onClick={() => setIsAnnual(!isAnnual)}
|
||||
className={`relative inline-flex h-8 w-14 items-center rounded-full transition-colors ${
|
||||
isAnnual ? 'bg-brand-500' : 'bg-surface-700'
|
||||
}`}
|
||||
aria-label="Toggle annual pricing"
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-6 w-6 transform rounded-full bg-white transition-transform ${
|
||||
isAnnual ? 'translate-x-7' : 'translate-x-1'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
<span className={`text-sm font-medium ${isAnnual ? 'text-surface-50' : 'text-surface-400'}`}>
|
||||
Annual
|
||||
</span>
|
||||
{isAnnual && (
|
||||
<span className="ml-2 inline-block rounded-full bg-brand-500/20 px-3 py-1 text-sm font-semibold text-brand-300">
|
||||
Best Value
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Pricing Cards */}
|
||||
<section className="relative py-12 sm:py-16 lg:py-20">
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<PricingCards />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* FAQ Section */}
|
||||
<section className="relative py-16 sm:py-20 lg:py-24 bg-gradient-to-b from-surface-950 to-surface-900">
|
||||
<div className="mx-auto max-w-3xl px-4 sm:px-6 lg:px-8">
|
||||
<div className="mb-12 text-center">
|
||||
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-4">
|
||||
Frequently Asked <span className="text-brand-400">Questions</span>
|
||||
</h2>
|
||||
<p className="text-lg text-surface-400">
|
||||
Have a question? We've got answers.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* FAQ Accordion */}
|
||||
<div className="space-y-4">
|
||||
{faqs.map((faq, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="rounded-lg border border-surface-700/50 bg-surface-800/50 overflow-hidden transition-all duration-200 hover:border-surface-700"
|
||||
>
|
||||
<button
|
||||
onClick={() => setExpandedFaq(expandedFaq === idx ? null : idx)}
|
||||
className="w-full px-6 py-4 flex items-center justify-between hover:bg-surface-800/70 transition-colors"
|
||||
>
|
||||
<h3 className="text-lg font-semibold text-surface-50 text-left">{faq.question}</h3>
|
||||
<svg
|
||||
className={`h-6 w-6 flex-shrink-0 text-brand-400 transition-transform duration-200 ${
|
||||
expandedFaq === idx ? 'rotate-180' : ''
|
||||
}`}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={2}
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{expandedFaq === idx && (
|
||||
<div className="border-t border-surface-700/50 bg-surface-900/50 px-6 py-4">
|
||||
<p className="text-surface-300 leading-relaxed">{faq.answer}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Final CTA */}
|
||||
<section className="relative py-16 sm:py-20 lg:py-24 bg-surface-950">
|
||||
<div className="mx-auto max-w-4xl px-4 sm:px-6 lg:px-8 text-center">
|
||||
<h2 className="text-3xl sm:text-4xl font-bold mb-6">
|
||||
Ready to Get Started?
|
||||
</h2>
|
||||
<p className="text-xl text-surface-300 mb-8 max-w-2xl mx-auto">
|
||||
Use DashCaddy free for core self-hosting, or unlock Premium for gated features.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link
|
||||
href="/docs"
|
||||
className="inline-flex items-center justify-center gap-2 rounded-lg bg-brand-500 px-8 py-3 text-base font-semibold text-white hover:bg-brand-600 transition-all duration-200 hover:shadow-lg hover:shadow-brand-500/30"
|
||||
>
|
||||
View Documentation
|
||||
</Link>
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-flex items-center justify-center gap-2 rounded-lg border border-surface-700 bg-surface-800/50 px-8 py-3 text-base font-semibold text-surface-50 hover:border-brand-400 hover:bg-surface-800 transition-colors"
|
||||
>
|
||||
Back to Home
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import Navbar from "@/components/Navbar";
|
||||
import Footer from "@/components/Footer";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Privacy Policy - DashCaddy",
|
||||
description:
|
||||
"How DashCaddy handles your data. We believe in privacy — your server, your data, never leaving your network.",
|
||||
};
|
||||
|
||||
export default function PrivacyPage() {
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<main className="flex-1">
|
||||
<section className="pt-32 pb-20 px-4">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h1 className="text-4xl font-bold text-white mb-8">
|
||||
Privacy Policy
|
||||
</h1>
|
||||
<div className="space-y-6 text-surface-300 leading-relaxed">
|
||||
<p className="text-surface-400 text-sm">
|
||||
Last updated: June 2025
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-white">
|
||||
Your Server, Your Data
|
||||
</h2>
|
||||
<p>
|
||||
DashCaddy runs entirely on your own hardware. We do not operate
|
||||
any cloud services, and DashCaddy never phones home. All data
|
||||
— container configurations, DNS records, SSL certificates, and
|
||||
usage statistics — stays on your server.
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-white">
|
||||
What We Collect
|
||||
</h2>
|
||||
<p>
|
||||
When you purchase a premium license, our payment processor
|
||||
(Stripe) collects standard billing information. We receive only
|
||||
your email address and a license key — no credit card numbers
|
||||
touch our servers.
|
||||
</p>
|
||||
<p>
|
||||
During license activation, your server sends a one-time machine
|
||||
fingerprint to generate a unique license key. This fingerprint
|
||||
contains no personal information and is used solely to bind your
|
||||
license to your hardware.
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-white">
|
||||
License Server
|
||||
</h2>
|
||||
<p>
|
||||
Premium features require a license server URL to validate your
|
||||
subscription. When you configure this, your server will make a
|
||||
brief HTTPS call to our license server to verify your key is
|
||||
active. The call includes only your license key and machine
|
||||
identifier — no content of your services is ever transmitted.
|
||||
</p>
|
||||
<p>
|
||||
The license server is hosted on infrastructure we control. We
|
||||
do not sell, share, or monetise any data from this process.
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-white">
|
||||
Cookies
|
||||
</h2>
|
||||
<p>
|
||||
The DashCaddy dashboard itself uses no tracking cookies. The
|
||||
license server uses a single HttpOnly session cookie during
|
||||
your browser session to manage authenticated license
|
||||
management operations. No third-party analytics or advertising
|
||||
cookies are used anywhere in the product.
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-white">
|
||||
Third Parties
|
||||
</h2>
|
||||
<p>
|
||||
DashCaddy's premium checkout is processed by Stripe, Inc. Their
|
||||
privacy policy governs any data you share with them. We do not
|
||||
receive your payment card details under any circumstances.
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-white">
|
||||
Changes to This Policy
|
||||
</h2>
|
||||
<p>
|
||||
If this policy changes, the date at the top of the page will
|
||||
reflect the update. We will never retroactively change how we
|
||||
handle data already collected.
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-white">Contact</h2>
|
||||
<p>
|
||||
Questions about privacy? Email{" "}
|
||||
<a
|
||||
href="mailto:privacy@dashcaddy.net"
|
||||
className="text-brand-400 hover:text-brand-300"
|
||||
>
|
||||
privacy@dashcaddy.net
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,121 +1,121 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Suspense } from "react";
|
||||
import Navbar from "@/components/Navbar";
|
||||
import Footer from "@/components/Footer";
|
||||
|
||||
function SuccessContent() {
|
||||
const searchParams = useSearchParams();
|
||||
const sessionId = searchParams.get("session_id");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<main className="flex-1 flex items-center justify-center px-4 py-24">
|
||||
<div className="max-w-lg w-full text-center">
|
||||
{/* Success icon */}
|
||||
<div className="mx-auto w-20 h-20 rounded-full bg-green-500/20 flex items-center justify-center mb-8">
|
||||
<svg
|
||||
className="w-10 h-10 text-green-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl font-bold text-white mb-4">
|
||||
Welcome to DashCaddy Premium!
|
||||
</h1>
|
||||
|
||||
<p className="text-lg text-surface-300 mb-8">
|
||||
Your subscription is active. Check your email for your license key
|
||||
and setup instructions. Your 14-day free trial has started.
|
||||
</p>
|
||||
|
||||
<div className="glass-card rounded-xl p-6 mb-8 text-left">
|
||||
<h2 className="text-lg font-semibold text-white mb-4">
|
||||
Next Steps
|
||||
</h2>
|
||||
<ol className="space-y-3 text-surface-300">
|
||||
<li className="flex gap-3">
|
||||
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-brand-500/20 text-brand-400 text-sm flex items-center justify-center font-medium">
|
||||
1
|
||||
</span>
|
||||
<span>
|
||||
Check your email for your license key (DC-XXXXX-...)
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex gap-3">
|
||||
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-brand-500/20 text-brand-400 text-sm flex items-center justify-center font-medium">
|
||||
2
|
||||
</span>
|
||||
<span>
|
||||
Open your DashCaddy dashboard and go to Admin → License
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex gap-3">
|
||||
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-brand-500/20 text-brand-400 text-sm flex items-center justify-center font-medium">
|
||||
3
|
||||
</span>
|
||||
<span>Paste your license key and click Activate</span>
|
||||
</li>
|
||||
<li className="flex gap-3">
|
||||
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-brand-500/20 text-brand-400 text-sm flex items-center justify-center font-medium">
|
||||
4
|
||||
</span>
|
||||
<span>
|
||||
Enjoy SSO, Recipes, Docker Swarm, and all premium features!
|
||||
</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
{sessionId && (
|
||||
<p className="text-sm text-surface-500 mb-6">
|
||||
Session ID: {sessionId.substring(0, 20)}...
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link
|
||||
href="/docs"
|
||||
className="px-6 py-3 rounded-lg bg-brand-600 hover:bg-brand-500 text-white font-medium transition-colors"
|
||||
>
|
||||
View Setup Guide
|
||||
</Link>
|
||||
<Link
|
||||
href="/"
|
||||
className="px-6 py-3 rounded-lg border border-surface-700 hover:border-surface-500 text-surface-300 font-medium transition-colors"
|
||||
>
|
||||
Back to Home
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SuccessPage() {
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="text-surface-400">Loading...</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<SuccessContent />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Suspense } from "react";
|
||||
import Navbar from "@/components/Navbar";
|
||||
import Footer from "@/components/Footer";
|
||||
|
||||
function SuccessContent() {
|
||||
const searchParams = useSearchParams();
|
||||
const sessionId = searchParams.get("session_id");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<main className="flex-1 flex items-center justify-center px-4 py-24">
|
||||
<div className="max-w-lg w-full text-center">
|
||||
{/* Success icon */}
|
||||
<div className="mx-auto w-20 h-20 rounded-full bg-green-500/20 flex items-center justify-center mb-8">
|
||||
<svg
|
||||
className="w-10 h-10 text-green-400"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M5 13l4 4L19 7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<h1 className="text-4xl font-bold text-white mb-4">
|
||||
Welcome to DashCaddy Premium!
|
||||
</h1>
|
||||
|
||||
<p className="text-lg text-surface-300 mb-8">
|
||||
Your subscription is active. Check your email for your license key
|
||||
and setup instructions.
|
||||
</p>
|
||||
|
||||
<div className="glass-card rounded-xl p-6 mb-8 text-left">
|
||||
<h2 className="text-lg font-semibold text-white mb-4">
|
||||
Next Steps
|
||||
</h2>
|
||||
<ol className="space-y-3 text-surface-300">
|
||||
<li className="flex gap-3">
|
||||
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-brand-500/20 text-brand-400 text-sm flex items-center justify-center font-medium">
|
||||
1
|
||||
</span>
|
||||
<span>
|
||||
Check your email for your license key (DC-XXXXX-...)
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex gap-3">
|
||||
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-brand-500/20 text-brand-400 text-sm flex items-center justify-center font-medium">
|
||||
2
|
||||
</span>
|
||||
<span>
|
||||
Open your DashCaddy dashboard and go to Admin → License
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex gap-3">
|
||||
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-brand-500/20 text-brand-400 text-sm flex items-center justify-center font-medium">
|
||||
3
|
||||
</span>
|
||||
<span>Paste your license key and click Activate</span>
|
||||
</li>
|
||||
<li className="flex gap-3">
|
||||
<span className="flex-shrink-0 w-6 h-6 rounded-full bg-brand-500/20 text-brand-400 text-sm flex items-center justify-center font-medium">
|
||||
4
|
||||
</span>
|
||||
<span>
|
||||
Enjoy SSO, Recipes, Docker Swarm, and all premium features!
|
||||
</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
{sessionId && (
|
||||
<p className="text-sm text-surface-500 mb-6">
|
||||
Session ID: {sessionId.substring(0, 20)}...
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link
|
||||
href="/docs"
|
||||
className="px-6 py-3 rounded-lg bg-brand-600 hover:bg-brand-500 text-white font-medium transition-colors"
|
||||
>
|
||||
View Setup Guide
|
||||
</Link>
|
||||
<Link
|
||||
href="/"
|
||||
className="px-6 py-3 rounded-lg border border-surface-700 hover:border-surface-500 text-surface-300 font-medium transition-colors"
|
||||
>
|
||||
Back to Home
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SuccessPage() {
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="text-surface-400">Loading...</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<SuccessContent />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import Navbar from "@/components/Navbar";
|
||||
import Footer from "@/components/Footer";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Terms of Service - DashCaddy",
|
||||
description:
|
||||
"The terms under which you may use DashCaddy. Plain language, no fine print surprises.",
|
||||
};
|
||||
|
||||
export default function TermsPage() {
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<main className="flex-1">
|
||||
<section className="pt-32 pb-20 px-4">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h1 className="text-4xl font-bold text-white mb-8">
|
||||
Terms of Service
|
||||
</h1>
|
||||
<div className="space-y-6 text-surface-300 leading-relaxed">
|
||||
<p className="text-surface-400 text-sm">
|
||||
Last updated: June 2025
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-white">
|
||||
Acceptance of Terms
|
||||
</h2>
|
||||
<p>
|
||||
By purchasing or using DashCaddy, you agree to these terms. If
|
||||
you do not agree, do not use the software.
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-white">
|
||||
License Grant
|
||||
</h2>
|
||||
<p>
|
||||
DashCaddy is provided under a commercial license. A valid
|
||||
license key grants you the right to use premium features on one
|
||||
server installation. Licenses are bound to your hardware and
|
||||
are not transferable. A free tier covering core features is
|
||||
available without a license.
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-white">
|
||||
Permitted Use
|
||||
</h2>
|
||||
<p>
|
||||
You may use DashCaddy to manage your own servers or those of
|
||||
your clients. You may not redistribute, resell, or sublicense
|
||||
the software. You may not reverse engineer the license
|
||||
validation system.
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-white">
|
||||
Subscription & Billing
|
||||
</h2>
|
||||
<p>
|
||||
Subscriptions are billed in advance on a monthly, quarterly,
|
||||
or annual basis depending on your plan. Cancelling a
|
||||
subscription stops future billing; you retain access to
|
||||
premium features until the end of your current billing
|
||||
period. No refunds are offered for partial periods, unless
|
||||
required by applicable law.
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-white">
|
||||
Service Availability
|
||||
</h2>
|
||||
<p>
|
||||
DashCaddy is software you run on your own infrastructure. We
|
||||
do not guarantee uptime for the license validation server. If
|
||||
our license server is temporarily unavailable, premium
|
||||
features gracefully degrade — your services continue to run
|
||||
normally; they simply stop enforcing license eligibility
|
||||
checks until connectivity is restored.
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-white">
|
||||
Disclaimer
|
||||
</h2>
|
||||
<p>
|
||||
DashCaddy is provided "as is" without warranty of any kind.
|
||||
We are not liable for any damages arising from the use of this
|
||||
software. It is your responsibility to ensure your
|
||||
configuration is secure and your backups are current.
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-white">
|
||||
Account Termination
|
||||
</h2>
|
||||
<p>
|
||||
We reserve the right to revoke a license key if it is used in
|
||||
violation of these terms, including use on more servers than
|
||||
the license permits, or attempting to circumvent license
|
||||
validation.
|
||||
</p>
|
||||
|
||||
<h2 className="text-xl font-semibold text-white">
|
||||
Contact
|
||||
</h2>
|
||||
<p>
|
||||
Questions? Email{" "}
|
||||
<a
|
||||
href="mailto:legal@dashcaddy.net"
|
||||
className="text-brand-400 hover:text-brand-300"
|
||||
>
|
||||
legal@dashcaddy.net
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,139 +1,168 @@
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
|
||||
export default function Footer() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
const footerSections = [
|
||||
{
|
||||
title: 'Product',
|
||||
links: [
|
||||
{ label: 'Features', href: '/features' },
|
||||
{ label: 'Pricing', href: '/pricing' },
|
||||
{ label: 'Documentation', href: '/docs' },
|
||||
{ label: 'About', href: '/about' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Resources',
|
||||
links: [
|
||||
{ label: 'Getting Started', href: '/docs' },
|
||||
{ label: 'API Reference', href: '/docs#api' },
|
||||
{ label: 'GitHub', href: 'https://git.dashcaddy.net/sami7777/dashcaddy' },
|
||||
{ label: 'Community', href: '#' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Support',
|
||||
links: [
|
||||
{ label: 'Contact', href: 'mailto:support@dashcaddy.net' },
|
||||
{ label: 'Discord', href: '#' },
|
||||
{ label: 'Privacy Policy', href: '#' },
|
||||
{ label: 'Terms of Service', href: '#' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const socialLinks = [
|
||||
{
|
||||
icon: (
|
||||
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.6.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
|
||||
</svg>
|
||||
),
|
||||
label: 'GitHub',
|
||||
href: 'https://git.dashcaddy.net/sami7777/dashcaddy',
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M20.317 4.37a19.791 19.791 0 00-4.885-1.515a.074.074 0 00-.079.037c-.211.375-.444.864-.607 1.25a18.27 18.27 0 00-5.487 0c-.163-.386-.395-.875-.607-1.25a.077.077 0 00-.079-.037A19.736 19.736 0 003.677 4.37a.07.07 0 00-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 00.031.057 19.9 19.9 0 005.993 3.03.078.078 0 00.084-.028c.462-.63.873-1.295 1.226-1.994a.076.076 0 00-.042-.106 13.107 13.107 0 01-1.872-.892.077.077 0 01-.008-.128 10.2 10.2 0 00.372-.294.075.075 0 01.078-.01c3.928 1.793 8.18 1.793 12.062 0a.075.075 0 01.079.009c.12.098.246.198.373.295a.077.077 0 01-.006.127 12.299 12.299 0 01-1.873.892.076.076 0 00-.041.107c.359.698.77 1.364 1.225 1.994a.077.077 0 00.084.028 19.839 19.839 0 006.002-3.03.076.076 0 00.032-.057c.534-4.506-.9-8.4-3.821-11.865a.055.055 0 00-.032-.027zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.948-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.948 2.419-2.157 2.419zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.948-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.948 2.419-2.157 2.419z" />
|
||||
</svg>
|
||||
),
|
||||
label: 'Discord',
|
||||
href: '#',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<footer className="border-t border-surface-700/50 bg-surface-950">
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-12">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-8 mb-8">
|
||||
{/* Brand Section */}
|
||||
<div>
|
||||
<Link href="/" className="flex items-center gap-2 font-bold text-lg text-brand-400 hover:text-brand-300 transition-colors mb-4">
|
||||
<span>DashCaddy</span>
|
||||
</Link>
|
||||
<p className="text-surface-400 text-sm leading-relaxed mb-4">
|
||||
Self-hosted Docker dashboard with automatic SSL, DNS, and reverse proxy. Making self-hosting beautiful and effortless.
|
||||
</p>
|
||||
<div className="flex items-center gap-4">
|
||||
{socialLinks.map((link) => (
|
||||
<a
|
||||
key={link.label}
|
||||
href={link.href}
|
||||
className="text-surface-400 hover:text-brand-400 transition-colors"
|
||||
aria-label={link.label}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{link.icon}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Link Sections */}
|
||||
{footerSections.map((section) => (
|
||||
<div key={section.title}>
|
||||
<h3 className="text-sm font-semibold text-surface-50 mb-4">
|
||||
{section.title}
|
||||
</h3>
|
||||
<ul className="space-y-3">
|
||||
{section.links.map((link) => (
|
||||
<li key={link.label}>
|
||||
<Link
|
||||
href={link.href}
|
||||
className="text-sm text-surface-400 hover:text-brand-400 transition-colors"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Support Link */}
|
||||
<div className="border-t border-surface-700/50 pt-8 mb-8">
|
||||
<p className="text-sm text-surface-400">
|
||||
Need help? Email us at{' '}
|
||||
<a
|
||||
href="mailto:support@dashcaddy.net"
|
||||
className="text-brand-400 hover:text-brand-300 transition-colors font-medium"
|
||||
>
|
||||
support@dashcaddy.net
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Copyright with samiahmed7777 logo */}
|
||||
<div className="border-t border-surface-700/50 pt-8">
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-3">
|
||||
<p className="text-sm text-surface-500">
|
||||
© {currentYear} DashCaddy. All rights reserved. A product by
|
||||
</p>
|
||||
<Image
|
||||
src="/images/samiahmed7777-logo.png"
|
||||
alt="samiahmed7777"
|
||||
width={160}
|
||||
height={32}
|
||||
className="h-7 w-auto opacity-80 hover:opacity-100 transition-opacity"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
|
||||
const footerSections = [
|
||||
{
|
||||
title: 'Product',
|
||||
links: [
|
||||
{ label: 'Features', href: '/features' },
|
||||
{ label: 'Pricing', href: '/pricing' },
|
||||
{ label: 'Documentation', href: '/docs' },
|
||||
{ label: 'About', href: '/about' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Resources',
|
||||
links: [
|
||||
{ label: 'Getting Started', href: '/docs' },
|
||||
{ label: 'API Reference', href: '/docs#api' },
|
||||
{ label: 'Community', href: '#' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Support',
|
||||
links: [
|
||||
{ label: 'Contact', href: 'mailto:support@dashcaddy.net' },
|
||||
{ label: 'Discord', href: '#' },
|
||||
{ label: 'Privacy Policy', href: '#' },
|
||||
{ label: 'Terms of Service', href: '#' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const socialLinks = [
|
||||
{
|
||||
icon: (
|
||||
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M20.317 4.37a19.791 19.791 0 00-4.885-1.515a.074.074 0 00-.079.037c-.211.375-.444.864-.607 1.25a18.27 18.27 0 00-5.487 0c-.163-.386-.395-.875-.607-1.25a.077.077 0 00-.079-.037A19.736 19.736 0 003.677 4.37a.07.07 0 00-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 00.031.057 19.9 19.9 0 005.993 3.03.078.078 0 00.084-.028c.462-.63.873-1.295 1.226-1.994a.076.076 0 00-.042-.106 13.107 13.107 0 01-1.872-.892.077.077 0 01-.008-.128 10.2 10.2 0 00.372-.294.075.075 0 01.078-.01c3.928 1.793 8.18 1.793 12.062 0a.075.075 0 01.079.009c.12.098.246.198.373.295a.077.077 0 01-.006.127 12.299 12.299 0 01-1.873.892.076.076 0 00-.041.107c.359.698.77 1.364 1.225 1.994a.077.077 0 00.084.028 19.839 19.839 0 006.002-3.03.076.076 0 00.032-.057c.534-4.506-.9-8.4-3.821-11.865a.055.055 0 00-.032-.027zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.948-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.948 2.419-2.157 2.419zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.948-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.948 2.419-2.157 2.419z" />
|
||||
</svg>
|
||||
),
|
||||
label: 'Discord',
|
||||
href: '#',
|
||||
},
|
||||
];
|
||||
|
||||
const CURRENT_YEAR = new Date().getFullYear();
|
||||
|
||||
export default function Footer() {
|
||||
const footerSections = [
|
||||
{
|
||||
title: 'Product',
|
||||
links: [
|
||||
{ label: 'Features', href: '/features' },
|
||||
{ label: 'Pricing', href: '/pricing' },
|
||||
{ label: 'Documentation', href: '/docs' },
|
||||
{ label: 'About', href: '/about' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Resources',
|
||||
links: [
|
||||
{ label: 'Getting Started', href: '/docs' },
|
||||
{ label: 'API Reference', href: '/docs#api' },
|
||||
{ label: 'Discord', href: 'https://discord.gg/dashcaddy' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Support',
|
||||
links: [
|
||||
{ label: 'Contact', href: 'mailto:support@dashcaddy.net' },
|
||||
{ label: 'Privacy Policy', href: '/privacy' },
|
||||
{ label: 'Terms of Service', href: '/terms' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const socialLinks = [
|
||||
{
|
||||
icon: (
|
||||
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M20.317 4.37a19.791 19.791 0 00-4.885-1.515a.074.074 0 00-.079.037c-.211.375-.444.864-.607 1.25a18.27 18.27 0 00-5.487 0c-.163-.386-.395-.875-.607-1.25a.077.077 0 00-.079-.037A19.736 19.736 0 003.677 4.37a.07.07 0 00-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 00.031.057 19.9 19.9 0 005.993 3.03.078.078 0 00.084-.028c.462-.63.873-1.295 1.226-1.994a.076.076 0 00-.042-.106 13.107 13.107 0 01-1.872-.892.077.077 0 01-.008-.128 10.2 10.2 0 00.372-.294.075.075 0 01.078-.01c3.928 1.793 8.18 1.793 12.062 0a.075.075 0 01.079.009c.12.098.246.198.373.295a.077.077 0 01-.006.127 12.299 12.299 0 01-1.873.892.076.076 0 00-.041.107c.359.698.77 1.364 1.225 1.994a.077.077 0 00.084.028 19.839 19.839 0 006.002-3.03.076.076 0 00.032-.057c.534-4.506-.9-8.4-3.821-11.865a.055.055 0 00-.032-.027zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.948-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.948 2.419-2.157 2.419zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.948-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.948 2.419-2.157 2.419z" />
|
||||
</svg>
|
||||
),
|
||||
label: 'Discord',
|
||||
href: 'https://discord.gg/dashcaddy',
|
||||
},
|
||||
];
|
||||
return (
|
||||
<footer className="border-t border-surface-700/50 bg-surface-950">
|
||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-12">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-8 mb-8">
|
||||
{/* Brand Section */}
|
||||
<div>
|
||||
<Link href="/" className="flex items-center gap-2 font-bold text-lg text-brand-400 hover:text-brand-300 transition-colors mb-4">
|
||||
<span>DashCaddy</span>
|
||||
</Link>
|
||||
<p className="text-surface-400 text-sm leading-relaxed mb-4">
|
||||
Self-hosted Docker dashboard with automatic SSL, DNS, and reverse proxy. Making self-hosting beautiful and effortless.
|
||||
</p>
|
||||
<div className="flex items-center gap-4">
|
||||
{socialLinks.map((link) => (
|
||||
<a
|
||||
key={link.label}
|
||||
href={link.href}
|
||||
className="text-surface-400 hover:text-brand-400 transition-colors"
|
||||
aria-label={link.label}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{link.icon}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Link Sections */}
|
||||
{footerSections.map((section) => (
|
||||
<div key={section.title}>
|
||||
<h3 className="text-sm font-semibold text-surface-50 mb-4">
|
||||
{section.title}
|
||||
</h3>
|
||||
<ul className="space-y-3">
|
||||
{section.links.map((link) => (
|
||||
<li key={link.label}>
|
||||
<Link
|
||||
href={link.href}
|
||||
className="text-sm text-surface-400 hover:text-brand-400 transition-colors"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Support Link */}
|
||||
<div className="border-t border-surface-700/50 pt-8 mb-8">
|
||||
<p className="text-sm text-surface-400">
|
||||
Need help? Email us at{' '}
|
||||
<a
|
||||
href="mailto:support@dashcaddy.net"
|
||||
className="text-brand-400 hover:text-brand-300 transition-colors font-medium"
|
||||
>
|
||||
support@dashcaddy.net
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Copyright with samiahmed7777 logo */}
|
||||
<div className="border-t border-surface-700/50 pt-8">
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-3">
|
||||
<p className="text-sm text-surface-500">
|
||||
© {CURRENT_YEAR} DashCaddy. All rights reserved. A product by
|
||||
</p>
|
||||
<Image
|
||||
src="/images/samiahmed7777-logo.png"
|
||||
alt="samiahmed7777"
|
||||
width={160}
|
||||
height={32}
|
||||
className="h-7 w-auto opacity-80 hover:opacity-100 transition-opacity"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
'use client';
|
||||
|
||||
import { useMemo, useState } from 'react';
|
||||
import { createCheckoutSession } from '@/lib/licenseServer';
|
||||
|
||||
type Plan = {
|
||||
code: string;
|
||||
name: string;
|
||||
price: string;
|
||||
period: string;
|
||||
description: string;
|
||||
savings?: string | null;
|
||||
highlighted?: boolean;
|
||||
features: string[];
|
||||
};
|
||||
|
||||
// Premium features shared across all paid plans
|
||||
const PREMIUM_FEATURES = [
|
||||
'Auto-Login SSO',
|
||||
'Recipes (multi-container stack deployment)',
|
||||
'Docker Swarm orchestration'
|
||||
] as const;
|
||||
|
||||
export default function PricingCards() {
|
||||
const [loadingPlan, setLoadingPlan] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const plans = useMemo<Plan[]>(() => [
|
||||
{
|
||||
code: 'free',
|
||||
name: 'Free',
|
||||
price: '$0',
|
||||
period: 'forever',
|
||||
description: 'Everything you need to self-host DashCaddy for core use.',
|
||||
features: [
|
||||
'Dashboard and monitoring',
|
||||
'Container deployment',
|
||||
'Caddy management',
|
||||
'DNS management',
|
||||
'Health checks',
|
||||
'App templates'
|
||||
]
|
||||
},
|
||||
{
|
||||
code: 'premium_1m',
|
||||
name: 'Premium, 1 Month',
|
||||
price: '$25',
|
||||
period: 'per month',
|
||||
description: 'Premium feature access for one month.',
|
||||
features: [...PREMIUM_FEATURES]
|
||||
},
|
||||
{
|
||||
code: 'premium_3m',
|
||||
name: 'Premium, 3 Months',
|
||||
price: '$50',
|
||||
period: 'every 3 months',
|
||||
savings: 'Best short-term value',
|
||||
description: 'A better value plan for regular use.',
|
||||
highlighted: true,
|
||||
features: [...PREMIUM_FEATURES]
|
||||
},
|
||||
{
|
||||
code: 'premium_6m',
|
||||
name: 'Premium, 6 Months',
|
||||
price: '$65',
|
||||
period: 'every 6 months',
|
||||
savings: 'Strong loyalty pricing',
|
||||
description: 'The commitment plan with aggressive value.',
|
||||
features: [...PREMIUM_FEATURES]
|
||||
},
|
||||
{
|
||||
code: 'premium_12m',
|
||||
name: 'Premium, 12 Months',
|
||||
price: '$99',
|
||||
period: 'per year',
|
||||
savings: 'Best overall value',
|
||||
description: 'The best-value annual Premium plan.',
|
||||
features: [...PREMIUM_FEATURES]
|
||||
}
|
||||
], []);
|
||||
|
||||
async function handleCheckout(planCode: string) {
|
||||
try {
|
||||
setError(null);
|
||||
setLoadingPlan(planCode);
|
||||
const result = await createCheckoutSession(planCode);
|
||||
window.location.href = result.url;
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Unable to start checkout');
|
||||
} finally {
|
||||
setLoadingPlan(null);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{error && (
|
||||
<div className="rounded-lg border border-red-500/40 bg-red-500/10 px-4 py-3 text-sm text-red-200">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-5 gap-6 max-w-7xl mx-auto">
|
||||
{plans.map((plan) => {
|
||||
const isFree = plan.code === 'free';
|
||||
const isLoading = loadingPlan === plan.code;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={plan.code}
|
||||
className={`relative rounded-2xl border transition-all duration-300 ${
|
||||
plan.highlighted
|
||||
? 'border-brand-500/50 bg-gradient-to-br from-surface-800 to-surface-900 shadow-2xl shadow-brand-500/20'
|
||||
: 'border-surface-700/50 bg-surface-800/50 hover:border-surface-700 hover:bg-surface-800/80'
|
||||
}`}
|
||||
>
|
||||
{plan.highlighted && (
|
||||
<div className="absolute -top-4 left-1/2 -translate-x-1/2">
|
||||
<span className="inline-block rounded-full bg-brand-500 px-4 py-1 text-xs font-bold uppercase tracking-wide text-white">
|
||||
Recommended
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="p-8">
|
||||
<div className="mb-8">
|
||||
<h3 className="text-2xl font-bold text-surface-50 mb-2">{plan.name}</h3>
|
||||
<p className="text-surface-400 text-sm mb-6">{plan.description}</p>
|
||||
<div className="flex items-baseline gap-2 mb-2">
|
||||
<span className="text-4xl font-bold text-surface-50">{plan.price}</span>
|
||||
<span className="text-surface-400 text-sm">/{plan.period}</span>
|
||||
</div>
|
||||
{plan.savings && <p className="text-sm text-brand-400 font-semibold">{plan.savings}</p>}
|
||||
</div>
|
||||
|
||||
{isFree ? (
|
||||
<a
|
||||
href="/docs"
|
||||
className="block w-full rounded-lg px-6 py-3 text-center font-semibold transition-all duration-200 mb-8 border border-surface-700 bg-surface-700/50 text-surface-50 hover:border-brand-400 hover:bg-surface-700 hover:text-brand-400"
|
||||
>
|
||||
Use Free Tier
|
||||
</a>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => handleCheckout(plan.code)}
|
||||
disabled={Boolean(loadingPlan)}
|
||||
className={`block w-full rounded-lg px-6 py-3 text-center font-semibold transition-all duration-200 mb-8 ${
|
||||
plan.highlighted
|
||||
? 'bg-brand-500 text-white hover:bg-brand-600 hover:shadow-lg hover:shadow-brand-500/30'
|
||||
: 'border border-surface-700 bg-surface-700/50 text-surface-50 hover:border-brand-400 hover:bg-surface-700 hover:text-brand-400'
|
||||
} disabled:opacity-60 disabled:cursor-not-allowed`}
|
||||
>
|
||||
{isLoading ? 'Opening Checkout...' : 'Buy Premium'}
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="border-t border-surface-700/50 pt-8">
|
||||
<ul className="space-y-4">
|
||||
{plan.features.map((feature) => (
|
||||
<li key={feature} className="flex items-start gap-3">
|
||||
<svg className="h-5 w-5 flex-shrink-0 text-green-400 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<span className="text-surface-300 text-sm">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export const LICENSE_SERVER_URL =
|
||||
process.env.NEXT_PUBLIC_LICENSE_SERVER_URL || 'https://licenses.dashcaddy.net';
|
||||
|
||||
export async function createCheckoutSession(planCode: string, customerEmail?: string) {
|
||||
const response = await fetch(`${LICENSE_SERVER_URL}/api/checkout/session`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ planCode, customerEmail })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok || !data?.url) {
|
||||
throw new Error(data?.error || 'Unable to start checkout');
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
# Getting Started with DashCaddy
|
||||
|
||||
This tutorial walks you through the initial setup of DashCaddy — logging in, configuring your timezone, and choosing a deployment mode. By the end, you'll have your dashboard ready to use.
|
||||
|
||||
**Prerequisites:** Access to a DashCaddy instance with TOTP credentials.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Access the Dashboard
|
||||
|
||||
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.
|
||||
|
||||
**What you should see:**
|
||||
- A centered 6-digit input field
|
||||
- The DashCaddy logo above the input
|
||||
- An empty error message area below
|
||||
|
||||
Generate your TOTP code using your authenticator app (the secret was provided during account setup) and enter the 6 digits. The inputs auto-advance as you type each digit.
|
||||
|
||||
**Troubleshooting:**
|
||||
- If you see "Invalid code" error, check that your device clock is synchronized (TOTP is time-based)
|
||||
- If the input doesn't accept digits, refresh the page and try again
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Select Your Timezone
|
||||
|
||||
After successful login, the Setup Wizard appears. The first step asks for your timezone.
|
||||
|
||||
**What you should see:**
|
||||
- Welcome message: "Welcome to DashCaddy! 🎉"
|
||||
- "Let's set up your self-hosted app dashboard. This will only take a few minutes."
|
||||
- "Your Timezone" dropdown with a long list of timezones
|
||||
|
||||
Scroll or search for your timezone in the dropdown (e.g., "America/Los_Angeles" for Pacific time). Click to select it.
|
||||
|
||||
Screenshot: `screenshots/01-getting-started/light/02-timezone-selected.png`
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Choose Deployment Mode
|
||||
|
||||
After setting your timezone, click **Continue →** to proceed to the deployment mode selection.
|
||||
|
||||
**What you should see:**
|
||||
- "Professional Home Lab Setup" (default selection)
|
||||
- Options for:
|
||||
- **🚀 Simple** — Access apps via IP:Port only. No DNS or SSL setup needed.
|
||||
- **🏠 Professional Home Lab** — Custom TLD + Private DNS + Internal CA. Full HTTPS with your own certificate authority.
|
||||
- **🌍 Public Server** — Real domain + Public DNS + Let's Encrypt. Internet-accessible with trusted SSL.
|
||||
- **⚙️ Custom** — I'll configure everything myself. Full control over every setting.
|
||||
|
||||
Screenshot: `screenshots/01-getting-started/light/03-deployment-mode-selection.png`
|
||||
|
||||
Choose the mode that matches your setup:
|
||||
- For home lab use without a domain: **Simple**
|
||||
- For a home lab with custom domain (e.g., `.home`): **Professional Home Lab**
|
||||
- For a public server with real domain: **Public Server**
|
||||
- For maximum control: **Custom**
|
||||
|
||||
Click **Continue →** to proceed or **← Back** to return to the previous step.
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Complete or Skip Setup
|
||||
|
||||
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
|
||||
|
||||
**What you should see:**
|
||||
After completing or skipping setup, you'll see the main dashboard with:
|
||||
- Weather widget at top left
|
||||
- Clock showing current time
|
||||
- Status cards in the main area
|
||||
- Navigation sections: STATUS, TOOLS, ADMIN
|
||||
|
||||
Screenshot: `screenshots/01-getting-started/light/01-initial-dashboard.png`
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| TOTP code rejected | Ensure your device time is correct; try regenerating the code |
|
||||
| Setup wizard shows no apps | The wizard is for initial configuration; apps are added via TOOLS → App Selector |
|
||||
| Timezone not saving | The timezone should auto-save; refresh and check if it persisted |
|
||||
| "Continue" button not clickable | Try scrolling down — the button may be below the viewport |
|
||||
@@ -0,0 +1,118 @@
|
||||
# Customizing the Dashboard Theme
|
||||
|
||||
DashCaddy includes a powerful theme builder that lets you customize colors, accents, and visual elements. This tutorial shows how to access and use the theme builder.
|
||||
|
||||
**Prerequisites:** Logged into DashCaddy dashboard.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Open the Theme Builder
|
||||
|
||||
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).
|
||||
|
||||
**What you should see:**
|
||||
- Top bar with: Weather widget, Clock, Settings gear, Theme toggle (🎨 Light), License badge
|
||||
|
||||
Click the **🎨 Light** button (or click **Customize** text if visible) to open the Theme Builder modal.
|
||||
|
||||
Screenshot: `screenshots/03-theme-customization/light/01-light-theme-dashboard.png`
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Explore the Theme Builder
|
||||
|
||||
The Theme Builder modal opens with several sections:
|
||||
|
||||
**Header:**
|
||||
- Theme Builder title
|
||||
- Edit dropdown (currently editing "New Theme")
|
||||
- Delete button
|
||||
- Name field for your theme
|
||||
|
||||
**Start From Presets:**
|
||||
A list of built-in theme presets you can start from:
|
||||
- **Dark** — Dark background with light text
|
||||
- **Light** — Light background
|
||||
- **Blue** — Blue-tinted theme
|
||||
- **Black** — Pure black background
|
||||
- **Nord** — Nord color palette
|
||||
- **Dracula** — Dracula color scheme
|
||||
- **Solarized Dark** / **Solarized Light** — Solarized themes
|
||||
- **Taxi** — Yellow-tinted theme
|
||||
- **Ocean** — Ocean blue theme
|
||||
|
||||
**Preview Area:**
|
||||
A sample card showing how your theme looks, including:
|
||||
- Secondary text preview
|
||||
- Accent Button preview
|
||||
- Status indicators (Online/Offline)
|
||||
|
||||
Screenshot: `screenshots/03-theme-customization/light/04-theme-builder-open.png`
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Select a Base Theme
|
||||
|
||||
Click on one of the preset themes (e.g., "Dark") to start from that base. The preview will update to show how the selected preset looks.
|
||||
|
||||
**What you should see:**
|
||||
The preview card updates to reflect the selected preset's colors. The "Start from" selection highlights your choice.
|
||||
|
||||
Screenshot: `screenshots/03-theme-customization/dark/05-dark-preset-selected.png`
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Customize Colors (Optional)
|
||||
|
||||
Below the presets, you'll find granular color controls organized into sections:
|
||||
|
||||
**Base Colors:**
|
||||
- Background — Main background color
|
||||
- Card — Card/panel background color
|
||||
- Text — Primary text color
|
||||
- Muted Text — Secondary/hint text color
|
||||
- Border — Border and divider color
|
||||
|
||||
**Accent Colors:**
|
||||
- Accent — Primary accent color (buttons, links, highlights)
|
||||
- Accent Strong — Deeper accent for emphasis
|
||||
|
||||
**Status Colors:**
|
||||
- OK Background — Background for success/OK indicators
|
||||
- OK Text — Text color for success states
|
||||
- Error Bg — Background for error indicators
|
||||
- Error Text — Text color for error states
|
||||
|
||||
Each color field is editable — click on a color swatch or enter a hex value to customize.
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Save Your Theme
|
||||
|
||||
When you're satisfied with your customizations:
|
||||
|
||||
1. Click **Save Theme** at the bottom of the modal
|
||||
2. The theme will be applied to your dashboard immediately
|
||||
3. Click **Cancel** to discard changes, or **✕** to close the modal
|
||||
|
||||
Screenshot: `screenshots/03-theme-customization/dark/02-dark-theme-dashboard.png`
|
||||
|
||||
---
|
||||
|
||||
## Importing and Exporting Themes
|
||||
|
||||
The Theme Builder supports theme import/export for backup or sharing:
|
||||
|
||||
- **Import** — Load a previously exported theme file (.json)
|
||||
- **Export** — Download your current theme as a file for backup or sharing
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Theme builder button not visible | Scroll to top of dashboard — the button is in the top bar |
|
||||
| Colors not updating in preview | Click directly on a color swatch to edit it |
|
||||
| Theme not saving | Ensure you click "Save Theme" before closing the modal |
|
||||
| Preset click not working | Try clicking directly on the preset name text |
|
||||
@@ -0,0 +1,88 @@
|
||||
# Viewing Service Logs
|
||||
|
||||
DashCaddy provides a centralized log viewer that lets you monitor logs from your services in real-time. This tutorial shows how to access and use the logs viewer.
|
||||
|
||||
**Prerequisites:** Logged into DashCaddy dashboard with running services.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Access the Logs Viewer
|
||||
|
||||
The Logs viewer is found in the TOOLS section of the dashboard.
|
||||
|
||||
**What you should see:**
|
||||
- Dashboard with navigation sections: STATUS, TOOLS, ADMIN
|
||||
- Various cards and widgets in the main area
|
||||
|
||||
Look for a "Logs" button (📋 Logs) in the TOOLS section of the dashboard. Note that some menu items may require clicking on the section header (like "TOOLS") to expand the menu first.
|
||||
|
||||
Screenshot: `screenshots/04-viewing-logs/light/01-dashboard.png`
|
||||
|
||||
---
|
||||
|
||||
## Step 2: The Logs Modal
|
||||
|
||||
Click on **Logs** to open the logs viewer modal.
|
||||
|
||||
**What you should see:**
|
||||
- **Title:** DNS Logs (or relevant service name)
|
||||
- **Show dropdown:** Options to show 25, 50, 100, or 200 log lines
|
||||
- **Live/Controls:** Live streaming toggle (📡 Live) and pause button (⏸️ Pause)
|
||||
- **Close button:** ✕ in the top right
|
||||
- **Log content area:** Shows the actual log entries
|
||||
|
||||
Screenshot: `screenshots/04-viewing-logs/light/02-logs-modal-open.png`
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Understanding the Log Display
|
||||
|
||||
The log viewer shows:
|
||||
- **Timestamp** — When each log entry was created
|
||||
- **Service** — Which service generated the entry
|
||||
- **Message** — The actual log content
|
||||
|
||||
**Log Line Options:**
|
||||
Use the "Show" dropdown to control how many log lines are displayed at once:
|
||||
- 25 lines — For quick overview
|
||||
- 50 lines — Default view
|
||||
- 100 lines — For more context
|
||||
- 200 lines — For detailed analysis
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Live Log Streaming
|
||||
|
||||
DashCaddy supports real-time log streaming:
|
||||
|
||||
**To enable live streaming:**
|
||||
- Click the **📡 Live** button to start streaming logs in real-time
|
||||
- New log entries will appear automatically at the top or bottom of the list
|
||||
|
||||
**To pause streaming:**
|
||||
- Click **⏸️ Pause** to stop auto-updating
|
||||
- This lets you scroll through logs without new entries pushing content
|
||||
|
||||
**What you should see:**
|
||||
When live streaming is active, new log entries appear automatically. The "Loading logs..." message will be replaced with actual log data if logs are available from your services.
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Filtering and Navigation
|
||||
|
||||
The log viewer supports keyboard navigation:
|
||||
- Use **↑/↓ arrow keys** to navigate through log entries
|
||||
- **Enter** to select an entry (if applicable)
|
||||
- **Esc** to close the modal
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Logs show "Loading logs..." | No services are running, or services haven't generated logs yet |
|
||||
| Can't find Logs button | The button may be in a collapsed menu — click on "TOOLS" section header to expand |
|
||||
| Log entries not appearing | Live streaming may be paused — click 📡 Live to resume |
|
||||
| Too many logs | Use the "Show" dropdown to limit displayed lines |
|
||||
| Can't scroll through logs | Make sure pause (⏸️ Pause) is enabled to prevent new entries from pushing content |
|
||||
@@ -0,0 +1,86 @@
|
||||
# Using Quick Search
|
||||
|
||||
DashCaddy's Quick Search feature provides instant access to services, settings, and apps across your dashboard. This tutorial shows how to use it.
|
||||
|
||||
**Prerequisites:** Logged into DashCaddy dashboard.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Open Quick Search
|
||||
|
||||
Quick Search is accessed via a modal that can be triggered in two ways:
|
||||
|
||||
**Method 1: Keyboard shortcut**
|
||||
- Press **Ctrl+K** (or **Cmd+K** on Mac) to open Quick Search
|
||||
|
||||
**Method 2: Via the UI**
|
||||
- The Quick Search modal can be accessed from the dashboard header or menu
|
||||
|
||||
Screenshot: `screenshots/05-quick-search/light/01-dashboard.png`
|
||||
|
||||
---
|
||||
|
||||
## Step 2: The Quick Search Modal
|
||||
|
||||
When opened, the Quick Search modal appears centered on your screen with a search input field.
|
||||
|
||||
**What you should see:**
|
||||
- Search input field at the top
|
||||
- Keyboard shortcut hints below the input:
|
||||
- **ESC** — Close the modal
|
||||
- **↑↓ Navigate** — Move between search results
|
||||
- **Enter Select** — Select the highlighted result
|
||||
- **Esc Close** — Another reminder to close with Escape
|
||||
|
||||
Screenshot: `screenshots/05-quick-search/light/02-quick-search-open.png`
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Search for Services or Settings
|
||||
|
||||
Click on the search input field and start typing. Quick Search supports fuzzy matching and will show results as you type.
|
||||
|
||||
**What to search for:**
|
||||
- Service names (e.g., "Plex", "Gitea", "Nextcloud")
|
||||
- Settings names (e.g., "backup", "theme", "notifications")
|
||||
- Dashboard features (e.g., "logs", "stats", "docker")
|
||||
|
||||
Screenshot: `screenshots/05-quick-search/light/03-quick-search-typed.png`
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Navigate and Select Results
|
||||
|
||||
Use the keyboard to navigate through search results:
|
||||
|
||||
1. **↑/↓ arrows** — Move the selection highlight up or down through results
|
||||
2. **Enter** — Select and open the highlighted item
|
||||
3. **ESC** — Close the Quick Search modal without selecting anything
|
||||
|
||||
**Result types may include:**
|
||||
- Services (apps you've deployed)
|
||||
- Settings pages
|
||||
- Dashboard features and modals
|
||||
- Documentation links
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Quick Actions
|
||||
|
||||
Depending on the selected result, Quick Search can:
|
||||
- Open a service's management modal
|
||||
- Navigate to a settings page
|
||||
- Open a specific dashboard feature
|
||||
- Show detailed information about a service
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Quick Search doesn't open | Try the keyboard shortcut (Ctrl+K/Cmd+K) |
|
||||
| No results appearing | Try shorter search terms; check spelling |
|
||||
| Results not relevant | Quick Search may need services to be configured first |
|
||||
| Can't select with Enter | Make sure a result is highlighted (has focus) |
|
||||
| Modal won't close | Press ESC to close; if it doesn't work, click outside the modal |
|
||||
@@ -0,0 +1,108 @@
|
||||
# Backup & Restore Your Configuration
|
||||
|
||||
DashCaddy includes a comprehensive backup system that saves your entire configuration — services, credentials, themes, and settings — into a single file. This tutorial shows how to create backups and restore from them.
|
||||
|
||||
**Prerequisites:** Logged into DashCaddy dashboard.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Access the Backup Modal
|
||||
|
||||
The Backup feature is found in the TOOLS section of the dashboard.
|
||||
|
||||
**What you should see:**
|
||||
- Dashboard with STATUS, TOOLS, ADMIN navigation
|
||||
- Service cards and widgets
|
||||
|
||||
Navigate to the TOOLS section and click on **Backup** to open the Backup & Restore modal.
|
||||
|
||||
Screenshot: `screenshots/06-backup-restore/light/01-dashboard.png`
|
||||
|
||||
---
|
||||
|
||||
## Step 2: The Backup Modal
|
||||
|
||||
The Backup modal opens with three tabs and detailed information about what gets backed up.
|
||||
|
||||
**What you should see:**
|
||||
- **Title:** 💾 Backup & Restore
|
||||
- **Description:** "Full backup of your entire DashCaddy setup — server config, credentials, themes, and browser preferences in one file."
|
||||
- **Tab buttons:** Manual, Automated, History
|
||||
|
||||
Screenshot: `screenshots/06-backup-restore/light/02-backup-modal-open.png`
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Manual Backup (Export)
|
||||
|
||||
The **Manual** tab is selected by default and contains export options.
|
||||
|
||||
**What you should see:**
|
||||
- **📤 Export Backup** section with description:
|
||||
- "Downloads everything — services, Caddyfile, credentials, encryption key, themes, and all browser preferences."
|
||||
- **⬇️ Download Full Backup** button
|
||||
|
||||
**To create a manual backup:**
|
||||
1. Click **⬇️ Download Full Backup**
|
||||
2. Your browser will download a backup file (typically `.tar.gz` or similar archive)
|
||||
3. Save this file in a safe location
|
||||
|
||||
The backup file includes:
|
||||
- All service configurations
|
||||
- Caddy reverse proxy configuration
|
||||
- Encrypted credentials
|
||||
- Encryption keys
|
||||
- Custom themes
|
||||
- Browser preferences and settings
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Restore from Backup
|
||||
|
||||
The **📥 Restore Backup** section lets you restore from a previously saved backup file.
|
||||
|
||||
**To restore from backup:**
|
||||
1. Click the upload/restore button (may be labeled differently in your version)
|
||||
2. Select your backup file from your computer
|
||||
3. Confirm the restore operation when prompted
|
||||
4. Wait for the restore process to complete
|
||||
|
||||
**⚠️ Warning:** Restoring a backup will replace your current configuration. Make sure to:
|
||||
- Export your current configuration first (if needed)
|
||||
- Verify the backup file is from a trusted source
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Automated Backups
|
||||
|
||||
The **Automated** tab lets you schedule regular backups.
|
||||
|
||||
**What you might see:**
|
||||
- Backup schedule configuration
|
||||
- Options for backup frequency (daily, weekly, etc.)
|
||||
- Backup retention settings
|
||||
|
||||
Configure automated backups to ensure you always have recent restore points available.
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Backup History
|
||||
|
||||
The **History** tab shows previous backup operations.
|
||||
|
||||
**What you might see:**
|
||||
- List of past backups
|
||||
- Timestamps for each backup
|
||||
- Restore options for individual backups
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Download button not working | Check browser popup blocker; try a different browser |
|
||||
| Backup file won't upload | Ensure you're uploading the correct file format; check file size limits |
|
||||
| Restore fails | Verify the backup file is complete and not corrupted |
|
||||
| Automated backup not running | Check that the DashCaddy service is running; verify schedule settings |
|
||||
| Forgotten encryption key | Without the encryption key from backup, some credentials may not restore |
|
||||
@@ -0,0 +1,113 @@
|
||||
# Monitoring Resources with Stats
|
||||
|
||||
DashCaddy's Stats feature provides real-time and historical monitoring of CPU, memory, network, and disk usage for your containers. This tutorial shows how to access and interpret the monitoring data.
|
||||
|
||||
**Prerequisites:** Logged into DashCaddy dashboard with Docker containers running.
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Access the Stats Modal
|
||||
|
||||
The Stats monitoring is found in the STATUS section of the dashboard.
|
||||
|
||||
**What you should see:**
|
||||
- Dashboard with STATUS, TOOLS, ADMIN navigation
|
||||
- Various service cards and status indicators
|
||||
|
||||
Navigate to the STATUS section and click on **Stats** to open the Resource Monitor.
|
||||
|
||||
Screenshot: `screenshots/07-stats-monitoring/light/01-dashboard.png`
|
||||
|
||||
---
|
||||
|
||||
## Step 2: The Stats Modal
|
||||
|
||||
The Stats modal opens with resource monitoring data organized into tabs.
|
||||
|
||||
**What you should see:**
|
||||
- **Title:** 📊 Resource Monitor
|
||||
- **Description:** "Real-time and historical CPU, memory, network, and disk usage for containers."
|
||||
- **Tabs:** Live Stats, 24h Summary, Alerts
|
||||
|
||||
Screenshot: `screenshots/07-stats-monitoring/light/02-stats-modal-open.png`
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Live Stats View
|
||||
|
||||
The **Live Stats** tab shows real-time resource usage.
|
||||
|
||||
**What you might see:**
|
||||
- Container list with real-time metrics
|
||||
- CPU usage percentage per container
|
||||
- Memory usage (used/available)
|
||||
- Network I/O rates
|
||||
- Disk I/O rates
|
||||
- Auto-refresh indicator: "Auto-refresh every 5s"
|
||||
|
||||
**Refresh controls:**
|
||||
- **🔄 Refresh Now** — Manually trigger a refresh of all stats
|
||||
- Automatic refresh can be paused if needed
|
||||
|
||||
Screenshot: `screenshots/07-stats-monitoring/light/02-stats-modal-open.png`
|
||||
|
||||
---
|
||||
|
||||
## Step 4: 24h Summary View
|
||||
|
||||
The **24h Summary** tab shows aggregated statistics over the past 24 hours.
|
||||
|
||||
**What you might see:**
|
||||
- Peak CPU usage
|
||||
- Average memory usage
|
||||
- Total network traffic
|
||||
- Disk usage trends
|
||||
- Historical graphs if available
|
||||
|
||||
This view helps identify resource usage patterns and plan for capacity needs.
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Alerts View
|
||||
|
||||
The **Alerts** tab shows any resource-related alerts.
|
||||
|
||||
**What you might see:**
|
||||
- High CPU warnings
|
||||
- Memory pressure alerts
|
||||
- Disk space warnings
|
||||
- Network issues
|
||||
|
||||
Alerts help you stay on top of problems before they cause service disruptions.
|
||||
|
||||
---
|
||||
|
||||
## Understanding the Metrics
|
||||
|
||||
**CPU Usage:**
|
||||
- Shows percentage of CPU being used by each container
|
||||
- High CPU (>80%) sustained may indicate performance issues
|
||||
|
||||
**Memory Usage:**
|
||||
- Shows RAM used vs. available
|
||||
- Watch for containers with memory limits approaching capacity
|
||||
|
||||
**Network I/O:**
|
||||
- Bytes/packets in and out
|
||||
- Helps identify unexpected network activity
|
||||
|
||||
**Disk I/O:**
|
||||
- Read/write speeds
|
||||
- Important for storage-heavy workloads
|
||||
|
||||
---
|
||||
|
||||
## Common Issues
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Stats show "Loading container stats..." | No containers running, or Docker is not responding |
|
||||
| Auto-refresh not working | Click "🔄 Refresh Now" manually; check Docker daemon |
|
||||
| Missing container in stats | Container may have stopped; check container status |
|
||||
| Network stats show zeros | Network monitoring may not be enabled for that container |
|
||||
| High CPU alerts | Check which container is using resources; consider resource limits |
|
||||