Upload files to "/"
This commit is contained in:
85
README.md
Normal file
85
README.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# Home Dashboard
|
||||||
|
|
||||||
|
A beautiful, self-hosted dashboard for your home services.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 🎨 **Modern Dark Theme** - Glassmorphism design with animated gradient background
|
||||||
|
- 📊 **Live Status** - Real-time online/offline indicators for each service
|
||||||
|
- 🔍 **Search** - Filter services with Ctrl+K or click the search box
|
||||||
|
- 📱 **Responsive** - Works on desktop, tablet, and mobile
|
||||||
|
- ⚡ **Zero Dependencies** - Pure HTML/CSS/JS, no build step required
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Option 1: Open Directly
|
||||||
|
Just double-click `index.html` to open in your browser.
|
||||||
|
|
||||||
|
### Option 2: Python HTTP Server
|
||||||
|
```bash
|
||||||
|
cd "U:\AntiGravity\Self-Hosted Dashboard"
|
||||||
|
python -m http.server 8080
|
||||||
|
```
|
||||||
|
Then visit: http://localhost:8080
|
||||||
|
|
||||||
|
### Option 3: Nginx
|
||||||
|
Copy all files to your nginx web root (usually `/var/www/html/dashboard/`).
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Edit `config.js` to customize your dashboard:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const DASHBOARD_CONFIG = {
|
||||||
|
title: "My Home Lab", // Dashboard title
|
||||||
|
subtitle: "All my services", // Subtitle text
|
||||||
|
enableStatusCheck: true, // Live status checking
|
||||||
|
statusCheckInterval: 60000, // Check every 60 seconds
|
||||||
|
|
||||||
|
services: [
|
||||||
|
{
|
||||||
|
name: "Plex",
|
||||||
|
description: "Media streaming",
|
||||||
|
url: "http://192.168.1.100:32400/web",
|
||||||
|
icon: "plex",
|
||||||
|
color: "orange"
|
||||||
|
},
|
||||||
|
// Add more services here...
|
||||||
|
]
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Available Icons
|
||||||
|
`plex`, `pihole`, `jellyfin`, `sonarr`, `radarr`, `homeassistant`, `portainer`, `nextcloud`
|
||||||
|
|
||||||
|
### Available Colors
|
||||||
|
`orange`, `blue`, `green`, `purple`, `red`, `cyan`, `pink`, `yellow`, `teal`, `indigo`
|
||||||
|
|
||||||
|
## Keyboard Shortcuts
|
||||||
|
|
||||||
|
| Key | Action |
|
||||||
|
|-----|--------|
|
||||||
|
| `Ctrl+K` | Focus search |
|
||||||
|
| `Escape` | Clear search |
|
||||||
|
| `R` | Refresh status |
|
||||||
|
|
||||||
|
## Adding New Services
|
||||||
|
|
||||||
|
1. Open `config.js`
|
||||||
|
2. Add a new object to the `services` array
|
||||||
|
3. Save and refresh your browser
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
```
|
||||||
|
├── index.html # Main page
|
||||||
|
├── styles.css # All styling
|
||||||
|
├── app.js # Dashboard logic
|
||||||
|
├── config.js # Your configuration
|
||||||
|
└── README.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
Made with ❤️ for self-hosters
|
||||||
106
config.js
Normal file
106
config.js
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
/**
|
||||||
|
* Dashboard Configuration
|
||||||
|
*
|
||||||
|
* Edit this file to customize your dashboard!
|
||||||
|
* Add, remove, or modify services as needed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const DASHBOARD_CONFIG = {
|
||||||
|
// Dashboard branding
|
||||||
|
title: "Home Dashboard",
|
||||||
|
subtitle: "Your self-hosted services at a glance",
|
||||||
|
|
||||||
|
// Status check settings
|
||||||
|
enableStatusCheck: true, // Set to false to disable status checking
|
||||||
|
statusCheckInterval: 60000, // How often to check status (in milliseconds)
|
||||||
|
statusTimeout: 5000, // Timeout for status checks (in milliseconds)
|
||||||
|
|
||||||
|
// Quick Links - appear as buttons at the top
|
||||||
|
quickLinks: [
|
||||||
|
{ name: "Router", url: "http://192.168.1.1", icon: "🌐" },
|
||||||
|
{ name: "Speed Test", url: "https://fast.com", icon: "⚡" },
|
||||||
|
{ name: "GitHub", url: "https://github.com/kiyreload27", icon: "💻" }
|
||||||
|
],
|
||||||
|
|
||||||
|
// Your services - organize by category!
|
||||||
|
// Categories: media, network, storage, automation, security, monitoring, development, other
|
||||||
|
services: [
|
||||||
|
// === MEDIA ===
|
||||||
|
{
|
||||||
|
name: "Plex",
|
||||||
|
description: "Stream your personal media library anywhere",
|
||||||
|
url: "http://192.168.1.196:32400/web",
|
||||||
|
icon: "plex",
|
||||||
|
color: "orange",
|
||||||
|
category: "media"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Jellyfin",
|
||||||
|
description: "Free software media system - Plex alternative",
|
||||||
|
url: "http://localhost:8096",
|
||||||
|
icon: "jellyfin",
|
||||||
|
color: "purple",
|
||||||
|
category: "media"
|
||||||
|
},
|
||||||
|
|
||||||
|
// === NETWORK ===
|
||||||
|
|
||||||
|
// === STORAGE ===
|
||||||
|
{
|
||||||
|
name: "TrueNAS",
|
||||||
|
description: "Self-hosted cloud storage and Application Server",
|
||||||
|
url: "http://192.168.1.196",
|
||||||
|
icon: "trueNAS",
|
||||||
|
color: "blue",
|
||||||
|
category: "storage"
|
||||||
|
},
|
||||||
|
|
||||||
|
// === MONITORING ===
|
||||||
|
{
|
||||||
|
name: "Portainer",
|
||||||
|
description: "Docker container management and monitoring",
|
||||||
|
url: "http://localhost:9000",
|
||||||
|
icon: "portainer",
|
||||||
|
color: "cyan",
|
||||||
|
category: "monitoring"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service Icons (SVG)
|
||||||
|
* Add custom icons for your services here
|
||||||
|
*/
|
||||||
|
const SERVICE_ICONS = {
|
||||||
|
plex: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M11.643 0H4.68l7.679 12L4.68 24h6.963L19.32 12 11.643 0z"/></svg>`,
|
||||||
|
|
||||||
|
pihole: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>`,
|
||||||
|
|
||||||
|
jellyfin: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm0 2.182a9.818 9.818 0 110 19.636 9.818 9.818 0 010-19.636zm0 3.273a6.545 6.545 0 100 13.09 6.545 6.545 0 000-13.09zm0 2.181a4.364 4.364 0 110 8.728 4.364 4.364 0 010-8.728z"/></svg>`,
|
||||||
|
|
||||||
|
sonarr: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-2-5.5l6-4.5-6-4.5v9z"/></svg>`,
|
||||||
|
|
||||||
|
radarr: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18 4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4h-4z"/></svg>`,
|
||||||
|
|
||||||
|
homeassistant: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.002 0a1.5 1.5 0 0 0-1.041.426L.453 10.29a.75.75 0 0 0 .5 1.312h1.545V21.75A2.252 2.252 0 0 0 4.75 24h14.5a2.252 2.252 0 0 0 2.252-2.25V11.602h1.545a.75.75 0 0 0 .5-1.312L13.04.426A1.5 1.5 0 0 0 12.002 0zm-.002 4.406l6.375 5.814v11.28H5.627V10.22L12 4.406zm0 4.669a3.375 3.375 0 1 0 0 6.75 3.375 3.375 0 0 0 0-6.75z"/></svg>`,
|
||||||
|
|
||||||
|
portainer: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.504 0c-.155 0-.31.005-.465.015A10.488 10.488 0 002.09 9.053a10.49 10.49 0 00-.504 3.2c0 5.799 4.701 10.5 10.5 10.5s10.5-4.701 10.5-10.5S18.303 0 12.504 0zm0 2.004a8.496 8.496 0 018.496 8.496 8.496 8.496 0 01-8.496 8.496 8.496 8.496 0 01-8.496-8.496 8.496 8.496 0 018.496-8.496zM8.25 7.5v9h1.5V12h4.5v4.5h1.5v-9h-1.5V10.5h-4.5V7.5z"/></svg>`,
|
||||||
|
|
||||||
|
nextcloud: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12.018 6.537c-2.5 0-4.6 1.712-5.241 4.015-.56-.341-1.21-.534-1.9-.534-2.009 0-3.638 1.629-3.638 3.638s1.629 3.638 3.638 3.638c.54 0 1.054-.119 1.517-.329.74 1.468 2.272 2.48 4.037 2.48 1.684 0 3.158-.923 3.943-2.287.694.267 1.456.414 2.257.414 3.439 0 6.228-2.789 6.228-6.228s-2.789-6.228-6.228-6.228c-.91 0-1.774.195-2.553.546-.78-1.636-2.444-2.765-4.36-2.765-1.873 0-3.503 1.07-4.313 2.632a5.19 5.19 0 0 1 1.613-.355z"/></svg>`,
|
||||||
|
|
||||||
|
grafana: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M22.5 12c0 5.799-4.701 10.5-10.5 10.5S1.5 17.799 1.5 12 6.201 1.5 12 1.5 22.5 6.201 22.5 12zm-2.437-2.062l-.007-.058a.58.58 0 0 0-.574-.505h-.031c-.188 0-.35.094-.451.234a7.44 7.44 0 0 0-.78-.558l.004-.023a.58.58 0 0 0-.574-.505c-.32 0-.58.26-.58.58 0 .03.006.056.01.084a7.547 7.547 0 0 0-4.08-1.187c-4.125 0-7.5 3.375-7.5 7.5s3.375 7.5 7.5 7.5 7.5-3.375 7.5-7.5c0-.878-.155-1.72-.434-2.505h-.003z"/></svg>`,
|
||||||
|
|
||||||
|
transmission: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-6v-4l3.5 2-3.5 2z"/></svg>`,
|
||||||
|
|
||||||
|
vpn: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>`,
|
||||||
|
|
||||||
|
nginx: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 0L1.605 6v12L12 24l10.395-6V6L12 0zm0 2.311l8.211 4.742v9.494L12 21.289l-8.211-4.742V7.053L12 2.311zm0 3.936L7.342 9.165v5.67L12 17.753l4.658-2.918v-5.67L12 6.247z"/></svg>`,
|
||||||
|
|
||||||
|
trueNAS: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L2 7v10l10 5 10-5V7L12 2zm0 2.18l6.9 3.45L12 11.08 5.1 7.63 12 4.18zM4 8.82l7 3.5v7.36l-7-3.5V8.82zm9 10.86v-7.36l7-3.5v7.36l-7 3.5z"/></svg>`,
|
||||||
|
|
||||||
|
unraid: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm0 4.5a7.5 7.5 0 1 1 0 15 7.5 7.5 0 0 1 0-15z"/></svg>`,
|
||||||
|
|
||||||
|
proxmox: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M2 3h8v8H2V3zm12 0h8v8h-8V3zM2 13h8v8H2v-8zm12 0h8v8h-8v-8z"/></svg>`,
|
||||||
|
|
||||||
|
default: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 9h6v6H9z"/></svg>`
|
||||||
|
};
|
||||||
583
index.html
Normal file
583
index.html
Normal 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>
|
||||||
2395
styles.css
Normal file
2395
styles.css
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user