Add central firewall and tunnel status screen
This commit is contained in:
@@ -190,6 +190,11 @@ const router = createRouter({
|
|||||||
name: 'openwrt-gateway',
|
name: 'openwrt-gateway',
|
||||||
component: () => import('../views/server/OpenWrtGateway.vue'),
|
component: () => import('../views/server/OpenWrtGateway.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'server/firewall-tunnels',
|
||||||
|
name: 'firewall-tunnels',
|
||||||
|
component: () => import('../views/server/FirewallTunnelSettings.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'monitoring',
|
path: 'monitoring',
|
||||||
name: 'monitoring',
|
name: 'monitoring',
|
||||||
|
|||||||
@@ -94,13 +94,18 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Refreshing network...
|
Refreshing network...
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
<router-link
|
||||||
|
to="/dashboard/server/firewall-tunnels"
|
||||||
|
class="flex items-center justify-between p-3 bg-white/5 rounded-lg hover:bg-white/10 transition-colors text-left"
|
||||||
|
aria-label="Open firewall and tunnel settings"
|
||||||
|
>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<svg class="w-5 h-5 text-white/60" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg>
|
<svg class="w-5 h-5 text-white/60" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg>
|
||||||
<span class="text-white/80 text-sm">Firewall Active</span>
|
<span class="text-white/80 text-sm">Firewall Active</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-green-400 text-sm font-medium">Protected</span>
|
<span class="text-green-400 text-sm font-medium">Protected</span>
|
||||||
</div>
|
<svg class="w-4 h-4 text-white/30" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /></svg>
|
||||||
|
</router-link>
|
||||||
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<svg class="w-5 h-5 text-white/60" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0" /></svg>
|
<svg class="w-5 h-5 text-white/60" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0" /></svg>
|
||||||
@@ -159,6 +164,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
<router-link
|
||||||
|
to="/dashboard/server/firewall-tunnels"
|
||||||
|
class="mt-4 w-full flex items-center justify-center gap-2 p-3 rounded-lg bg-white/10 hover:bg-white/15 transition-colors text-sm text-white/90"
|
||||||
|
>
|
||||||
|
Firewall & tunnels
|
||||||
|
<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="M9 5l7 7-7 7" /></svg>
|
||||||
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FipsNetworkCard />
|
<FipsNetworkCard />
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
import { RouterLink } from 'vue-router'
|
||||||
|
import { rpcClient } from '@/api/rpc-client'
|
||||||
|
|
||||||
|
interface TunnelStatus {
|
||||||
|
connected?: boolean
|
||||||
|
provider?: string
|
||||||
|
ip_address?: string
|
||||||
|
wg_ip?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FipsStatus {
|
||||||
|
installed?: boolean
|
||||||
|
service_active?: boolean
|
||||||
|
anchor_connected?: boolean
|
||||||
|
authenticated_peer_count?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Forward { [key: string]: unknown }
|
||||||
|
|
||||||
|
const loading = ref(true)
|
||||||
|
const error = ref('')
|
||||||
|
const tunnel = ref<TunnelStatus | null>(null)
|
||||||
|
const fips = ref<FipsStatus | null>(null)
|
||||||
|
const forwards = ref<Forward[]>([])
|
||||||
|
|
||||||
|
const tunnelLabel = computed(() => {
|
||||||
|
if (!tunnel.value) return 'Unknown'
|
||||||
|
return tunnel.value.connected ? 'Connected' : 'Not connected'
|
||||||
|
})
|
||||||
|
const firewallLabel = computed(() => {
|
||||||
|
if (!fips.value) return 'Unknown'
|
||||||
|
if (fips.value.service_active === false) return 'Inactive'
|
||||||
|
return 'Protected'
|
||||||
|
})
|
||||||
|
|
||||||
|
async function refresh() {
|
||||||
|
loading.value = true
|
||||||
|
error.value = ''
|
||||||
|
const results = await Promise.allSettled([
|
||||||
|
rpcClient.vpnStatus(),
|
||||||
|
rpcClient.call<FipsStatus>({ method: 'fips.status', dedup: true, maxRetries: 1 }),
|
||||||
|
rpcClient.call<{ forwards?: Forward[] }>({ method: 'router.list-forwards', dedup: true, maxRetries: 1 }),
|
||||||
|
])
|
||||||
|
const failures = results.filter((result) => result.status === 'rejected')
|
||||||
|
if (results[0]?.status === 'fulfilled') tunnel.value = results[0].value
|
||||||
|
if (results[1]?.status === 'fulfilled') fips.value = results[1].value
|
||||||
|
if (results[2]?.status === 'fulfilled') forwards.value = results[2].value.forwards ?? []
|
||||||
|
if (failures.length === results.length) error.value = 'Could not load network protection status'
|
||||||
|
else if (failures.length) error.value = 'Some checks are unavailable; verify each route before changing it'
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(refresh)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<main class="max-w-5xl mx-auto px-4 sm:px-6 py-6">
|
||||||
|
<div class="flex items-start justify-between gap-4 mb-6">
|
||||||
|
<div>
|
||||||
|
<RouterLink to="/dashboard/server" class="text-xs text-white/50 hover:text-white">← Network</RouterLink>
|
||||||
|
<h1 class="text-2xl font-semibold text-white mt-2">Firewall & tunnels</h1>
|
||||||
|
<p class="text-sm text-white/60 mt-1">Review reachability before exposing a service. Transport access and app authentication are separate controls.</p>
|
||||||
|
</div>
|
||||||
|
<button class="glass-button px-3 py-2 text-sm shrink-0" :disabled="loading" @click="refresh">
|
||||||
|
<span v-if="loading" class="inline-flex items-center gap-2"><span class="h-3.5 w-3.5 rounded-full border-2 border-white/30 border-t-white animate-spin" /> Checking…</span>
|
||||||
|
<span v-else>Refresh checks</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="error" class="mb-4 rounded-lg border border-orange-400/30 bg-orange-500/10 px-4 py-3 text-sm text-orange-200">{{ error }}</div>
|
||||||
|
|
||||||
|
<section class="grid gap-4 md:grid-cols-2 mb-6">
|
||||||
|
<div class="glass-card p-5">
|
||||||
|
<h2 class="text-lg font-medium text-white">Firewall</h2>
|
||||||
|
<p class="text-sm text-white/60 mt-1">Node protection status and mesh reachability.</p>
|
||||||
|
<div class="mt-4 flex items-center justify-between"><span class="text-white/70">Node firewall</span><span :class="firewallLabel === 'Protected' ? 'text-green-300' : 'text-orange-300'">{{ firewallLabel }}</span></div>
|
||||||
|
<div class="mt-2 flex items-center justify-between"><span class="text-white/70">Authenticated mesh peers</span><span class="text-white">{{ fips?.authenticated_peer_count ?? 'Unknown' }}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="glass-card p-5">
|
||||||
|
<h2 class="text-lg font-medium text-white">Tunnel</h2>
|
||||||
|
<p class="text-sm text-white/60 mt-1">Connection and handshake status.</p>
|
||||||
|
<div class="mt-4 flex items-center justify-between"><span class="text-white/70">Tunnel</span><span :class="tunnel?.connected ? 'text-green-300' : 'text-orange-300'">{{ tunnelLabel }}</span></div>
|
||||||
|
<div class="mt-2 flex items-center justify-between"><span class="text-white/70">Address</span><span class="text-white font-mono text-xs">{{ tunnel?.wg_ip || tunnel?.ip_address || 'Unknown' }}</span></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="glass-card p-5 mb-6">
|
||||||
|
<div class="flex items-start justify-between gap-3"><div><h2 class="text-lg font-medium text-white">Public access checks</h2><p class="text-sm text-white/60 mt-1">Existing router forwards are shown for review. No route is changed from this screen yet.</p></div><span class="text-sm text-white/60">{{ forwards.length }} forward{{ forwards.length === 1 ? '' : 's' }}</span></div>
|
||||||
|
<div class="mt-4 grid gap-2 sm:grid-cols-2">
|
||||||
|
<div v-for="(forward, index) in forwards" :key="index" class="rounded-lg bg-white/5 p-3 text-xs text-white/70 font-mono break-all">{{ JSON.stringify(forward) }}</div>
|
||||||
|
<div v-if="!forwards.length" class="text-sm text-white/45">No router forwards reported.</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-white/45 mt-4">Public forwarding, node firewall, service listener and external reachability are independent checks. A local pass does not claim public access.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="glass-card p-5">
|
||||||
|
<h2 class="text-lg font-medium text-white">App exposure</h2>
|
||||||
|
<p class="text-sm text-white/60 mt-1">App manifests and owned exposure rules will be added here after validation and rollback support are qualified.</p>
|
||||||
|
<div class="mt-4 rounded-lg border border-white/10 bg-white/5 p-3 text-sm text-white/60">No changes are available yet. Existing app access remains untouched.</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user