diff --git a/package.json b/package.json index 55e6c92..6ccf587 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@aws-sdk/lib-storage": "^3.650.0", "@aws-sdk/s3-request-presigner": "^3.650.0", "@smithy/node-http-handler": "^3.2.0", + "archiver": "^7.0.1", "express": "^4.21.0", "multer": "^1.4.5-lts.1" }, diff --git a/public/index.html b/public/index.html index ef597bf..8ecbb0f 100644 --- a/public/index.html +++ b/public/index.html @@ -320,6 +320,15 @@ body:not([data-theme="light"]) .header-wordmark{filter:invert(1)} .admin-tab.active{color:var(--blue-bright);border-bottom-color:var(--blue-bright)} .admin-panel{display:none} .admin-panel.active{display:block} +/* EXTENSION INSTALL GUIDE */ +.ext-steps{display:flex;flex-direction:column;gap:.88rem;margin-bottom:1.5rem} +.ext-step{display:flex;gap:1rem;align-items:flex-start} +.ext-step-num{flex-shrink:0;width:28px;height:28px;border-radius:50%;background:var(--blue);color:#fff;font-size:.78rem;font-weight:700;display:flex;align-items:center;justify-content:center;margin-top:.1rem} +.ext-step-body{flex:1} +.ext-step-body strong{display:block;font-size:.88rem;color:var(--text-primary);margin-bottom:.25rem} +.ext-step-body p{font-size:.82rem;color:var(--text-secondary);margin:0;line-height:1.55} +.ext-step-body code{background:var(--bg-secondary);border:1px solid var(--border);border-radius:4px;padding:.1rem .4rem;font-family:'Courier New',monospace;font-size:.8rem;color:var(--blue-bright)} +.ext-note{background:var(--bg-secondary);border:1px solid var(--border);border-left:3px solid var(--dragon);border-radius:8px;padding:1rem 1.1rem;font-size:.82rem;color:var(--text-secondary);line-height:1.6} /* FORMS */ .form-group{margin-bottom:1rem} @@ -520,6 +529,7 @@ body:not([data-theme="light"]) .header-wordmark{filter:invert(1)}
S3 Storage
UDP Relay
AMPP
+
🧩 Extension
Users
Folders
@@ -578,6 +588,58 @@ body:not([data-theme="light"]) .header-wordmark{filter:invert(1)}
+ +
+
Chrome Extension — UDP Upload
+

The Dragon Wind Chrome extension enables UDP-accelerated uploads directly from your browser. Download the extension package below, then follow the steps to install it.

+ + + +
Installation Steps
+
+
+
1
+
+ Unzip the downloaded file +

Extract dragon-wind-extension.zip to a permanent folder on your computer — Chrome needs this folder to stay in place.

+
+
+
+
2
+
+ Open Chrome Extensions +

In Chrome, navigate to chrome://extensions or open the menu → More Tools → Extensions.

+
+
+
+
3
+
+ Enable Developer Mode +

Toggle the Developer mode switch in the top-right corner of the Extensions page.

+
+
+
+
4
+
+ Load the extension +

Click Load unpacked, then navigate to and select the dragon-wind-extension folder you unzipped in step 1.

+
+
+
+
5
+
+ Configure the extension +

Click the 🐉 Dragon Wind icon in Chrome's toolbar. Enter this server's URL and log in with your credentials. UDP uploads will now be available from any page.

+
+
+
+ +
+ 📡 UDP Port Forwarding
+ For uploads from outside your local network, make sure your UDP relay port is forwarded in your router. You can find your assigned UDP port in the .env file (RELAY_UDP_PORT) or check the UDP Relay tab above. +
+
+
User Management
@@ -753,7 +815,7 @@ function switchPage(name) { function switchAdminTab(name) { document.querySelectorAll('.admin-tab').forEach((t,i) => { - t.classList.toggle('active', ['s3','relay','ampp','users','folders'][i] === name); + t.classList.toggle('active', ['s3','relay','ampp','extension','users','folders'][i] === name); }); document.querySelectorAll('.admin-panel').forEach(p => p.classList.toggle('active', p.id === `admin-${name}`)); if (name === 'users') loadUsers(); @@ -761,6 +823,25 @@ function switchAdminTab(name) { if (name === 'ampp') loadAmppConfig(); } +async function downloadExtension() { + const btn = event.target; + btn.disabled = true; btn.textContent = '⏳ Preparing download…'; + try { + const res = await fetch('/api/extension/download', { headers: { 'Authorization': 'Bearer ' + authToken } }); + if (!res.ok) throw new Error(`Server error ${res.status}`); + const blob = await res.blob(); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; a.download = 'dragon-wind-extension.zip'; a.click(); + URL.revokeObjectURL(url); + btn.textContent = '✅ Downloaded!'; + setTimeout(() => { btn.disabled = false; btn.textContent = '⬇️ Download Extension (.zip)'; }, 3000); + } catch(e) { + btn.disabled = false; btn.textContent = '⬇️ Download Extension (.zip)'; + showToast('Download failed: ' + e.message, 'error'); + } +} + // ============================================================ // UPLOAD MODE // ============================================================ diff --git a/server.js b/server.js index 406dbc9..c95057e 100644 --- a/server.js +++ b/server.js @@ -10,6 +10,7 @@ const { NodeHttpHandler } = require("@smithy/node-http-handler"); const fs = require("fs"); const https = require("https"); const http = require("http"); +const archiver = require("archiver"); require("events").defaultMaxListeners = 50; @@ -640,6 +641,21 @@ app.get("/api/ampp/jobs/:jobId", requireAuth, async (req, res) => { } catch (err) { res.status(500).json({ success: false, error: err.message }); } }); +// ==================== CHROME EXTENSION DOWNLOAD ==================== +app.get("/api/extension/download", requireAdmin, (req, res) => { + const extDir = path.join(__dirname, "chrome-extension"); + if (!fs.existsSync(extDir)) { + return res.status(404).json({ success: false, error: "chrome-extension directory not found" }); + } + res.setHeader("Content-Type", "application/zip"); + res.setHeader("Content-Disposition", "attachment; filename=\"dragon-wind-extension.zip\""); + const archive = archiver("zip", { zlib: { level: 6 } }); + archive.on("error", (err) => { console.error("[Extension ZIP]", err.message); res.end(); }); + archive.pipe(res); + archive.directory(extDir, "dragon-wind-extension"); + archive.finalize(); +}); + // ==================== HEALTH ==================== app.get("/api/health", (req, res) => { res.json({