Backend - api/routes.rs models the Babel-style route shape; metric uses an untagged enum to round-trip both numeric hop counts and the literal "infinite" string the daemon emits for poisoned routes - routes_snapshot() runs the three GETs concurrently with try_join so the snapshot is internally consistent - poller spawns a second 5s loop emitting routes://updated; both loops are owned by the Poller and aborted together on stop_daemon Frontend - routes store mirrors the snapshot shape; tabbed view (radix-vue) with selected, fallback and queried lists - RouteTable component shared by selected/fallback; metric column is colour-coded (0 green, low neutral, high yellow, infinite red) - Queried subnets show a live `expires in 12s` countdown driven by a 1Hz tick ref instead of mutating the store
59 lines
1.8 KiB
Vue
59 lines
1.8 KiB
Vue
<script setup lang="ts">
|
|
import type { Metric, Route } from "@/lib/api";
|
|
|
|
defineProps<{
|
|
rows: Route[];
|
|
ready: boolean;
|
|
}>();
|
|
|
|
function metricLabel(m: Metric): string {
|
|
return typeof m === "number" ? String(m) : m;
|
|
}
|
|
|
|
function metricClass(m: Metric): string {
|
|
if (typeof m === "number") {
|
|
if (m === 0) return "text-emerald-500";
|
|
if (m < 10) return "text-foreground";
|
|
return "text-yellow-500";
|
|
}
|
|
return "text-destructive";
|
|
}
|
|
|
|
function rowKey(r: Route): string {
|
|
return `${r.subnet}|${r.nextHop}|${r.seqno}`;
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="overflow-hidden rounded-lg border border-border">
|
|
<table class="w-full text-sm">
|
|
<thead class="bg-muted/40 text-xs uppercase text-muted-foreground">
|
|
<tr>
|
|
<th class="px-4 py-2 text-left font-medium">Subnet</th>
|
|
<th class="px-4 py-2 text-left font-medium">Next hop</th>
|
|
<th class="px-4 py-2 text-right font-medium">Metric</th>
|
|
<th class="px-4 py-2 text-right font-medium">Seqno</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-border">
|
|
<tr v-if="!rows.length">
|
|
<td colspan="4" class="px-4 py-8 text-center text-muted-foreground">
|
|
{{ ready ? "No routes." : "Daemon offline." }}
|
|
</td>
|
|
</tr>
|
|
<tr v-for="r in rows" :key="rowKey(r)" class="hover:bg-muted/30">
|
|
<td class="px-4 py-2 font-mono text-xs break-all">{{ r.subnet }}</td>
|
|
<td class="px-4 py-2 font-mono text-xs break-all">{{ r.nextHop }}</td>
|
|
<td
|
|
class="px-4 py-2 text-right font-mono text-xs"
|
|
:class="metricClass(r.metric)"
|
|
>
|
|
{{ metricLabel(r.metric) }}
|
|
</td>
|
|
<td class="px-4 py-2 text-right font-mono text-xs">{{ r.seqno }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</template>
|