Upload files to "/"

This commit is contained in:
Ben
2025-12-27 03:34:59 +00:00
commit 4a87d77991
5 changed files with 4686 additions and 0 deletions

583
index.html Normal file
View File

@@ -0,0 +1,583 @@
<!DOCTYPE html>
<html lang="en" data-theme="dark" data-accent="indigo" data-card-size="normal">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Personal Home Dashboard - Access all your self-hosted services in one place">
<title>Home Dashboard</title>
<link rel="stylesheet" href="styles.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
rel="stylesheet">
</head>
<body>
<!-- Particles Background -->
<canvas id="particles-canvas"></canvas>
<div class="background-effects">
<div class="gradient-orb orb-1"></div>
<div class="gradient-orb orb-2"></div>
<div class="gradient-orb orb-3"></div>
<div class="grid-overlay"></div>
<div class="custom-bg" id="custom-bg"></div>
</div>
<div class="container">
<!-- Top Bar -->
<header class="top-bar">
<div class="greeting-section">
<h1 class="greeting" id="greeting">Good evening</h1>
<p class="date-display" id="date-display"></p>
</div>
<div class="clock-weather">
<div class="clock" id="clock">
<span class="clock-time" id="clock-time">00:00</span>
<span class="clock-seconds" id="clock-seconds">:00</span>
</div>
<div class="weather-widget" id="weather-widget">
<div class="weather-icon" id="weather-icon">🌙</div>
<div class="weather-info">
<span class="weather-temp" id="weather-temp">--°</span>
<span class="weather-desc" id="weather-desc">Loading...</span>
</div>
</div>
<button class="theme-toggle" id="theme-toggle" title="Toggle theme (T)">
<svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="5"></circle>
<path
d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42">
</path>
</svg>
<svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</button>
</div>
</header>
<!-- Stats Panel -->
<section class="stats-panel" id="stats-panel">
<div class="stat-card">
<div class="stat-icon online">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
<polyline points="22 4 12 14.01 9 11.01"></polyline>
</svg>
</div>
<div class="stat-content">
<span class="stat-value" id="stat-online">0</span>
<span class="stat-label">Online</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon offline">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
<line x1="15" y1="9" x2="9" y2="15"></line>
<line x1="9" y1="9" x2="15" y2="15"></line>
</svg>
</div>
<div class="stat-content">
<span class="stat-value" id="stat-offline">0</span>
<span class="stat-label">Offline</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon total">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="7" height="7"></rect>
<rect x="14" y="3" width="7" height="7"></rect>
<rect x="14" y="14" width="7" height="7"></rect>
<rect x="3" y="14" width="7" height="7"></rect>
</svg>
</div>
<div class="stat-content">
<span class="stat-value" id="stat-total">0</span>
<span class="stat-label">Services</span>
</div>
</div>
<div class="stat-card">
<div class="stat-icon uptime">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
</svg>
</div>
<div class="stat-content">
<span class="stat-value" id="stat-uptime">--%</span>
<span class="stat-label">Uptime</span>
</div>
</div>
</section>
<!-- Search & Actions -->
<section class="actions-bar">
<div class="search-container">
<svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.35-4.35"></path>
</svg>
<input type="text" id="search-input" class="search-input" placeholder="Search services... (Ctrl+K)">
<div class="search-shortcut">
<kbd></kbd><kbd>K</kbd>
</div>
</div>
<div class="action-buttons">
<button class="action-btn" id="add-service-btn" title="Add Service (A)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="8" x2="12" y2="16"></line>
<line x1="8" y1="12" x2="16" y2="12"></line>
</svg>
</button>
<button class="action-btn" id="refresh-btn" title="Refresh status (R)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path>
<path d="M3 3v5h5"></path>
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"></path>
<path d="M16 21h5v-5"></path>
</svg>
</button>
<button class="action-btn" id="fullscreen-btn" title="Toggle fullscreen (F)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="15 3 21 3 21 9"></polyline>
<polyline points="9 21 3 21 3 15"></polyline>
<line x1="21" y1="3" x2="14" y2="10"></line>
<line x1="3" y1="21" x2="10" y2="14"></line>
</svg>
</button>
</div>
</section>
<!-- Quick Links -->
<section class="quick-links" id="quick-links">
<!-- Populated by JavaScript -->
</section>
<!-- Category Tabs -->
<nav class="category-tabs" id="category-tabs">
<button class="category-tab active" data-category="all">
<span class="tab-icon">🏠</span>
All Services
</button>
<!-- Additional tabs populated by JavaScript -->
</nav>
<!-- Favorites Section -->
<section class="favorites-section" id="favorites-section">
<div class="section-header">
<h2 class="section-title">⭐ Favorites</h2>
</div>
<div class="favorites-grid" id="favorites-grid">
<!-- Favorites rendered here -->
</div>
</section>
<!-- Recently Accessed Section -->
<section class="recently-accessed-section" id="recently-accessed-section">
<div class="section-header">
<h2 class="section-title">🕐 Recently Accessed</h2>
</div>
<div class="recently-accessed-grid" id="recently-accessed-grid">
<!-- Recently accessed rendered here -->
</div>
</section>
<!-- Services Grid -->
<main class="services-grid" id="services-grid">
<!-- Services will be dynamically inserted here -->
</main>
<!-- RSS News Widget -->
<section class="rss-widget" id="rss-widget">
<div class="section-header">
<h2 class="section-title">📰 News Feed</h2>
<button class="rss-refresh-btn" id="rss-refresh-btn" title="Refresh feeds">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path>
<path d="M3 3v5h5"></path>
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"></path>
<path d="M16 21h5v-5"></path>
</svg>
</button>
</div>
<div class="rss-feed-container" id="rss-feed-container">
<div class="rss-loading">Loading feeds...</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="footer-left">
<span class="footer-brand" id="dashboard-title">Home Dashboard</span>
</div>
<div class="footer-center">
<p>Last updated: <span id="last-updated">Never</span></p>
</div>
<div class="footer-right">
<button class="footer-btn" id="import-btn" title="Import Config">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
</button>
<button class="footer-btn" id="export-btn" title="Export Config">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="17 8 12 3 7 8"></polyline>
<line x1="12" y1="3" x2="12" y2="15"></line>
</svg>
</button>
<button class="footer-btn" id="settings-link" title="Settings">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="3"></circle>
<path
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z">
</path>
</svg>
</button>
</div>
</footer>
</div>
<!-- Settings Modal -->
<div class="modal-overlay" id="settings-modal">
<div class="modal modal-large">
<div class="modal-header">
<h2>Dashboard Settings</h2>
<button class="modal-close" id="modal-close">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="modal-tabs">
<button class="modal-tab active" data-tab="general">General</button>
<button class="modal-tab" data-tab="appearance">Appearance</button>
<button class="modal-tab" data-tab="notifications">Notifications</button>
<button class="modal-tab" data-tab="rss">RSS Feeds</button>
</div>
<div class="modal-body">
<!-- General Tab -->
<div class="tab-content active" id="tab-general">
<div class="setting-group">
<label class="setting-label">Weather Location</label>
<input type="text" id="weather-location" class="setting-input"
placeholder="City name (e.g., London)">
</div>
<div class="setting-group">
<label class="setting-label">Temperature Unit</label>
<div class="setting-toggle-group">
<button class="setting-toggle active" data-unit="celsius">°C</button>
<button class="setting-toggle" data-unit="fahrenheit">°F</button>
</div>
</div>
<div class="setting-group">
<label class="setting-label">Status Check Interval</label>
<select id="status-interval" class="setting-select">
<option value="30000">30 seconds</option>
<option value="60000" selected>1 minute</option>
<option value="300000">5 minutes</option>
<option value="0">Disabled</option>
</select>
</div>
<div class="setting-group">
<label class="setting-label">Card Size</label>
<div class="setting-toggle-group">
<button class="setting-toggle" data-size="compact">Compact</button>
<button class="setting-toggle active" data-size="normal">Normal</button>
<button class="setting-toggle" data-size="large">Large</button>
</div>
</div>
</div>
<!-- Appearance Tab -->
<div class="tab-content" id="tab-appearance">
<div class="setting-group">
<label class="setting-label">Theme</label>
<div class="theme-grid">
<button class="theme-option active" data-theme="dark">
<div class="theme-preview dark-preview"></div>
<span>Dark</span>
</button>
<button class="theme-option" data-theme="light">
<div class="theme-preview light-preview"></div>
<span>Light</span>
</button>
<button class="theme-option" data-theme="nord">
<div class="theme-preview nord-preview"></div>
<span>Nord</span>
</button>
<button class="theme-option" data-theme="dracula">
<div class="theme-preview dracula-preview"></div>
<span>Dracula</span>
</button>
</div>
</div>
<div class="setting-group">
<label class="setting-label">Accent Color</label>
<div class="color-picker">
<button class="color-option active" data-accent="indigo"
style="--preview-color: #6366f1"></button>
<button class="color-option" data-accent="purple" style="--preview-color: #8b5cf6"></button>
<button class="color-option" data-accent="pink" style="--preview-color: #ec4899"></button>
<button class="color-option" data-accent="red" style="--preview-color: #ef4444"></button>
<button class="color-option" data-accent="orange" style="--preview-color: #f97316"></button>
<button class="color-option" data-accent="yellow" style="--preview-color: #eab308"></button>
<button class="color-option" data-accent="green" style="--preview-color: #22c55e"></button>
<button class="color-option" data-accent="teal" style="--preview-color: #14b8a6"></button>
<button class="color-option" data-accent="cyan" style="--preview-color: #06b6d4"></button>
<button class="color-option" data-accent="blue" style="--preview-color: #3b82f6"></button>
</div>
</div>
<div class="setting-group">
<label class="setting-label">Background</label>
<div class="bg-options">
<button class="bg-option active" data-bg="default">Default</button>
<button class="bg-option" data-bg="particles">Particles</button>
<button class="bg-option" data-bg="gradient">Gradient Only</button>
<button class="bg-option" data-bg="minimal">Minimal</button>
</div>
<div class="custom-bg-upload">
<label class="upload-label">
<input type="file" id="bg-upload" accept="image/*" hidden>
<span>📷 Upload Custom Background</span>
</label>
</div>
</div>
<div class="setting-group">
<label class="setting-label">
<input type="checkbox" id="particles-enabled" checked>
Enable Particles Animation
</label>
</div>
</div>
<!-- Notifications Tab -->
<div class="tab-content" id="tab-notifications">
<div class="setting-group">
<label class="setting-label">
<input type="checkbox" id="notifications-enabled">
Enable Browser Notifications
</label>
<p class="setting-hint">Get notified when a service goes offline</p>
</div>
<div class="setting-group">
<label class="setting-label">
<input type="checkbox" id="sound-enabled">
Enable Sound Alerts
</label>
</div>
</div>
<!-- RSS Feeds Tab -->
<div class="tab-content" id="tab-rss">
<div class="setting-group">
<label class="setting-label">RSS Feed URLs</label>
<p class="setting-hint">Add news feeds to display on your dashboard</p>
<div class="rss-feeds-list" id="rss-feeds-list">
<!-- Feed items rendered by JS -->
</div>
<div class="add-feed-form">
<input type="text" id="new-feed-name" class="setting-input" placeholder="Feed name">
<input type="url" id="new-feed-url" class="setting-input"
placeholder="https://example.com/rss">
<button class="btn-secondary" id="add-feed-btn">Add Feed</button>
</div>
</div>
<div class="setting-group">
<label class="setting-label">Default Feeds</label>
<div class="default-feeds">
<button class="btn-secondary" data-feed="hackernews">+ Hacker News</button>
<button class="btn-secondary" data-feed="selfhosted">+ r/selfhosted</button>
<button class="btn-secondary" data-feed="techmeme">+ Techmeme</button>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" id="settings-cancel">Cancel</button>
<button class="btn-primary" id="settings-save">Save Changes</button>
</div>
</div>
</div>
<!-- Add Service Modal -->
<div class="modal-overlay" id="add-service-modal">
<div class="modal">
<div class="modal-header">
<h2>Add New Service</h2>
<button class="modal-close" id="add-modal-close">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="modal-body">
<div class="setting-group">
<label class="setting-label">Service Name *</label>
<input type="text" id="service-name" class="setting-input" placeholder="e.g., Plex">
</div>
<div class="setting-group">
<label class="setting-label">URL *</label>
<input type="url" id="service-url" class="setting-input" placeholder="http://192.168.1.100:8080">
</div>
<div class="setting-group">
<label class="setting-label">Description</label>
<input type="text" id="service-desc" class="setting-input" placeholder="What does this service do?">
</div>
<div class="setting-group">
<label class="setting-label">Category</label>
<select id="service-category" class="setting-select">
<option value="media">Media</option>
<option value="network">Network</option>
<option value="storage">Storage</option>
<option value="automation">Automation</option>
<option value="monitoring">Monitoring</option>
<option value="security">Security</option>
<option value="development">Development</option>
<option value="other" selected>Other</option>
</select>
</div>
<div class="setting-group">
<label class="setting-label">Color</label>
<div class="color-picker">
<button class="color-option" data-color="orange" style="--preview-color: #f97316"></button>
<button class="color-option" data-color="blue" style="--preview-color: #3b82f6"></button>
<button class="color-option" data-color="green" style="--preview-color: #22c55e"></button>
<button class="color-option" data-color="purple" style="--preview-color: #8b5cf6"></button>
<button class="color-option" data-color="red" style="--preview-color: #ef4444"></button>
<button class="color-option active" data-color="cyan" style="--preview-color: #06b6d4"></button>
<button class="color-option" data-color="pink" style="--preview-color: #ec4899"></button>
<button class="color-option" data-color="yellow" style="--preview-color: #eab308"></button>
</div>
</div>
<div class="setting-group">
<label class="setting-label">Icon</label>
<select id="service-icon" class="setting-select">
<option value="default">Default</option>
<option value="plex">Plex</option>
<option value="jellyfin">Jellyfin</option>
<option value="sonarr">Sonarr</option>
<option value="radarr">Radarr</option>
<option value="pihole">Pi-hole</option>
<option value="homeassistant">Home Assistant</option>
<option value="portainer">Portainer</option>
<option value="nextcloud">Nextcloud</option>
<option value="trueNAS">TrueNAS</option>
<option value="nginx">Nginx</option>
<option value="grafana">Grafana</option>
<option value="vpn">VPN</option>
</select>
</div>
<div class="setting-group">
<label class="setting-label">Notes (optional)</label>
<textarea id="service-notes" class="setting-textarea"
placeholder="Add any notes about this service..."></textarea>
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" id="add-service-cancel">Cancel</button>
<button class="btn-primary" id="add-service-save">Add Service</button>
</div>
</div>
</div>
<!-- Service Detail Modal (for notes/edit) -->
<div class="modal-overlay" id="service-detail-modal">
<div class="modal">
<div class="modal-header">
<h2 id="detail-service-name">Service Name</h2>
<button class="modal-close" id="detail-modal-close">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="modal-body">
<div class="service-detail-status">
<div class="detail-status-indicator" id="detail-status"></div>
<span class="detail-ping" id="detail-ping">-- ms</span>
</div>
<div class="uptime-history" id="uptime-history">
<label class="setting-label">Uptime History (last 24h)</label>
<div class="uptime-bar" id="uptime-bar">
<!-- Generated by JS -->
</div>
</div>
<div class="setting-group maintenance-group">
<label class="setting-label">
<input type="checkbox" id="maintenance-enabled">
🔧 Maintenance Mode
</label>
<p class="setting-hint">Skip status checks and show as maintenance</p>
<div class="maintenance-options" id="maintenance-options">
<div class="maintenance-until">
<label>Until (optional):</label>
<input type="datetime-local" id="maintenance-until" class="setting-input">
</div>
<div class="maintenance-reason">
<label>Reason:</label>
<input type="text" id="maintenance-reason" class="setting-input"
placeholder="e.g., Server upgrade">
</div>
</div>
</div>
<div class="setting-group">
<label class="setting-label">Notes</label>
<textarea id="detail-notes" class="setting-textarea"
placeholder="Add notes for this service..."></textarea>
</div>
</div>
<div class="modal-footer">
<button class="btn-danger" id="delete-service-btn">Delete</button>
<button class="btn-secondary" id="detail-cancel">Close</button>
<button class="btn-primary" id="save-notes-btn">Save Notes</button>
</div>
</div>
</div>
<!-- Import Modal -->
<div class="modal-overlay" id="import-modal">
<div class="modal">
<div class="modal-header">
<h2>Import Configuration</h2>
<button class="modal-close" id="import-modal-close">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div class="modal-body">
<div class="setting-group">
<label class="setting-label">Paste your exported JSON config:</label>
<textarea id="import-data" class="setting-textarea import-textarea"
placeholder='{"services": [...], "settings": {...}}'></textarea>
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" id="import-cancel">Cancel</button>
<button class="btn-primary" id="import-confirm">Import</button>
</div>
</div>
</div>
<!-- Toast Notifications -->
<div class="toast-container" id="toast-container"></div>
<!-- Hidden file input for import -->
<input type="file" id="import-file" accept=".json" hidden>
<script src="config.js"></script>
<script src="app.js"></script>
</body>
</html>