fix: WireGuard routing, DNS, service access, and UI improvements

- Fix CoreDNS not loading .cell zones (wrong Corefile path, now uses -conf flag)
- Fix WireGuard server address conflict (172.20.0.1/16 overlapped with Docker
  network; changed to 10.0.0.1/24 to eliminate duplicate routes)
- Add SERVERMODE=true and sysctls to WireGuard docker-compose for server mode
- Fix DNS zone file parser to handle 4-field records (name IN type value)
- Add get_dns_records() to NetworkManager; mount data/dns into API container
- Fix peer config endpoint: look up IP/key from registry, use real endpoint
- Add bulk peer statuses endpoint keyed by public_key
- Normalize snake_case API fields to camelCase in WireGuard UI
- Add port check endpoint (checks via live handshake, not unreliable TCP probe)
- Add Caddy virtual hosts for ui/calendar/files/mail .cell domains (HTTP only)
- Fix cell config domain default from cell.local to cell
- Fix Routing Network Config tab (was calling hardcoded localhost:3000)
- Fix DNS records display (record.value not record.ip)
- Move service access guide to top of Dashboard with login hints
- Add /api/routing/setup endpoint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-20 12:43:23 -04:00
parent bd67764bf4
commit e79ee08c63
14 changed files with 422 additions and 306 deletions
+33 -109
View File
@@ -95,7 +95,7 @@ function Routing() {
setNetworkLoading(true);
setNetworkError(null);
try {
const response = await fetch('http://localhost:3000/api/wireguard/network/status');
const response = await fetch('/api/routing/status');
if (response.ok) {
const data = await response.json();
setNetworkStatus(data);
@@ -114,11 +114,9 @@ function Routing() {
setIsSettingUp(true);
setNetworkError(null);
try {
const response = await fetch('http://localhost:3000/api/wireguard/network/setup', {
const response = await fetch('/api/routing/setup', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
headers: { 'Content-Type': 'application/json' },
});
if (response.ok) {
@@ -404,125 +402,51 @@ function Routing() {
</button>
</div>
{networkError && (
<div className="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg">
<p className="text-red-800">{networkError}</p>
</div>
)}
{networkLoading ? (
<div className="flex justify-center items-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
</div>
) : networkStatus ? (
<div className="space-y-6">
{/* Network Status Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="bg-white p-4 rounded-lg border border-gray-200">
<div className="flex items-center">
<div className={`w-3 h-3 rounded-full mr-3 ${networkStatus.ip_forwarding ? 'bg-green-400' : 'bg-red-400'}`}></div>
<div>
<p className="text-sm font-medium text-gray-900">IP Forwarding</p>
<p className="text-xs text-gray-500">{networkStatus.ip_forwarding ? 'Enabled' : 'Disabled'}</p>
</div>
<div className="space-y-4">
{/* Status cards */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
{[
{ label: 'Routing', value: networkStatus.status === 'online' ? 'Online' : 'Offline', ok: networkStatus.running },
{ label: 'NAT Rules', value: networkStatus.nat_rules_count ?? 0, ok: true },
{ label: 'Firewall Rules', value: networkStatus.firewall_rules_count ?? 0, ok: true },
{ label: 'Peer Routes', value: networkStatus.peer_routes_count ?? 0, ok: true },
].map(item => (
<div key={item.label} className="bg-white p-4 rounded-lg border border-gray-200">
<p className="text-xs text-gray-500">{item.label}</p>
<p className={`text-lg font-semibold mt-1 ${item.ok ? 'text-gray-900' : 'text-red-600'}`}>{item.value}</p>
</div>
</div>
<div className="bg-white p-4 rounded-lg border border-gray-200">
<div className="flex items-center">
<div className={`w-3 h-3 rounded-full mr-3 ${networkStatus.interface_status ? 'bg-green-400' : 'bg-red-400'}`}></div>
<div>
<p className="text-sm font-medium text-gray-900">WireGuard Interface</p>
<p className="text-xs text-gray-500">{networkStatus.interface_status ? 'Up' : 'Down'}</p>
</div>
</div>
</div>
<div className="bg-white p-4 rounded-lg border border-gray-200">
<div className="flex items-center">
<div className={`w-3 h-3 rounded-full mr-3 ${networkStatus.nat_rules ? 'bg-green-400' : 'bg-red-400'}`}></div>
<div>
<p className="text-sm font-medium text-gray-900">NAT Rules</p>
<p className="text-xs text-gray-500">{networkStatus.nat_rules ? 'Configured' : 'Missing'}</p>
</div>
</div>
</div>
<div className="bg-white p-4 rounded-lg border border-gray-200">
<div className="flex items-center">
<div className={`w-3 h-3 rounded-full mr-3 ${networkStatus.forwarding_rules ? 'bg-green-400' : 'bg-red-400'}`}></div>
<div>
<p className="text-sm font-medium text-gray-900">Forwarding Rules</p>
<p className="text-xs text-gray-500">{networkStatus.forwarding_rules ? 'Configured' : 'Missing'}</p>
</div>
</div>
</div>
))}
</div>
{/* Configuration Details */}
<div className="bg-gray-50 p-4 rounded-lg">
<h4 className="text-md font-medium text-gray-900 mb-3">Configuration Details</h4>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-600">Last Updated:</span>
<span className="text-gray-900">{new Date(networkStatus.timestamp).toLocaleString()}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">IP Forwarding:</span>
<span className={`font-medium ${networkStatus.ip_forwarding ? 'text-green-600' : 'text-red-600'}`}>
{networkStatus.ip_forwarding ? 'Enabled' : 'Disabled'}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">WireGuard Interface:</span>
<span className={`font-medium ${networkStatus.interface_status ? 'text-green-600' : 'text-red-600'}`}>
{networkStatus.interface_status ? 'Up (wg0)' : 'Down'}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">NAT Translation:</span>
<span className={`font-medium ${networkStatus.nat_rules ? 'text-green-600' : 'text-red-600'}`}>
{networkStatus.nat_rules ? 'Active' : 'Not Configured'}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Traffic Forwarding:</span>
<span className={`font-medium ${networkStatus.forwarding_rules ? 'text-green-600' : 'text-red-600'}`}>
{networkStatus.forwarding_rules ? 'Allowed' : 'Blocked'}
</span>
{/* Routing table */}
{networkStatus.routing_status?.routing_table?.length > 0 && (
<div className="bg-gray-50 rounded-lg p-4">
<h4 className="text-sm font-medium text-gray-700 mb-2">Active Routes</h4>
<div className="space-y-1 font-mono text-xs text-gray-600">
{networkStatus.routing_status.routing_table.map((r, i) => (
<div key={i} className="flex gap-4">
<span className="text-gray-900 w-40 truncate">{r.parsed?.destination || r.route}</span>
<span className="text-gray-500">via {r.parsed?.dev || '—'}</span>
{r.parsed?.via && <span className="text-gray-400">{r.parsed.via}</span>}
</div>
))}
</div>
</div>
</div>
)}
{/* Quick Actions */}
<div className="bg-blue-50 p-4 rounded-lg">
<h4 className="text-md font-medium text-blue-900 mb-2">Quick Actions</h4>
<div className="flex flex-wrap gap-2">
<button
className="btn btn-sm btn-outline"
onClick={fetchNetworkStatus}
>
Refresh Status
</button>
<button
className="btn btn-sm btn-primary"
onClick={setupNetworkConfiguration}
disabled={isSettingUp}
>
{isSettingUp ? 'Setting up...' : 'Setup Network'}
</button>
</div>
<div className="flex gap-2">
<button className="btn btn-secondary text-sm" onClick={fetchNetworkStatus}>Refresh</button>
</div>
</div>
) : (
<div className="text-center py-8">
<p className="text-gray-500">Failed to load network status</p>
<button
className="btn btn-primary mt-2"
onClick={fetchNetworkStatus}
>
Retry
</button>
<p className="text-gray-500">Could not load network status</p>
<button className="btn btn-primary mt-2" onClick={fetchNetworkStatus}>Retry</button>
</div>
)}
</div>