Port-forward UX: drop tile Step 4 and misleading local 'ready' status
The Element Call tile still appended a synthetic 'Step 4: Router Setup
Needed' to the domain diagnostic checklist, and every port table carried a
'Sovran_SystemsOS Status' column with Ready / Not ready yet verdicts.
Both were misleading: port forwarding happens on the router, which this
computer cannot inspect. A local ss/firewall probe can neither prove nor
disprove that forwarding works — and the LiveKit TURN relay range binds on
demand, so it reported 'Not ready yet' even on a perfectly working system.
- server.py: add ROUTER_FORWARD_ONLY_UNITS ({livekit.service}); skip the
local probe for those units, drop the step-4 append, replace extra_ports
with router_ports (no status field), and exclude router-only ports from
both tile health and /api/ports/health so they can't raise false alarms.
- helpers.js: new shared renderPortForwardGuideHtml() — one intro naming the
internal IP, explicit instructions (same internal/external port, match the
protocol, use port-range fields for 30000-40000), a colour-coded
TCP / UDP / TCP+UDP badge per row, and a closing note that the only real
test is loading the service from a phone on mobile data.
- features.js: enable-time modal uses the shared guide and now always lists
every port to forward (the old local pre-filter hid ports the user still
had to open).
- service-detail.js: tile port section uses the same guide; the SSH/non-domain
branch keeps a small 'not open on this computer yet' hint, which is a real
local fact, separate from router forwarding.
- onboarding.js: step 3 router note reworded to match (same number for
internal/external, notes that Element Call adds UDP ports).
- domain-setup.css: styles for the protocol badges and instruction list.
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
This commit is contained in:
co-authored by
arena-agent
parent
0e3cab78f8
commit
419680a847
@@ -279,24 +279,12 @@ function openPortRequirementsModal(featureName, ports, onContinue) {
|
||||
: '';
|
||||
|
||||
function renderPortRequirements(internalIp) {
|
||||
var rows = ports.map(function(p) {
|
||||
return '<tr><td class="port-req-port">' + escHtml(p.port) + '</td>' +
|
||||
'<td class="port-req-proto">' + escHtml(p.protocol) + '</td>' +
|
||||
'<td class="port-req-desc">' + escHtml(p.description) + '</td></tr>';
|
||||
}).join("");
|
||||
var ipPart = internalIp
|
||||
? ' to this computer’s internal IP <code class="port-req-internal-ip">' + escHtml(internalIp) + '</code>'
|
||||
: " to this computer's internal IP";
|
||||
|
||||
$portReqBody.innerHTML =
|
||||
'<p class="port-req-intro">For <strong>' + escHtml(featureName) + '</strong> to work for people outside your home network, ' +
|
||||
'forward each port below' + ipPart + " in your router's port-forwarding settings. " +
|
||||
'Set the internal and external port to the same number.</p>' +
|
||||
'<table class="port-req-table">' +
|
||||
'<thead><tr><th>Port(s)</th><th>Protocol</th><th>Purpose</th></tr></thead>' +
|
||||
'<tbody>' + rows + '</tbody>' +
|
||||
'</table>' +
|
||||
'<p class="port-req-hint">💡 You can review these ports with live status any time on the <strong>' + escHtml(featureName) + '</strong> tile after enabling.</p>' +
|
||||
renderPortForwardGuideHtml(ports, {
|
||||
internalIp: internalIp,
|
||||
serviceName: featureName,
|
||||
}) +
|
||||
'<p class="port-req-hint">💡 This list is always available again on the <strong>' + escHtml(featureName) + '</strong> tile.</p>' +
|
||||
'<div class="domain-field-actions">' +
|
||||
'<button class="btn btn-close-modal" id="port-req-dismiss-btn">Dismiss</button>' +
|
||||
continueBtn +
|
||||
@@ -429,39 +417,10 @@ function handleFeatureToggle(feat, newEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check which ports are actually closed before showing the modal
|
||||
fetch("/api/ports/status", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ ports: ports }),
|
||||
})
|
||||
.then(function(r) {
|
||||
if (!r.ok) throw new Error("Port status request failed: " + r.status);
|
||||
return r.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
var portStatuses = {};
|
||||
(data.ports || []).forEach(function(p) {
|
||||
portStatuses[p.port + "/" + p.protocol] = p.status;
|
||||
});
|
||||
|
||||
var closedPorts = ports.filter(function(p) {
|
||||
var key = p.port + "/" + p.protocol;
|
||||
var status = portStatuses[key] || "unknown";
|
||||
return status !== "listening" && status !== "firewall_open";
|
||||
});
|
||||
|
||||
if (closedPorts.length === 0) {
|
||||
proceedAfterPortCheck();
|
||||
} else {
|
||||
openPortRequirementsModal(feat.name, closedPorts, proceedAfterPortCheck);
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.warn("Failed to fetch port status for feature enable flow:", err);
|
||||
// Safe fallback if status check fails
|
||||
openPortRequirementsModal(feat.name, ports, proceedAfterPortCheck);
|
||||
});
|
||||
// Always show the full list of ports to forward. Port forwarding happens on
|
||||
// the router, which this computer cannot inspect — a local check would only
|
||||
// hide ports the user still has to open.
|
||||
openPortRequirementsModal(feat.name, ports, proceedAfterPortCheck);
|
||||
}
|
||||
|
||||
if (feat.id === "bitcoin-core") {
|
||||
|
||||
Reference in New Issue
Block a user