feat(federation): v1.5.0 bump + transport badge on each node card
Every federated node card now shows a colored badge indicating how
archipelago actually reached the peer on the most recent successful
call — FIPS / TOR / LAN / MESH — not a prediction based on available
addresses. The badge is hidden when we've never reached the peer.
Backend:
- Cargo.toml: 1.4.0 → 1.5.0 (visible in the sidebar health endpoint).
- FederatedNode gains last_transport + last_transport_at (serde
default for back-compat with v1.4 nodes.json files).
- federation::storage::record_peer_transport(did, onion, transport)
— writes both fields plus last_seen after each successful peer
call. Matches by DID first, falls back to onion.
- federation::sync::sync_with_peer now calls record_peer_transport
immediately after a successful PeerRequest return, so the badge
on the sync'ing peer's card reflects the transport the call
actually rode (fips vs tor).
Frontend:
- types.ts FederatedNode gains last_transport / last_transport_at
(union-typed to the four known kinds).
- NodeList.vue: new transportBadge(node) returns {label, cls, title}
tuned per transport. Hidden when last_transport is absent so we
never lie. Tooltip shows "Last reached via <x> · <time ago>" so
stale data is self-evident. Removed the predictive icon from the
transport store — badge is now 100% ground-truth.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -53,10 +53,11 @@
|
||||
<div class="w-2.5 h-2.5 rounded-full shrink-0" :class="isOnline(node) ? 'bg-green-400' : 'bg-white/30'"></div>
|
||||
<span class="text-sm font-medium text-white truncate" :title="node.did">{{ nodeName(node) }}</span>
|
||||
<span
|
||||
class="text-xs shrink-0"
|
||||
:class="nodeTransportIcon(node.did).color"
|
||||
:title="'Transport: ' + nodeTransportIcon(node.did).label"
|
||||
>{{ nodeTransportIcon(node.did).icon }}</span>
|
||||
v-if="transportBadge(node)"
|
||||
class="text-[10px] font-semibold uppercase tracking-wider px-1.5 py-0.5 rounded shrink-0"
|
||||
:class="transportBadge(node)!.cls"
|
||||
:title="transportBadge(node)!.title"
|
||||
>{{ transportBadge(node)!.label }}</span>
|
||||
</div>
|
||||
<span
|
||||
class="text-xs px-2 py-0.5 rounded-full shrink-0"
|
||||
@@ -114,6 +115,12 @@
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<div class="w-2.5 h-2.5 rounded-full shrink-0" :class="isOnline(node) ? 'bg-green-400' : 'bg-white/30'"></div>
|
||||
<span class="text-sm font-medium text-white truncate" :title="node.did">{{ nodeName(node) }}</span>
|
||||
<span
|
||||
v-if="transportBadge(node)"
|
||||
class="text-[10px] font-semibold uppercase tracking-wider px-1.5 py-0.5 rounded shrink-0"
|
||||
:class="transportBadge(node)!.cls"
|
||||
:title="transportBadge(node)!.title"
|
||||
>{{ transportBadge(node)!.label }}</span>
|
||||
</div>
|
||||
<span class="text-xs px-2 py-0.5 rounded-full shrink-0" :class="trustBadgeClass(node.trust_level)">{{ node.trust_level }}</span>
|
||||
</div>
|
||||
@@ -130,7 +137,6 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useTransportStore } from '@/stores/transport'
|
||||
import type { FederatedNode, SyncResult } from './types'
|
||||
import { nodeName, nodeNameFromDid, timeAgo, formatTimeAgo, trustBadgeClass, isOnline } from './utils'
|
||||
|
||||
@@ -149,19 +155,44 @@ defineEmits<{
|
||||
'cleanup-dead': []
|
||||
}>()
|
||||
|
||||
const transportStore = useTransportStore()
|
||||
|
||||
const trustedNodes = computed(() => props.nodes.filter(n => n.trust_level === 'trusted'))
|
||||
const peerNodes = computed(() => props.nodes.filter(n => n.trust_level !== 'trusted'))
|
||||
|
||||
function nodeTransportIcon(did: string): { icon: string; color: string; label: string } {
|
||||
const peer = transportStore.peers.find(p => p.did === did)
|
||||
if (!peer) return { icon: '?', color: 'text-white/30', label: 'unknown' }
|
||||
switch (peer.preferred_transport) {
|
||||
case 'mesh': return { icon: '\u{1F4E1}', color: 'text-orange-400', label: 'mesh' }
|
||||
case 'lan': return { icon: '\u{1F310}', color: 'text-green-400', label: 'lan' }
|
||||
case 'tor': return { icon: '\u{1F9C5}', color: 'text-purple-400', label: 'tor' }
|
||||
default: return { icon: '?', color: 'text-white/30', label: 'unknown' }
|
||||
// Badge showing the actual transport the most recent reach used —
|
||||
// NOT a prediction. If we've never reached the peer, return null so
|
||||
// the badge stays hidden rather than lying. When the transport is
|
||||
// fips, the tooltip also shows how recent the reading is so stale
|
||||
// data is visible at a glance.
|
||||
function transportBadge(node: FederatedNode): { label: string; cls: string; title: string } | null {
|
||||
if (!node.last_transport) return null
|
||||
const age = node.last_transport_at ? timeAgo(node.last_transport_at) : 'unknown'
|
||||
switch (node.last_transport) {
|
||||
case 'fips':
|
||||
return {
|
||||
label: 'FIPS',
|
||||
cls: 'bg-cyan-500/20 text-cyan-300 ring-1 ring-cyan-400/40',
|
||||
title: `Last reached via FIPS mesh · ${age}`,
|
||||
}
|
||||
case 'tor':
|
||||
return {
|
||||
label: 'TOR',
|
||||
cls: 'bg-purple-500/20 text-purple-300 ring-1 ring-purple-400/40',
|
||||
title: `Last reached via Tor · ${age}`,
|
||||
}
|
||||
case 'lan':
|
||||
return {
|
||||
label: 'LAN',
|
||||
cls: 'bg-green-500/20 text-green-300 ring-1 ring-green-400/40',
|
||||
title: `Last reached via LAN · ${age}`,
|
||||
}
|
||||
case 'mesh':
|
||||
return {
|
||||
label: 'MESH',
|
||||
cls: 'bg-orange-500/20 text-orange-300 ring-1 ring-orange-400/40',
|
||||
title: `Last reached via mesh radio · ${age}`,
|
||||
}
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -25,6 +25,12 @@ export interface FederatedNode {
|
||||
name?: string
|
||||
last_seen?: string
|
||||
last_state?: NodeState
|
||||
/** bech32 FIPS npub this peer advertised (when known). */
|
||||
fips_npub?: string
|
||||
/** Transport used on the most recent successful reach: 'fips' | 'tor' | 'mesh' | 'lan'. */
|
||||
last_transport?: 'fips' | 'tor' | 'mesh' | 'lan'
|
||||
/** RFC 3339 timestamp of last_transport. */
|
||||
last_transport_at?: string
|
||||
}
|
||||
|
||||
export interface DwnStatus {
|
||||
|
||||
Reference in New Issue
Block a user