P3: routes (selected, fallback, queried)
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
This commit is contained in:
58
src/components/RouteTable.vue
Normal file
58
src/components/RouteTable.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user