diff --git a/neode-ui/src/views/Federation.vue b/neode-ui/src/views/Federation.vue index 1f4e5680..4717193f 100644 --- a/neode-ui/src/views/Federation.vue +++ b/neode-ui/src/views/Federation.vue @@ -73,32 +73,52 @@ @@ -449,6 +508,11 @@ const nodes = ref([]) const loading = ref(true) const error = ref('') const selectedNode = ref(null) +const inviteType = ref<'trusted' | 'observer'>('trusted') + +// Split nodes into Your Nodes (trusted) and Peers (observer/untrusted) +const trustedNodes = computed(() => nodes.value.filter(n => n.trust_level === 'trusted')) +const peerNodes = computed(() => nodes.value.filter(n => n.trust_level !== 'trusted')) const inviteCode = ref('') const generatingInvite = ref(false) @@ -716,6 +780,32 @@ function formatBytes(bytes?: number): string { return val.toFixed(1) + ' ' + units[i] } +// Dead node cleanup +const cleaningNodes = ref(false) +async function cleanupDeadNodes() { + cleaningNodes.value = true + try { + const deadNodes = nodes.value.filter(n => !isOnline(n) && (!n.last_seen || n.last_seen === 'never')) + for (const node of deadNodes) { + await rpcClient.federationRemoveNode(node.did) + } + await loadNodes() + } catch (e) { + error.value = e instanceof Error ? e.message : 'Cleanup failed' + } finally { + cleaningNodes.value = false + } +} + +function formatTimeAgo(iso: string): string { + if (!iso || iso === 'never') return 'never' + const ms = Date.now() - new Date(iso).getTime() + if (ms < 60000) return 'just now' + if (ms < 3600000) return `${Math.floor(ms / 60000)}m ago` + if (ms < 86400000) return `${Math.floor(ms / 3600000)}h ago` + return `${Math.floor(ms / 86400000)}d ago` +} + // DID rotation const showRotateModal = ref(false) const rotatePassword = ref('')