249 lines
13 KiB
HTML
249 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>StackPilot — Docker Stack Builder</title>
|
|
<link rel="icon" href="/favicon.svg" />
|
|
<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@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
|
<link rel="stylesheet" href="/src/style.css" />
|
|
<link rel="manifest" href="/manifest.json" />
|
|
<meta name="theme-color" content="#6366f1" />
|
|
<link rel="apple-touch-icon" href="/icon-192.png" />
|
|
</head>
|
|
<body>
|
|
<header class="header">
|
|
<h1>🚀 StackPilot</h1>
|
|
<p class="subtitle">Build your self-hosted Docker stack in minutes</p>
|
|
<nav class="mode-nav">
|
|
<button class="mode-btn active" data-mode="builder">Stack Builder</button>
|
|
<button class="mode-btn" data-mode="wizard">Wizard</button>
|
|
<button class="mode-btn" data-mode="analyser">Analyser</button>
|
|
<button class="mode-btn" data-mode="gallery">Community</button>
|
|
</nav>
|
|
<div class="auth-bar" id="auth-bar">
|
|
<button class="auth-btn" id="auth-btn">Sign In</button>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="container">
|
|
<!-- Step 1: Global Config -->
|
|
<section class="step" id="step-config">
|
|
<h2>1. Configure Paths</h2>
|
|
<div class="os-selector">
|
|
<label class="os-option">
|
|
<input type="radio" name="os" value="linux" checked />
|
|
<span class="os-label">🐧 Linux</span>
|
|
</label>
|
|
<label class="os-option">
|
|
<input type="radio" name="os" value="macos" />
|
|
<span class="os-label">🍎 macOS</span>
|
|
</label>
|
|
<label class="os-option">
|
|
<input type="radio" name="os" value="windows" />
|
|
<span class="os-label">🪟 Windows</span>
|
|
</label>
|
|
</div>
|
|
<div class="config-grid">
|
|
<label>
|
|
<span>Config path</span>
|
|
<input type="text" id="config-path" value="/opt/stackpilot/config" placeholder="/opt/stackpilot/config" />
|
|
</label>
|
|
<label>
|
|
<span>Media path</span>
|
|
<input type="text" id="media-path" value="/mnt/media" placeholder="/mnt/media" />
|
|
</label>
|
|
<label>
|
|
<span>Downloads path</span>
|
|
<input type="text" id="downloads-path" value="/mnt/downloads" placeholder="/mnt/downloads" />
|
|
</label>
|
|
<label>
|
|
<span>Timezone</span>
|
|
<input type="text" id="timezone" value="Europe/Dublin" placeholder="Europe/London" />
|
|
</label>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Step 2: App Selection -->
|
|
<section class="step" id="step-apps">
|
|
<h2>2. Select Apps</h2>
|
|
<div class="search-bar">
|
|
<input type="text" id="app-search" placeholder="🔍 Search 186 apps..." />
|
|
</div>
|
|
<div class="presets-bar">
|
|
<span class="presets-label">Quick stacks:</span>
|
|
<button class="preset-btn" data-preset="media">🎬 Media Server</button>
|
|
<button class="preset-btn" data-preset="arr">📡 Full Arr Suite</button>
|
|
<button class="preset-btn" data-preset="ai">🤖 AI Lab</button>
|
|
<button class="preset-btn" data-preset="dev">💻 Dev Stack</button>
|
|
<button class="preset-btn" data-preset="homelab">🏠 Homelab Essentials</button>
|
|
<button class="preset-btn" data-preset="gaming">🎮 Game Servers</button>
|
|
<button class="preset-btn preset-clear" id="deselect-all-btn">✕ Deselect All</button>
|
|
</div>
|
|
<div id="dependency-warnings" class="dependency-warnings"></div>
|
|
<div id="port-conflicts" class="port-conflicts"></div>
|
|
<div id="app-grid"></div>
|
|
</section>
|
|
|
|
<!-- Step 3: Tailscale Remote Access -->
|
|
<section class="step" id="step-tailscale">
|
|
<h2>3. Remote Access (Optional)</h2>
|
|
<div class="tailscale-config">
|
|
<label class="toggle-row">
|
|
<input type="checkbox" id="tailscale-enable" />
|
|
<span>Enable Tailscale — access your services from anywhere</span>
|
|
</label>
|
|
<div class="tailscale-options hidden" id="tailscale-options">
|
|
<div class="config-grid">
|
|
<label>
|
|
<span>Tailscale Auth Key</span>
|
|
<input type="text" id="ts-authkey" placeholder="tskey-auth-..." />
|
|
</label>
|
|
<label>
|
|
<span>Hostname</span>
|
|
<input type="text" id="ts-hostname" value="stackpilot" placeholder="stackpilot" />
|
|
</label>
|
|
</div>
|
|
<label class="toggle-row sub">
|
|
<input type="checkbox" id="ts-funnel" />
|
|
<span>Enable Tailscale Funnel (expose selected services to the internet)</span>
|
|
</label>
|
|
<label class="toggle-row sub">
|
|
<input type="checkbox" id="ts-subnet" checked />
|
|
<span>Advertise as subnet router (access all services via Tailscale IP)</span>
|
|
</label>
|
|
<div class="ts-warning">
|
|
⚠️ Security note: Tailscale Funnel exposes services publicly. Subnet routing only shares with your Tailnet (your devices).
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Step 4: Preview & Download -->
|
|
<section class="step" id="step-preview">
|
|
<h2>4. Preview & Download</h2>
|
|
<div class="preview-area">
|
|
<div class="preview-tabs">
|
|
<button class="tab active" data-tab="compose">docker-compose.yml</button>
|
|
<button class="tab" data-tab="env">.env</button>
|
|
<button class="tab" data-tab="setup">setup.sh</button>
|
|
<button class="tab" data-tab="oracle">☁️ Oracle Cloud</button>
|
|
</div>
|
|
<pre class="preview-code"><code id="preview-content">Select apps above to see preview...</code></pre>
|
|
</div>
|
|
<button class="download-btn" id="download-btn" disabled>📦 Download Stack Bundle (.zip)</button>
|
|
</section>
|
|
</main>
|
|
|
|
<footer class="footer">
|
|
<p>StackPilot — No server required. Everything runs in your browser.</p>
|
|
</footer>
|
|
|
|
<!-- Community Gallery -->
|
|
<section class="step gallery-section" id="step-gallery" style="display:none;">
|
|
<h2>🌐 Community Stacks</h2>
|
|
<div class="gallery-controls">
|
|
<input type="text" id="gallery-search" class="gallery-search" placeholder="Search community stacks..." />
|
|
<select id="gallery-sort" class="gallery-sort">
|
|
<option value="stars">⭐ Most Starred</option>
|
|
<option value="downloads">📦 Most Downloaded</option>
|
|
<option value="recent">🕐 Most Recent</option>
|
|
</select>
|
|
<button class="analyse-btn" id="share-stack-btn" style="margin:0;padding:0.5rem 1.2rem;font-size:0.85rem;">+ Share My Stack</button>
|
|
</div>
|
|
<div id="gallery-grid" class="gallery-grid"></div>
|
|
</section>
|
|
|
|
<!-- Auth Modal -->
|
|
<div class="modal-overlay hidden" id="auth-modal">
|
|
<div class="modal">
|
|
<button class="modal-close" id="modal-close">×</button>
|
|
<h2 id="auth-modal-title">Sign In</h2>
|
|
<form id="auth-form" class="auth-form">
|
|
<input type="text" id="auth-username" placeholder="Username" class="auth-input" style="display:none;" />
|
|
<input type="email" id="auth-email" placeholder="Email" class="auth-input" />
|
|
<input type="password" id="auth-password" placeholder="Password" class="auth-input" />
|
|
<button type="submit" class="analyse-btn" style="width:100%;margin:0;">Sign In</button>
|
|
<p class="auth-toggle">Don't have an account? <a href="#" id="auth-switch">Sign Up</a></p>
|
|
<p class="auth-error hidden" id="auth-error"></p>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Share Stack Modal -->
|
|
<div class="modal-overlay hidden" id="share-modal">
|
|
<div class="modal">
|
|
<button class="modal-close" id="share-modal-close">×</button>
|
|
<h2>Share Your Stack</h2>
|
|
<form id="share-form" class="auth-form">
|
|
<input type="text" id="share-name" placeholder="Stack name (e.g. My Media Setup)" class="auth-input" required />
|
|
<textarea id="share-desc" placeholder="Description (optional)" class="auth-input" style="min-height:80px;resize:vertical;"></textarea>
|
|
<input type="text" id="share-tags" placeholder="Tags (comma-separated, e.g. media, arr, beginner)" class="auth-input" />
|
|
<button type="submit" class="analyse-btn" style="width:100%;margin:0;">🚀 Share Stack</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Onboarding Wizard -->
|
|
<section class="step wizard-section" id="step-wizard" style="display:none;">
|
|
<h2>🧙 Stack Wizard</h2>
|
|
<p class="wizard-desc">Tell us what you want to do, and we'll pick the right apps for you.</p>
|
|
<div class="wizard-goals" id="wizard-goals">
|
|
<label class="wizard-goal"><input type="checkbox" value="movies" /><span>🎬 Watch movies & TV shows</span></label>
|
|
<label class="wizard-goal"><input type="checkbox" value="music" /><span>🎵 Stream my music collection</span></label>
|
|
<label class="wizard-goal"><input type="checkbox" value="books" /><span>📚 Read ebooks & comics</span></label>
|
|
<label class="wizard-goal"><input type="checkbox" value="photos" /><span>📷 Manage & share photos</span></label>
|
|
<label class="wizard-goal"><input type="checkbox" value="downloads" /><span>⬇️ Automate downloads</span></label>
|
|
<label class="wizard-goal"><input type="checkbox" value="ai" /><span>🤖 Run AI models locally</span></label>
|
|
<label class="wizard-goal"><input type="checkbox" value="gaming" /><span>🎮 Host game servers</span></label>
|
|
<label class="wizard-goal"><input type="checkbox" value="smart-home" /><span>🏠 Smart home automation</span></label>
|
|
<label class="wizard-goal"><input type="checkbox" value="development" /><span>💻 Self-hosted dev tools (Git, CI/CD)</span></label>
|
|
<label class="wizard-goal"><input type="checkbox" value="cloud-storage" /><span>☁️ Cloud storage & file sync</span></label>
|
|
<label class="wizard-goal"><input type="checkbox" value="privacy" /><span>🕵️ Privacy tools (VPN, ad blocking, private search)</span></label>
|
|
<label class="wizard-goal"><input type="checkbox" value="monitoring" /><span>📊 Monitor my server & services</span></label>
|
|
<label class="wizard-goal"><input type="checkbox" value="remote-access" /><span>🌐 Access from outside my network</span></label>
|
|
<label class="wizard-goal"><input type="checkbox" value="team-chat" /><span>💬 Team communication</span></label>
|
|
<label class="wizard-goal"><input type="checkbox" value="notes" /><span>📝 Notes & knowledge base</span></label>
|
|
<label class="wizard-goal"><input type="checkbox" value="finance" /><span>💰 Personal finance tracking</span></label>
|
|
</div>
|
|
<button class="analyse-btn" id="wizard-build-btn">🚀 Build My Stack</button>
|
|
<div id="wizard-result" class="wizard-result hidden">
|
|
<h3>Your recommended stack:</h3>
|
|
<div id="wizard-apps" class="wizard-apps"></div>
|
|
<button class="analyse-btn" id="wizard-use-btn" style="margin-top:1rem;">Use This Stack →</button>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Compose Analyser Modal/Section -->
|
|
<section class="step" id="step-analyser" style="display:none;">
|
|
<h2>🔍 Analyse Existing Docker Compose</h2>
|
|
<p class="analyser-desc">Paste or drop your docker-compose.yml below to get security, performance, and best-practice recommendations.</p>
|
|
<div class="drop-zone" id="drop-zone">
|
|
<textarea id="analyser-input" class="analyser-textarea" placeholder="Paste your docker-compose.yml here, or drag & drop a file..."></textarea>
|
|
<div class="drop-overlay" id="drop-overlay">📁 Drop docker-compose.yml here</div>
|
|
</div>
|
|
<button class="analyse-btn" id="analyse-btn">Analyse</button>
|
|
<div id="analyser-results" class="analyser-results"></div>
|
|
<button class="analyse-btn fixed-btn hidden" id="show-fixed-btn">✨ Show Fixed Compose</button>
|
|
<div class="view-toggle hidden" id="view-toggle">
|
|
<button class="view-btn active" data-view="fixed">Fixed</button>
|
|
<button class="view-btn" data-view="diff">Diff</button>
|
|
</div>
|
|
<div id="fixed-preview" class="fixed-preview hidden">
|
|
<h3>Improved docker-compose.yml</h3>
|
|
<pre class="preview-code"><code id="fixed-content"></code></pre>
|
|
<button class="download-btn" id="download-fixed-btn" style="margin-top:1rem;">📦 Download Fixed Compose</button>
|
|
</div>
|
|
<div id="diff-preview" class="fixed-preview hidden">
|
|
<h3>Changes (Before → After)</h3>
|
|
<pre class="preview-code diff-code"><code id="diff-content"></code></pre>
|
|
</div>
|
|
</section>
|
|
|
|
<script type="module" src="/src/main.js"></script>
|
|
<script>if('serviceWorker' in navigator) navigator.serviceWorker.register('/sw.js');</script>
|
|
</body>
|
|
</html>
|