feat(home): surface TollGate status on the Network tile
Add a TollGate row (Enabled/Disabled/Not installed) to the Home dashboard's Network tile, polling the existing openwrt.get-status RPC on the same cadence as the other network rows. Only rendered once an OpenWrt router is actually configured, so nodes without one aren't cluttered with an always-"Not configured" row. Also fixes the underlying reason this could never have worked: nothing in the OpenWrt Gateway flow ever persisted the router's host/credentials server-side — the "connect" form only kept them in local component state, so any no-args openwrt.get-status call (this new tile, and even the Gateway page's own reload) always failed with "No router configured" despite a fully working, provisioned router. Now handle_openwrt_get_status saves the connection to router_config.json whenever a host is explicitly passed in and the connection succeeds.
This commit is contained in:
parent
d6c1feca97
commit
e497f8fed1
@ -53,6 +53,7 @@ impl RpcHandler {
|
||||
) -> Result<serde_json::Value> {
|
||||
let saved = net_router::load_router_config(&self.config.data_dir).await?;
|
||||
let p = params.unwrap_or_default();
|
||||
let host_from_params = p.get("host").and_then(|v| v.as_str()).is_some();
|
||||
|
||||
let host = p
|
||||
.get("host")
|
||||
@ -78,6 +79,22 @@ impl RpcHandler {
|
||||
let router = Router::connect_password(&host, 22, &ssh_user, &ssh_password)?;
|
||||
router.verify_openwrt()?;
|
||||
|
||||
// Persist the connection so other views (e.g. the Home dashboard's
|
||||
// Network tile) can poll `openwrt.get-status` with no params instead
|
||||
// of every caller needing to carry host/credentials around. Only do
|
||||
// this when the host actually came from params — otherwise every
|
||||
// no-args poll would re-save the same thing it just read.
|
||||
if host_from_params {
|
||||
let _ = net_router::configure_router(
|
||||
&self.config.data_dir,
|
||||
net_router::RouterType::OpenWrt,
|
||||
&host,
|
||||
None,
|
||||
Some(&ssh_user),
|
||||
Some(&ssh_password),
|
||||
).await;
|
||||
}
|
||||
|
||||
// System info
|
||||
let release = router.run_ok("cat /etc/openwrt_release").unwrap_or_default();
|
||||
let hostname = router
|
||||
|
||||
@ -49,10 +49,12 @@ export const useHomeStatusStore = defineStore('homeStatus', () => {
|
||||
const bitcoinStale = ref(false)
|
||||
const vpnLoadState = ref<LoadState>('idle')
|
||||
const fipsLoadState = ref<LoadState>('idle')
|
||||
const tollgateLoadState = ref<LoadState>('idle')
|
||||
const lastSystemRefreshAt = ref<number | null>(null)
|
||||
const lastBitcoinRefreshAt = ref<number | null>(null)
|
||||
const lastVpnRefreshAt = ref<number | null>(null)
|
||||
const lastFipsRefreshAt = ref<number | null>(null)
|
||||
const lastTollgateRefreshAt = ref<number | null>(null)
|
||||
|
||||
const vpnStatus = ref<{
|
||||
connected: boolean | null
|
||||
@ -67,6 +69,9 @@ export const useHomeStatusStore = defineStore('homeStatus', () => {
|
||||
authenticated_peer_count?: number
|
||||
} | null>(null)
|
||||
|
||||
// null = no OpenWrt router configured at all (tile row shows "Not configured").
|
||||
const tollgateStatus = ref<{ installed: boolean; enabled: boolean } | null>(null)
|
||||
|
||||
const systemStatsLoaded = computed(() => systemLoadState.value === 'ready')
|
||||
const bitcoinKnown = computed(() => stats.bitcoinAvailable !== null)
|
||||
const vpnKnown = computed(() => vpnStatus.value.connected !== null)
|
||||
@ -185,12 +190,37 @@ export const useHomeStatusStore = defineStore('homeStatus', () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshTollgate() {
|
||||
tollgateLoadState.value = tollgateLoadState.value === 'ready' ? 'ready' : 'loading'
|
||||
try {
|
||||
const res = await rpcClient.call<{ tollgate: { installed: boolean; enabled?: boolean } }>({
|
||||
method: 'openwrt.get-status',
|
||||
timeout: 15000,
|
||||
})
|
||||
tollgateStatus.value = { installed: res.tollgate.installed, enabled: res.tollgate.enabled ?? false }
|
||||
tollgateLoadState.value = 'ready'
|
||||
lastTollgateRefreshAt.value = Date.now()
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : String(e)
|
||||
if (msg.includes('No router configured')) {
|
||||
// Not an error — most nodes simply don't have an OpenWrt gateway set up.
|
||||
tollgateStatus.value = null
|
||||
tollgateLoadState.value = 'ready'
|
||||
lastTollgateRefreshAt.value = Date.now()
|
||||
} else {
|
||||
// Transient failure (SSH hiccup, router rebooting) — keep last-known state.
|
||||
tollgateLoadState.value = tollgateStatus.value ? 'ready' : 'error'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function refresh(packages: Record<string, PackageDataEntry>) {
|
||||
await Promise.all([
|
||||
refreshSystemStats(),
|
||||
refreshBitcoin(packages),
|
||||
refreshVpn(packages),
|
||||
refreshFips(),
|
||||
refreshTollgate(),
|
||||
])
|
||||
}
|
||||
|
||||
@ -201,19 +231,23 @@ export const useHomeStatusStore = defineStore('homeStatus', () => {
|
||||
bitcoinStale,
|
||||
vpnLoadState,
|
||||
fipsLoadState,
|
||||
tollgateLoadState,
|
||||
systemStatsLoaded,
|
||||
bitcoinKnown,
|
||||
vpnKnown,
|
||||
vpnStatus,
|
||||
fipsStatus,
|
||||
tollgateStatus,
|
||||
lastSystemRefreshAt,
|
||||
lastBitcoinRefreshAt,
|
||||
lastVpnRefreshAt,
|
||||
lastFipsRefreshAt,
|
||||
lastTollgateRefreshAt,
|
||||
refresh,
|
||||
refreshSystemStats,
|
||||
refreshBitcoin,
|
||||
refreshVpn,
|
||||
refreshFips,
|
||||
refreshTollgate,
|
||||
}
|
||||
})
|
||||
|
||||
@ -173,6 +173,10 @@
|
||||
<div class="flex items-center gap-3"><div class="w-2 h-2 rounded-full" :class="fipsDotClass"></div><span class="text-sm text-white/80">FIPS</span></div>
|
||||
<span class="text-sm font-medium" :class="fipsTextClass">{{ fipsStatusLabel }}</span>
|
||||
</div>
|
||||
<div v-if="homeStatus.tollgateStatus" class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
||||
<div class="flex items-center gap-3"><div class="w-2 h-2 rounded-full" :class="tollgateDotClass"></div><span class="text-sm text-white/80">TollGate</span></div>
|
||||
<span class="text-sm font-medium" :class="tollgateTextClass">{{ tollgateStatusLabel }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="home-card-buttons flex gap-2 mt-auto pt-4 shrink-0">
|
||||
<RouterLink to="/dashboard/server" class="home-card-btn flex-1 px-4 py-2 glass-button rounded-lg text-sm font-medium text-center transition-colors">{{ t('home.manageNetwork') }}</RouterLink>
|
||||
@ -445,6 +449,22 @@ const fipsStatusLabel = computed(() => {
|
||||
const peers = s.authenticated_peer_count ?? 0
|
||||
return peers === 1 ? 'Active · 1 peer' : `Active · ${peers} peers`
|
||||
})
|
||||
const tollgateDotClass = computed(() => {
|
||||
const s = homeStatus.tollgateStatus
|
||||
if (!s || !s.installed) return 'bg-white/40'
|
||||
return s.enabled ? 'bg-green-400' : 'bg-yellow-400'
|
||||
})
|
||||
const tollgateTextClass = computed(() => {
|
||||
const s = homeStatus.tollgateStatus
|
||||
if (!s || !s.installed) return 'text-white/40'
|
||||
return s.enabled ? 'text-green-400' : 'text-yellow-400'
|
||||
})
|
||||
const tollgateStatusLabel = computed(() => {
|
||||
const s = homeStatus.tollgateStatus
|
||||
if (!s) return homeStatus.tollgateLoadState === 'loading' ? 'Checking…' : 'Not configured'
|
||||
if (!s.installed) return 'Not installed'
|
||||
return s.enabled ? 'Enabled' : 'Disabled'
|
||||
})
|
||||
const bitcoinSyncDisplay = computed(() => {
|
||||
if (homeStatus.stats.bitcoinAvailable === null) return 'Checking…'
|
||||
if (!homeStatus.stats.bitcoinAvailable) return 'Not running'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user