feat: Chrome extension download + install guide in admin panel
- GET /api/extension/download — streams chrome-extension/ as a .zip using archiver
- Admin → 🧩 Extension tab with:
- One-click download button (fetches zip via auth'd API, triggers browser save)
- 5-step install guide: unzip, chrome://extensions, developer mode, load unpacked, configure
- UDP port-forwarding reminder note
- Added archiver dependency to package.json
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
fbec32dcc4
commit
895145e1ed
3 changed files with 99 additions and 1 deletions
|
|
@ -12,6 +12,7 @@
|
||||||
"@aws-sdk/lib-storage": "^3.650.0",
|
"@aws-sdk/lib-storage": "^3.650.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.650.0",
|
"@aws-sdk/s3-request-presigner": "^3.650.0",
|
||||||
"@smithy/node-http-handler": "^3.2.0",
|
"@smithy/node-http-handler": "^3.2.0",
|
||||||
|
"archiver": "^7.0.1",
|
||||||
"express": "^4.21.0",
|
"express": "^4.21.0",
|
||||||
"multer": "^1.4.5-lts.1"
|
"multer": "^1.4.5-lts.1"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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-tab.active{color:var(--blue-bright);border-bottom-color:var(--blue-bright)}
|
||||||
.admin-panel{display:none}
|
.admin-panel{display:none}
|
||||||
.admin-panel.active{display:block}
|
.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 */
|
/* FORMS */
|
||||||
.form-group{margin-bottom:1rem}
|
.form-group{margin-bottom:1rem}
|
||||||
|
|
@ -520,6 +529,7 @@ body:not([data-theme="light"]) .header-wordmark{filter:invert(1)}
|
||||||
<div class="admin-tab active" onclick="switchAdminTab('s3')">S3 Storage</div>
|
<div class="admin-tab active" onclick="switchAdminTab('s3')">S3 Storage</div>
|
||||||
<div class="admin-tab" onclick="switchAdminTab('relay')">UDP Relay</div>
|
<div class="admin-tab" onclick="switchAdminTab('relay')">UDP Relay</div>
|
||||||
<div class="admin-tab" onclick="switchAdminTab('ampp')">AMPP</div>
|
<div class="admin-tab" onclick="switchAdminTab('ampp')">AMPP</div>
|
||||||
|
<div class="admin-tab" onclick="switchAdminTab('extension')">🧩 Extension</div>
|
||||||
<div class="admin-tab" onclick="switchAdminTab('users')">Users</div>
|
<div class="admin-tab" onclick="switchAdminTab('users')">Users</div>
|
||||||
<div class="admin-tab" onclick="switchAdminTab('folders')">Folders</div>
|
<div class="admin-tab" onclick="switchAdminTab('folders')">Folders</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -578,6 +588,58 @@ body:not([data-theme="light"]) .header-wordmark{filter:invert(1)}
|
||||||
<div class="status-msg" id="ampp-status"></div>
|
<div class="status-msg" id="ampp-status"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Extension -->
|
||||||
|
<div class="admin-panel" id="admin-extension">
|
||||||
|
<div class="section-title">Chrome Extension — UDP Upload</div>
|
||||||
|
<p style="color:var(--text-secondary);margin-bottom:1.5rem;line-height:1.6">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.</p>
|
||||||
|
|
||||||
|
<button class="btn-primary" style="margin-bottom:2rem" onclick="downloadExtension()">⬇️ Download Extension (.zip)</button>
|
||||||
|
|
||||||
|
<div class="section-title" style="font-size:.8rem;margin-bottom:1rem">Installation Steps</div>
|
||||||
|
<div class="ext-steps">
|
||||||
|
<div class="ext-step">
|
||||||
|
<div class="ext-step-num">1</div>
|
||||||
|
<div class="ext-step-body">
|
||||||
|
<strong>Unzip the downloaded file</strong>
|
||||||
|
<p>Extract <code>dragon-wind-extension.zip</code> to a permanent folder on your computer — Chrome needs this folder to stay in place.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ext-step">
|
||||||
|
<div class="ext-step-num">2</div>
|
||||||
|
<div class="ext-step-body">
|
||||||
|
<strong>Open Chrome Extensions</strong>
|
||||||
|
<p>In Chrome, navigate to <code>chrome://extensions</code> or open the menu → More Tools → Extensions.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ext-step">
|
||||||
|
<div class="ext-step-num">3</div>
|
||||||
|
<div class="ext-step-body">
|
||||||
|
<strong>Enable Developer Mode</strong>
|
||||||
|
<p>Toggle the <strong>Developer mode</strong> switch in the top-right corner of the Extensions page.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ext-step">
|
||||||
|
<div class="ext-step-num">4</div>
|
||||||
|
<div class="ext-step-body">
|
||||||
|
<strong>Load the extension</strong>
|
||||||
|
<p>Click <strong>Load unpacked</strong>, then navigate to and select the <code>dragon-wind-extension</code> folder you unzipped in step 1.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ext-step">
|
||||||
|
<div class="ext-step-num">5</div>
|
||||||
|
<div class="ext-step-body">
|
||||||
|
<strong>Configure the extension</strong>
|
||||||
|
<p>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.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ext-note">
|
||||||
|
<strong>📡 UDP Port Forwarding</strong><br>
|
||||||
|
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 <strong>.env</strong> file (<code>RELAY_UDP_PORT</code>) or check the UDP Relay tab above.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Users -->
|
<!-- Users -->
|
||||||
<div class="admin-panel" id="admin-users">
|
<div class="admin-panel" id="admin-users">
|
||||||
<div class="section-title">User Management</div>
|
<div class="section-title">User Management</div>
|
||||||
|
|
@ -753,7 +815,7 @@ function switchPage(name) {
|
||||||
|
|
||||||
function switchAdminTab(name) {
|
function switchAdminTab(name) {
|
||||||
document.querySelectorAll('.admin-tab').forEach((t,i) => {
|
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}`));
|
document.querySelectorAll('.admin-panel').forEach(p => p.classList.toggle('active', p.id === `admin-${name}`));
|
||||||
if (name === 'users') loadUsers();
|
if (name === 'users') loadUsers();
|
||||||
|
|
@ -761,6 +823,25 @@ function switchAdminTab(name) {
|
||||||
if (name === 'ampp') loadAmppConfig();
|
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
|
// UPLOAD MODE
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
16
server.js
16
server.js
|
|
@ -10,6 +10,7 @@ const { NodeHttpHandler } = require("@smithy/node-http-handler");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const https = require("https");
|
const https = require("https");
|
||||||
const http = require("http");
|
const http = require("http");
|
||||||
|
const archiver = require("archiver");
|
||||||
|
|
||||||
require("events").defaultMaxListeners = 50;
|
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 }); }
|
} 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 ====================
|
// ==================== HEALTH ====================
|
||||||
app.get("/api/health", (req, res) => {
|
app.get("/api/health", (req, res) => {
|
||||||
res.json({
|
res.json({
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue