feat: add DWN sync status section to Federation node detail modal

- Shows message count, last sync time, sync status indicator
- Sync Now button triggers dwn.sync RPC with loading state
- DWN status dot in node list cards (green/amber/red)
- Loads DWN status on mount alongside federation nodes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian 2026-03-13 02:50:55 +00:00
parent fa674fc79e
commit 9a22a3c5df
2 changed files with 82 additions and 6 deletions

View File

@ -526,7 +526,7 @@
- [x] **DWN-SYNC-02** — Test DWN sync across all 4 nodes. Register the same protocol on all 4 nodes. Write unique messages on each node (node A writes 5, B writes 3, C writes 2, D writes 4 = 14 total). Trigger sync from each node. After sync completes, query all messages on each node — every node should have all 14 messages. If sync is missing messages: check the bidirectional replication logic in `dwn_sync.rs`, ensure Tor SOCKS proxy is used correctly, check for deduplication issues. **Acceptance**: All 4 nodes have all 14 messages after sync. Message content and metadata intact.
- [ ] **DWN-SYNC-03** — Add DWN sync status to Federation dashboard. In `neode-ui/src/views/Federation.vue`, in the node detail modal, add a "DWN Sync" section showing: last sync time, messages synced count, sync status (idle/syncing/error), and a "Sync Now" button. Wire to `dwn.sync` RPC. In the node list, add a small DWN icon/badge showing sync state (green dot = synced recently, amber = stale, red = error). Fetch DWN status alongside federation state. **Acceptance**: Federation dashboard shows DWN sync state per node. Manual sync trigger works from the modal. Deploy and verify.
- [x] **DWN-SYNC-03** — Add DWN sync status to Federation dashboard. In `neode-ui/src/views/Federation.vue`, in the node detail modal, add a "DWN Sync" section showing: last sync time, messages synced count, sync status (idle/syncing/error), and a "Sync Now" button. Wire to `dwn.sync` RPC. In the node list, add a small DWN icon/badge showing sync state (green dot = synced recently, amber = stale, red = error). Fetch DWN status alongside federation state. **Acceptance**: Federation dashboard shows DWN sync state per node. Manual sync trigger works from the modal. Deploy and verify.
### Sprint 46: Node Visualization Map (July 2026 Week 1-2)

View File

@ -151,9 +151,9 @@
<span class="text-white/30">CPU:</span>
{{ node.last_state?.cpu_usage_percent != null ? node.last_state.cpu_usage_percent.toFixed(1) + '%' : '--' }}
</div>
<div>
<span class="text-white/30">Tor:</span>
{{ node.last_state?.tor_active ? 'Active' : '--' }}
<div class="flex items-center gap-1">
<span class="text-white/30">DWN:</span>
<span class="w-1.5 h-1.5 rounded-full" :class="dwnSyncDotClass"></span>
</div>
<div>
<span class="text-white/30">Seen:</span>
@ -244,6 +244,28 @@
<p v-if="deployResult" class="text-xs mt-2" :class="deployResult.startsWith('Error') ? 'text-red-400' : 'text-green-400'">{{ deployResult }}</p>
</div>
<!-- DWN Sync -->
<div class="bg-white/5 rounded-lg p-3">
<div class="flex items-center justify-between mb-2">
<p class="text-xs text-white/40">DWN Sync</p>
<div class="flex items-center gap-1.5">
<span class="w-1.5 h-1.5 rounded-full" :class="dwnSyncDotClass"></span>
<span class="text-xs text-white/50">{{ dwnSyncLabel }}</span>
</div>
</div>
<div class="grid grid-cols-2 gap-2 text-sm text-white/70 mb-3">
<div><span class="text-white/30">Messages:</span> {{ dwnStatus?.message_count ?? '--' }}</div>
<div><span class="text-white/30">Last sync:</span> {{ dwnStatus?.last_sync ? timeAgo(dwnStatus.last_sync) : 'never' }}</div>
</div>
<button
@click="triggerDwnSync"
class="px-3 py-1.5 glass-button rounded text-xs text-white/90 font-medium disabled:opacity-50"
:disabled="dwnSyncing"
>
{{ dwnSyncing ? 'Syncing...' : 'Sync Now' }}
</button>
</div>
<div v-if="!confirmRemove">
<button
@click="confirmRemove = true"
@ -313,7 +335,7 @@
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { rpcClient } from '@/api/rpc-client'
interface AppStatus {
@ -369,6 +391,36 @@ const deployAppId = ref('')
const deploying = ref(false)
const deployResult = ref('')
interface DwnStatus {
sync_status: string
last_sync: string | null
messages_synced: number
message_count: number
}
const dwnStatus = ref<DwnStatus | null>(null)
const dwnSyncing = ref(false)
const dwnSyncDotClass = computed(() => {
if (!dwnStatus.value) return 'bg-white/30'
switch (dwnStatus.value.sync_status) {
case 'synced': return 'bg-green-400'
case 'syncing': return 'bg-yellow-400 animate-pulse'
case 'error': return 'bg-red-400'
default: return 'bg-white/30'
}
})
const dwnSyncLabel = computed(() => {
if (!dwnStatus.value) return 'Unknown'
switch (dwnStatus.value.sync_status) {
case 'synced': return 'Synced'
case 'syncing': return 'Syncing...'
case 'error': return 'Error'
default: return dwnStatus.value.sync_status
}
})
async function loadNodes() {
try {
loading.value = true
@ -483,6 +535,27 @@ async function deployApp(did: string) {
}
}
async function loadDwnStatus() {
try {
const result = await rpcClient.call<DwnStatus>({ method: 'dwn.status' })
dwnStatus.value = result
} catch {
dwnStatus.value = null
}
}
async function triggerDwnSync() {
try {
dwnSyncing.value = true
await rpcClient.call({ method: 'dwn.sync', timeout: 120000 })
await loadDwnStatus()
} catch {
// Silently handle sync errors
} finally {
dwnSyncing.value = false
}
}
function isOnline(node: FederatedNode): boolean {
if (!node.last_seen) return false
const lastSeen = new Date(node.last_seen).getTime()
@ -532,5 +605,8 @@ function trustBadgeClass(level: string): string {
}
}
onMounted(loadNodes)
onMounted(() => {
loadNodes()
loadDwnStatus()
})
</script>