simplify onboarding Step 3 port forwarding to clean static list

- Replace complex per-service/health-check UI with a clear, hardcoded
  table of required ports (80, 443, 22, 8448) and an optional Element
  Calling section (7881 TCP, 7882-7894 UDP, 5349 TCP, 3478 UDP,
  30000-40000 TCP/UDP).
- Add totals line: 4 openings without Element Calling, 9 with.
- Drop /api/ports/health fetch and all dynamic breakdowns (affected
  services loop, closed-port warnings, "View All Required Ports" table).
- Keep internal-IP display box, SSL-cert warning, and "How to set up
  port forwarding" collapsible section.
- Add prominent note that each port only needs to be forwarded once.
- Update Step 3 header description in onboarding.html to match.

Agent-Logs-Url: https://github.com/naturallaw777/staging_alpha/sessions/523e0770-f144-4f47-932b-c0d40782a35b

Co-authored-by: naturallaw777 <99053422+naturallaw777@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-04-03 20:32:05 +00:00
committed by GitHub
parent 8cf8fcdf82
commit c7974c7aa9
2 changed files with 52 additions and 88 deletions

View File

@@ -228,114 +228,79 @@ async function loadStep3() {
body.innerHTML = '<p class="onboarding-loading">Checking ports…</p>';
var networkData = null;
var portHealth = null;
try {
var results = await Promise.all([
apiFetch("/api/network"),
apiFetch("/api/ports/health"),
]);
networkData = results[0];
portHealth = results[1];
networkData = await apiFetch("/api/network");
} catch (err) {
body.innerHTML = '<p class="onboarding-error">⚠ Could not load port data: ' + escHtml(err.message) + '</p>';
body.innerHTML = '<p class="onboarding-error">⚠ Could not load network data: ' + escHtml(err.message) + '</p>';
return;
}
var internalIp = (networkData && networkData.internal_ip) || "unknown";
var html = '<div class="onboarding-port-warn" style="margin-bottom:16px;">'
+ '⚠ <strong>IMPORTANT: Ports 80 (HTTP) and 443 (HTTPS) MUST be forwarded first.</strong><br>'
+ 'Caddy uses these ports to obtain SSL certificates from Let\'s Encrypt. '
+ 'If these ports are closed, certificate authentication will fail and '
+ 'none of your domain-based services will work over HTTPS.'
+ '</div>';
var ip = escHtml(internalIp);
var html = '<p class="onboarding-port-note" style="margin-bottom:14px;">'
+ '⚠ <strong>Each port only needs to be forwarded once — all services share the same ports.</strong>'
+ '</p>';
html += '<div class="onboarding-port-ip">';
html += ' <span class="onboarding-port-ip-label">Forward ports to this machine\'s internal IP:</span>';
html += ' <span class="port-req-internal-ip">' + escHtml(internalIp) + '</span>';
html += ' <span class="port-req-internal-ip">' + ip + '</span>';
html += '</div>';
// Required ports table
html += '<div class="onboarding-port-section" style="margin-bottom:20px;">';
html += '<div class="onboarding-port-section-title" style="font-weight:700;margin-bottom:8px;">Required Ports — open these on your router:</div>';
html += '<table class="onboarding-port-table">';
html += '<thead><tr><th>Port</th><th>Protocol</th><th>Forward&nbsp;to</th><th>Purpose</th></tr></thead>';
html += '<tbody>';
html += '<tr><td class="port-req-port">80</td><td class="port-req-proto">TCP</td><td class="port-req-internal-ip">' + ip + '</td><td class="port-req-desc">HTTP</td></tr>';
html += '<tr><td class="port-req-port">443</td><td class="port-req-proto">TCP</td><td class="port-req-internal-ip">' + ip + '</td><td class="port-req-desc">HTTPS</td></tr>';
html += '<tr><td class="port-req-port">22</td><td class="port-req-proto">TCP</td><td class="port-req-internal-ip">' + ip + '</td><td class="port-req-desc">SSH Remote Access</td></tr>';
html += '<tr><td class="port-req-port">8448</td><td class="port-req-proto">TCP</td><td class="port-req-internal-ip">' + ip + '</td><td class="port-req-desc">Matrix Federation</td></tr>';
html += '</tbody></table>';
html += '</div>';
// Optional ports table
html += '<div class="onboarding-port-section" style="margin-bottom:20px;">';
html += '<div class="onboarding-port-section-title" style="font-weight:700;margin-bottom:4px;">Optional — Only needed if you enable Element Calling:</div>';
html += '<div style="font-size:0.88em;margin-bottom:8px;color:var(--color-text-muted,#888);">These 5 additional port openings are required on top of the 4 required ports above.</div>';
html += '<table class="onboarding-port-table">';
html += '<thead><tr><th>Port</th><th>Protocol</th><th>Forward&nbsp;to</th><th>Purpose</th></tr></thead>';
html += '<tbody>';
html += '<tr><td class="port-req-port">7881</td><td class="port-req-proto">TCP</td><td class="port-req-internal-ip">' + ip + '</td><td class="port-req-desc">LiveKit WebRTC signalling</td></tr>';
html += '<tr><td class="port-req-port">78827894</td><td class="port-req-proto">UDP</td><td class="port-req-internal-ip">' + ip + '</td><td class="port-req-desc">LiveKit media streams</td></tr>';
html += '<tr><td class="port-req-port">5349</td><td class="port-req-proto">TCP</td><td class="port-req-internal-ip">' + ip + '</td><td class="port-req-desc">TURN over TLS</td></tr>';
html += '<tr><td class="port-req-port">3478</td><td class="port-req-proto">UDP</td><td class="port-req-internal-ip">' + ip + '</td><td class="port-req-desc">TURN (STUN/relay)</td></tr>';
html += '<tr><td class="port-req-port">3000040000</td><td class="port-req-proto">TCP/UDP</td><td class="port-req-internal-ip">' + ip + '</td><td class="port-req-desc">TURN relay (WebRTC)</td></tr>';
html += '</tbody></table>';
html += '</div>';
// Totals
html += '<div class="onboarding-port-totals" style="margin-bottom:18px;padding:10px 14px;background:var(--color-bg-subtle,#f6f8fa);border-radius:6px;font-size:0.93em;">';
html += '<strong>Total port openings: 4</strong> (without Element Calling)<br>';
html += '<strong>Total port openings: 9</strong> (with Element Calling — 4 required + 5 optional)';
html += '</div>';
html += '<div class="onboarding-port-warn" style="margin-bottom:16px;">'
+ '⚠ <strong>Ports 80 and 443 must be forwarded first.</strong> '
+ 'Caddy uses these to obtain SSL certificates from Let\'s Encrypt. '
+ 'If they are closed, HTTPS will not work and your services will be unreachable from outside your network.'
+ '</div>';
html += '<details class="onboarding-port-details" style="margin-bottom:16px;">'
+ '<summary class="onboarding-port-details-summary">How to set up port forwarding</summary>'
+ '<ol style="margin:12px 0 0 16px; padding:0; line-height:1.8;">'
+ '<li>Open your router\'s admin panel — usually <code>http://192.168.1.1</code> or <code>http://192.168.0.1</code></li>'
+ '<li>Look for <strong>"Port Forwarding"</strong>, <strong>"NAT"</strong>, or <strong>"Virtual Server"</strong> in the settings</li>'
+ '<li>Create a new rule for each port listed below</li>'
+ '<li>Set the destination/internal IP to <strong>' + escHtml(internalIp) + '</strong></li>'
+ '<li>Create a new rule for each port listed above</li>'
+ '<li>Set the destination/internal IP to <strong>' + ip + '</strong></li>'
+ '<li>Set both internal and external port to the same number</li>'
+ '<li>Save and apply changes</li>'
+ '</ol>'
+ '</details>';
var status = (portHealth && portHealth.status) || "ok";
var totalPorts = (portHealth && portHealth.total_ports) || 0;
var closedPorts = (portHealth && portHealth.closed_ports) || 0;
if (totalPorts === 0) {
html += '<p class="onboarding-body-text">No port requirements detected for your current role.</p>';
} else if (status === "ok") {
html += '<p class="onboarding-port-all-ok">✅ All ' + totalPorts + ' required ports are open and ready.</p>';
} else {
html += '<div class="onboarding-port-warn">';
html += '⚠ ' + closedPorts + ' of ' + totalPorts + ' ports appear closed. ';
html += 'You can continue, but affected services may not work until ports are forwarded.';
html += '</div>';
}
// Show per-service breakdown
var affectedSvcs = (portHealth && portHealth.affected_services) || [];
if (affectedSvcs.length > 0) {
html += '<div class="onboarding-port-breakdown">';
html += '<div class="onboarding-port-breakdown-title">Affected Services</div>';
affectedSvcs.forEach(function(svc) {
html += '<div class="onboarding-port-svc">';
html += '<div class="onboarding-port-svc-name">' + escHtml(svc.name) + '</div>';
(svc.closed_ports || []).forEach(function(p) {
html += '<div class="onboarding-port-row">';
html += ' <span class="port-status-closed">🔴</span>';
html += ' <span class="port-req-port">' + escHtml(p.port) + '/' + escHtml(p.protocol) + '</span>';
if (p.description) html += ' <span class="port-req-desc">' + escHtml(p.description) + '</span>';
html += '</div>';
});
html += '</div>';
});
html += '</div>';
}
// Full port table from services
if (_servicesData) {
// Collect all unique port requirements
var allPorts = [];
var seen = new Set();
(_servicesData || []).forEach(function(svc) {
(svc.port_requirements || []).forEach(function(p) {
var key = p.port + "/" + p.protocol;
if (!seen.has(key)) {
seen.add(key);
allPorts.push(p);
}
});
});
if (allPorts.length > 0) {
html += '<details class="onboarding-port-details">';
html += '<summary class="onboarding-port-details-summary">View All Required Ports</summary>';
html += '<table class="onboarding-port-table">';
html += '<thead><tr><th>Port</th><th>Protocol</th><th>Purpose</th></tr></thead>';
html += '<tbody>';
allPorts.forEach(function(p) {
html += '<tr>';
html += '<td class="port-req-port">' + escHtml(p.port) + '</td>';
html += '<td class="port-req-proto">' + escHtml(p.protocol) + '</td>';
html += '<td class="port-req-desc">' + escHtml(p.description || "") + '</td>';
html += '</tr>';
});
html += '</tbody></table>';
html += '</details>';
}
}
body.innerHTML = html;
}