refactor: remove duplicate network diagnostics from Settings, add link to Server page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian 2026-03-11 00:01:15 +00:00
parent 76269a50fa
commit 38b29dd2fd
2 changed files with 11 additions and 173 deletions

View File

@ -40,7 +40,7 @@
- [x] **UI-02** — Fix Web5.vue top bar: use proper glass sub-card pattern. In `neode-ui/src/views/Web5.vue` lines 10-119, the 5 quick-action cards inside the `.glass-card` container use `bg-white/5 rounded-lg`. This is the correct pattern for info sub-cards inside a glass container per CLAUDE.md CSS hierarchy (`bg-white/5` = "Simple read-only info rows"). However, verify alignment with the Server.vue quick-actions bar (lines 10-96) which uses the identical pattern. Confirm both pages are visually consistent. If Web5 cards lack `data-controller-container` and `tabindex="0"` attributes, add them for keyboard/gamepad navigation parity. **Acceptance**: Web5 and Server quick-action bars visually match. No animation changes. Deploy and verify.
- [ ] **UI-03** — Remove duplicate network diagnostics from Settings.vue. Settings.vue contains a "Network Diagnostics" section that duplicates functionality available on the Server.vue (Network) page. Remove the entire Network Diagnostics section from Settings.vue. Add a small link/button in Settings that says "Network Diagnostics" and routes to `/dashboard/server` instead. Keep the "Network Diagnostics" section only in Server.vue. **Acceptance**: Settings no longer shows duplicate network info; link navigates to Server page. Deploy and verify.
- [x] **UI-03** — Remove duplicate network diagnostics from Settings.vue. Settings.vue contains a "Network Diagnostics" section that duplicates functionality available on the Server.vue (Network) page. Remove the entire Network Diagnostics section from Settings.vue. Add a small link/button in Settings that says "Network Diagnostics" and routes to `/dashboard/server` instead. Keep the "Network Diagnostics" section only in Server.vue. **Acceptance**: Settings no longer shows duplicate network info; link navigates to Server page. Deploy and verify.
- [ ] **UI-04** — Server.vue: wire real RPC data to Local Network card. The Local Network card in `neode-ui/src/views/Server.vue` lines 100-159 shows hardcoded values ("2 configured", "12 active", "5 rules"). Replace with data from RPC calls: `network.diagnostics` for connectivity info and `router.list-forwards` for port forwarding count. Add `onMounted` lifecycle hook to fetch data. Show skeleton loading states while fetching. **Acceptance**: Network card shows real data from backend (or graceful "N/A" if RPC unavailable). Deploy and verify.

View File

@ -542,111 +542,21 @@
</div>
</div>
<!-- Network & Connectivity Section -->
<!-- Network Diagnostics Link -->
<div class="glass-card px-6 py-6 mb-6">
<div class="mb-4">
<h2 class="text-xl font-semibold text-white/96">Network</h2>
<p class="text-sm text-white/60 mt-1">Network connectivity, UPnP, and diagnostics</p>
</div>
<!-- Loading -->
<div v-if="networkLoading" class="py-4 text-center">
<p class="text-white/50 text-sm">Running diagnostics...</p>
</div>
<template v-else-if="networkDiag">
<!-- Status Grid -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 mb-4">
<div class="bg-white/5 rounded-lg p-3">
<div class="text-xs text-white/50 mb-1">WAN IP</div>
<span class="text-sm text-white font-mono">{{ networkDiag.wan_ip || 'Unknown' }}</span>
</div>
<div class="bg-white/5 rounded-lg p-3">
<div class="text-xs text-white/50 mb-1">NAT Type</div>
<span class="text-sm text-white">{{ networkDiag.nat_type }}</span>
</div>
<div class="bg-white/5 rounded-lg p-3">
<div class="text-xs text-white/50 mb-1">UPnP</div>
<div class="flex items-center gap-2">
<div class="w-2 h-2 rounded-full" :class="networkDiag.upnp_available ? 'bg-green-400' : 'bg-red-400'"></div>
<span class="text-sm text-white">{{ networkDiag.upnp_available ? 'Available' : 'Unavailable' }}</span>
</div>
</div>
<div class="bg-white/5 rounded-lg p-3">
<div class="text-xs text-white/50 mb-1">Tor</div>
<div class="flex items-center gap-2">
<div class="w-2 h-2 rounded-full" :class="networkDiag.tor_connected ? 'bg-green-400' : 'bg-red-400'"></div>
<span class="text-sm text-white">{{ networkDiag.tor_connected ? 'Connected' : 'Offline' }}</span>
</div>
</div>
<div class="flex items-center justify-between">
<div>
<h2 class="text-xl font-semibold text-white/96">Network</h2>
<p class="text-sm text-white/60 mt-1">Network connectivity, UPnP, and diagnostics</p>
</div>
<!-- Recommendations -->
<div v-if="networkDiag.recommendations?.length" class="mb-4">
<div class="text-xs text-white/50 mb-2">Recommendations</div>
<div class="space-y-1">
<div v-for="(rec, idx) in networkDiag.recommendations" :key="idx" class="flex items-start gap-2 text-xs text-yellow-400/80 bg-yellow-500/10 rounded-lg px-3 py-2">
<svg class="w-3 h-3 mt-0.5 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>
<span>{{ rec }}</span>
</div>
</div>
</div>
<!-- Port Forwards -->
<div class="border-t border-white/10 pt-4">
<div class="flex items-center justify-between mb-3">
<h3 class="text-sm font-semibold text-white">Port Forwards</h3>
<button @click="showAddForwardModal = true" class="glass-button glass-button-sm px-3 rounded-lg text-xs">Add</button>
</div>
<div v-if="portForwards.length" class="space-y-2">
<div v-for="fwd in portForwards" :key="fwd.id" class="flex items-center justify-between p-2 bg-white/5 rounded-lg">
<div class="text-sm text-white">
<span class="font-medium">{{ fwd.service_name }}</span>
<span class="text-white/40 text-xs ml-2">:{{ fwd.internal_port }} :{{ fwd.external_port }} ({{ fwd.protocol }})</span>
</div>
<button @click="removePortForward(fwd.id)" class="text-white/30 hover:text-red-400 transition-colors p-1">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>
</button>
</div>
</div>
<div v-else class="text-center text-white/40 text-xs py-3">No port forwards configured</div>
</div>
</template>
<div class="flex justify-end pt-3">
<button @click="runNetworkDiag" :disabled="networkLoading" class="glass-button glass-button-sm px-4 rounded-lg text-sm disabled:opacity-50">
{{ networkLoading ? 'Running...' : 'Run Diagnostics' }}
<button @click="router.push('/dashboard/server')" class="glass-button px-4 py-2 rounded-lg text-sm flex items-center gap-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6" />
</svg>
Network Diagnostics
</button>
</div>
</div>
<!-- Add Port Forward Modal -->
<div v-if="showAddForwardModal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm" @click.self="showAddForwardModal = false">
<div class="glass-card p-6 w-full max-w-sm mx-4">
<h2 class="text-lg font-bold text-white mb-4">Add Port Forward</h2>
<div class="space-y-3">
<div>
<label class="text-white/60 text-xs block mb-1">Service Name</label>
<input v-model="newFwdService" type="text" placeholder="Bitcoin RPC" class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-white/30" />
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="text-white/60 text-xs block mb-1">Internal Port</label>
<input v-model.number="newFwdInternal" type="number" placeholder="8332" class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-white/30" />
</div>
<div>
<label class="text-white/60 text-xs block mb-1">External Port</label>
<input v-model.number="newFwdExternal" type="number" placeholder="8332" class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-white text-sm focus:outline-none focus:border-white/30" />
</div>
</div>
</div>
<div v-if="fwdError" class="text-xs text-red-400 mt-2">{{ fwdError }}</div>
<div class="flex gap-3 mt-4">
<button @click="showAddForwardModal = false" class="flex-1 glass-button px-4 py-2 rounded-lg text-sm">Cancel</button>
<button @click="addPortForward" :disabled="!newFwdService.trim() || !newFwdInternal || !newFwdExternal" class="flex-1 glass-button px-4 py-2 rounded-lg text-sm font-medium bg-blue-500/20 border-blue-500/30 disabled:opacity-50">Add</button>
</div>
</div>
</div>
</div>
</template>
@ -956,81 +866,9 @@ function closeChangePasswordModal() {
changePasswordForm.value = { currentPassword: '', newPassword: '', confirmPassword: '', alsoChangeSsh: true }
}
// --- Network & Connectivity ---
interface NetworkDiagData {
wan_ip: string | null
nat_type: string
upnp_available: boolean
tor_connected: boolean
dns_working: boolean
recommendations: string[]
}
interface PortForwardData {
id: string
service_name: string
internal_port: number
external_port: number
protocol: string
enabled: boolean
}
const networkDiag = ref<NetworkDiagData | null>(null)
const networkLoading = ref(false)
const portForwards = ref<PortForwardData[]>([])
const showAddForwardModal = ref(false)
const newFwdService = ref('')
const newFwdInternal = ref(0)
const newFwdExternal = ref(0)
const fwdError = ref('')
async function runNetworkDiag() {
networkLoading.value = true
try {
const [diagRes, fwdRes] = await Promise.all([
rpcClient.call<NetworkDiagData>({ method: 'network.diagnostics' }),
rpcClient.call<{ forwards: PortForwardData[] }>({ method: 'router.list-forwards' }),
])
networkDiag.value = diagRes
portForwards.value = fwdRes.forwards || []
} catch {
networkDiag.value = null
} finally {
networkLoading.value = false
}
}
async function addPortForward() {
if (!newFwdService.value.trim() || !newFwdInternal.value || !newFwdExternal.value) return
fwdError.value = ''
try {
await rpcClient.call({ method: 'router.add-forward', params: {
service_name: newFwdService.value.trim(),
internal_port: newFwdInternal.value,
external_port: newFwdExternal.value,
protocol: 'TCP',
}})
showAddForwardModal.value = false
newFwdService.value = ''
newFwdInternal.value = 0
newFwdExternal.value = 0
await runNetworkDiag()
} catch (e: unknown) {
fwdError.value = e instanceof Error ? e.message : 'Failed to add forward'
}
}
async function removePortForward(id: string) {
try {
await rpcClient.call({ method: 'router.remove-forward', params: { id } })
await runNetworkDiag()
} catch {
// Silent
}
}
onMounted(async () => {
checkClaudeStatus()
loadTotpStatus()
runNetworkDiag()
if (!serverTorAddressFromStore.value) {
try {
const res = await rpcClient.getTorAddress()