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:
@@ -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)
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user