Files
sproutly/nginx/html/index.html
derekc bd2bd43395 Add super admin panel and update README
- Admin account bootstrapped from ADMIN_EMAIL/ADMIN_PASSWORD env vars on startup
- Admin panel: list users, view content, reset passwords, disable/delete accounts
- is_admin and is_disabled columns on users table
- Disabled accounts blocked at login
- README updated with admin setup instructions and panel docs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 00:24:27 -07:00

316 lines
13 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sproutly</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="stylesheet" href="/css/style.css" />
</head>
<body>
<!-- AUTH SCREEN -->
<div id="auth-screen" class="auth-overlay">
<div class="auth-card">
<div class="auth-brand">
<span class="brand-icon">&#127807;</span>
<span class="brand-name">Sproutly</span>
</div>
<div class="auth-tabs">
<button id="tab-login" class="auth-tab active" onclick="Auth.showTab('login')">Log In</button>
<button id="tab-register" class="auth-tab" onclick="Auth.showTab('register')">Create Account</button>
</div>
<div id="auth-login-panel">
<div class="form-group">
<label class="form-label">Email</label>
<input type="email" id="auth-email" class="form-input" placeholder="you@example.com"
onkeydown="if(event.key==='Enter') Auth.submit()" />
</div>
<div class="form-group">
<label class="form-label">Password</label>
<input type="password" id="auth-password" class="form-input" placeholder="Password"
onkeydown="if(event.key==='Enter') Auth.submit()" />
</div>
<div id="auth-error" class="auth-msg error hidden"></div>
<button class="btn btn-primary btn-full" onclick="Auth.submit()">Log In</button>
</div>
<div id="auth-register-panel" class="hidden">
<div class="form-group">
<label class="form-label">Email</label>
<input type="email" id="reg-email" class="form-input" placeholder="you@example.com"
onkeydown="if(event.key==='Enter') Auth.submitRegister()" />
</div>
<div class="form-group">
<label class="form-label">Password</label>
<input type="password" id="reg-password" class="form-input" placeholder="At least 8 characters"
onkeydown="if(event.key==='Enter') Auth.submitRegister()" />
</div>
<div id="reg-error" class="auth-msg error hidden"></div>
<button class="btn btn-primary btn-full" onclick="Auth.submitRegister()">Create Account</button>
</div>
</div>
</div>
<!-- APP SHELL -->
<div id="app-shell" class="hidden">
<aside class="sidebar">
<div class="sidebar-brand">
<span class="brand-icon">&#127807;</span>
<span class="brand-name">Sproutly</span>
</div>
<nav class="sidebar-nav">
<a href="#dashboard" class="nav-link active" data-page="dashboard">
<span class="nav-icon">&#128200;</span> Dashboard
</a>
<a href="#varieties" class="nav-link" data-page="varieties">
<span class="nav-icon">&#127793;</span> Seed Library
</a>
<a href="#garden" class="nav-link" data-page="garden">
<span class="nav-icon">&#127807;</span> My Garden
</a>
<a href="#settings" class="nav-link" data-page="settings">
<span class="nav-icon">&#9881;</span> Settings
</a>
<a href="#admin" class="nav-link admin-only hidden" data-page="admin">
<span class="nav-icon">&#128272;</span> Admin
</a>
</nav>
<div class="sidebar-footer">
<span id="sidebar-user" class="sidebar-user"></span>
<span id="sidebar-date"></span>
<button class="btn-logout" onclick="Auth.logout()">Log out</button>
</div>
</aside>
<main class="main-content">
<!-- DASHBOARD -->
<section id="page-dashboard" class="page active">
<div class="page-header">
<div>
<h1 class="page-title">Dashboard</h1>
<p class="page-subtitle" id="dash-subtitle">Loading your garden...</p>
</div>
<button class="btn btn-primary" onclick="App.showAddBatchModal()">+ Log Batch</button>
</div>
<div class="stats-row" id="stats-row">
<div class="stat-card">
<div class="stat-value" id="stat-varieties"></div>
<div class="stat-label">Seed Varieties</div>
</div>
<div class="stat-card accent">
<div class="stat-value" id="stat-active"></div>
<div class="stat-label">Active Batches</div>
</div>
<div class="stat-card green">
<div class="stat-value" id="stat-garden"></div>
<div class="stat-label">In Garden</div>
</div>
<div class="stat-card warn">
<div class="stat-value" id="stat-tasks"></div>
<div class="stat-label">Tasks This Month</div>
</div>
</div>
<div class="section-header">
<h2>Action Needed</h2>
<span class="badge badge-frost" id="frost-badge"></span>
</div>
<div id="tasks-container">
<div class="empty-state">Loading tasks...</div>
</div>
<div class="section-header" style="margin-top:2rem">
<h2>Year Planting Timeline</h2>
<span class="hint-text">Based on your last frost date</span>
</div>
<div class="timeline-wrapper" id="timeline-container">
<div class="empty-state">Configure your last frost date in Settings to see the timeline.</div>
</div>
<div class="section-header" style="margin-top:2rem">
<h2>Active Batches</h2>
</div>
<div class="batch-grid" id="active-batches-container">
<div class="empty-state">No active batches. Start tracking by logging a batch!</div>
</div>
</section>
<!-- SEED LIBRARY -->
<section id="page-varieties" class="page">
<div class="page-header">
<div>
<h1 class="page-title">Seed Library</h1>
<p class="page-subtitle">Manage your plant varieties and growing schedules</p>
</div>
<button class="btn btn-primary" onclick="App.showAddVarietyModal()">+ Add Variety</button>
</div>
<div class="filter-bar">
<input type="text" id="variety-search" class="search-input" placeholder="Search varieties..." oninput="App.filterVarieties()" />
<select id="variety-cat-filter" class="select-input" onchange="App.filterVarieties()">
<option value="">All Categories</option>
<option value="vegetable">Vegetables</option>
<option value="herb">Herbs</option>
<option value="flower">Flowers</option>
<option value="fruit">Fruit</option>
</select>
</div>
<div id="varieties-container">
<div class="empty-state">Loading varieties...</div>
</div>
</section>
<!-- MY GARDEN -->
<section id="page-garden" class="page">
<div class="page-header">
<div>
<h1 class="page-title">My Garden</h1>
<p class="page-subtitle">Track your growing batches from seed to harvest</p>
</div>
<button class="btn btn-primary" onclick="App.showAddBatchModal()">+ Log Batch</button>
</div>
<div class="filter-bar">
<select id="garden-status-filter" class="select-input" onchange="App.filterBatches()">
<option value="">All Statuses</option>
<option value="planned">Planned</option>
<option value="germinating">Germinating</option>
<option value="seedling">Seedling</option>
<option value="potted_up">Potted Up</option>
<option value="hardening">Hardening Off</option>
<option value="garden">In Garden</option>
<option value="harvested">Harvested</option>
<option value="failed">Failed</option>
</select>
</div>
<div class="batch-grid" id="garden-container">
<div class="empty-state">Loading batches...</div>
</div>
</section>
<!-- SETTINGS -->
<section id="page-settings" class="page">
<div class="page-header">
<div>
<h1 class="page-title">Settings</h1>
<p class="page-subtitle">Configure your growing zone and notifications</p>
</div>
</div>
<div class="settings-grid">
<div class="settings-card">
<h3 class="settings-section-title">Growing Zone</h3>
<div class="form-group">
<label class="form-label">Location Name</label>
<input type="text" id="s-location" class="form-input" placeholder="e.g. Backyard Garden" />
</div>
<div class="form-group">
<label class="form-label">Last Spring Frost Date</label>
<input type="date" id="s-last-frost" class="form-input" />
<span class="form-hint">Used to calculate all seed starting dates</span>
</div>
<div class="form-group">
<label class="form-label">First Fall Frost Date</label>
<input type="date" id="s-first-frost" class="form-input" />
<span class="form-hint">Used for fall planting planning</span>
</div>
<div class="form-group">
<label class="form-label">Timezone</label>
<input type="text" id="s-timezone" class="form-input" placeholder="e.g. America/New_York" />
</div>
</div>
<div class="settings-card">
<h3 class="settings-section-title">Ntfy Notifications</h3>
<p class="settings-desc">
Get daily summaries on your phone via <a href="https://ntfy.sh" target="_blank">ntfy.sh</a> (free, open source push notifications).
</p>
<div class="form-group">
<label class="form-label">Ntfy Server</label>
<input type="text" id="s-ntfy-server" class="form-input" placeholder="https://ntfy.sh" />
</div>
<div class="form-group">
<label class="form-label">Ntfy Topic</label>
<input type="text" id="s-ntfy-topic" class="form-input" placeholder="my-garden-alerts" />
<span class="form-hint">Subscribe to this topic in the ntfy app</span>
</div>
<div class="form-group">
<label class="form-label">Authentication</label>
<div class="auth-toggle">
<label class="auth-toggle-option">
<input type="radio" name="ntfy-auth-type" value="none" checked onchange="App.toggleNtfyAuth(this.value)" /> None
</label>
<label class="auth-toggle-option">
<input type="radio" name="ntfy-auth-type" value="basic" onchange="App.toggleNtfyAuth(this.value)" /> Username &amp; Password
</label>
<label class="auth-toggle-option">
<input type="radio" name="ntfy-auth-type" value="token" onchange="App.toggleNtfyAuth(this.value)" /> API Key / Token
</label>
</div>
</div>
<div id="ntfy-basic-auth" class="hidden">
<div class="form-group">
<label class="form-label">Username</label>
<input type="text" id="s-ntfy-username" class="form-input" placeholder="ntfy username" autocomplete="off" />
</div>
<div class="form-group">
<label class="form-label">Password</label>
<input type="password" id="s-ntfy-password" class="form-input" placeholder="ntfy password" autocomplete="off" />
</div>
</div>
<div id="ntfy-token-auth" class="hidden">
<div class="form-group">
<label class="form-label">API Key / Access Token</label>
<input type="password" id="s-ntfy-api-key" class="form-input" placeholder="tk_..." autocomplete="off" />
<span class="form-hint">Generate in ntfy account settings</span>
</div>
</div>
<div class="form-group">
<label class="form-label">Daily Summary Time</label>
<input type="time" id="s-notif-time" class="form-input" />
</div>
<div class="btn-row">
<button class="btn btn-secondary" onclick="App.sendTestNotification()">Send Test</button>
<button class="btn btn-secondary" onclick="App.sendDailySummary()">Send Summary Now</button>
</div>
</div>
</div>
<div style="margin-top:1rem">
<button class="btn btn-primary" onclick="App.saveSettings()">Save Settings</button>
<span id="settings-status" class="settings-status"></span>
</div>
</section>
<!-- ADMIN -->
<section id="page-admin" class="page">
<div class="page-header">
<div>
<h1 class="page-title">Admin</h1>
<p class="page-subtitle">Manage user accounts</p>
</div>
</div>
<div id="admin-users-container">
<div class="empty-state">Loading users...</div>
</div>
</section>
</main>
<!-- MODALS -->
<div id="modal-overlay" class="modal-overlay hidden" onclick="App.closeModal(event)">
<div class="modal" id="modal-box">
<div class="modal-header">
<h3 id="modal-title">Modal</h3>
<button class="modal-close" onclick="App.closeModal()">&times;</button>
</div>
<div id="modal-body"></div>
</div>
</div>
<div id="toast" class="toast hidden"></div>
</div><!-- /app-shell -->
<script src="/js/app.js"></script>
</body>
</html>