feat: DID management UI in Federation — rotate DID + notify peers
- "My Node Identity" card shows DID with copy button - "Rotate DID" button opens modal with password confirmation - Rotation generates new keypair, then auto-notifies all federation peers - Shows success/failure count after notification Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fb953e888a
commit
f0e33b91ac
@ -15,6 +15,42 @@
|
|||||||
<p class="text-sm text-white/60 mt-2">{{ nodes.length }} federated node{{ nodes.length !== 1 ? 's' : '' }}</p>
|
<p class="text-sm text-white/60 mt-2">{{ nodes.length }} federated node{{ nodes.length !== 1 ? 's' : '' }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- My Node Identity -->
|
||||||
|
<div v-if="selfDid" class="glass-card p-4 mb-6">
|
||||||
|
<div class="flex items-center justify-between gap-4">
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<p class="text-xs text-white/40 mb-1">Your Node DID</p>
|
||||||
|
<p class="text-sm text-white/70 font-mono truncate cursor-pointer" :title="selfDid" @click="copyDid">{{ selfDid }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 shrink-0">
|
||||||
|
<button @click="copyDid" class="glass-button px-3 py-1.5 rounded-lg text-xs">Copy</button>
|
||||||
|
<button @click="showRotateModal = true" class="glass-button px-3 py-1.5 rounded-lg text-xs text-orange-300">Rotate DID</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Rotate DID Modal -->
|
||||||
|
<Teleport to="body">
|
||||||
|
<Transition name="modal">
|
||||||
|
<div v-if="showRotateModal" class="fixed inset-0 z-[3000] flex items-center justify-center p-4" @click.self="showRotateModal = false">
|
||||||
|
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm"></div>
|
||||||
|
<div class="glass-card p-6 max-w-md w-full relative z-10">
|
||||||
|
<h3 class="text-lg font-semibold text-white mb-2">Rotate Node DID</h3>
|
||||||
|
<p class="text-sm text-white/60 mb-4">This generates a new identity keypair and notifies all federated peers. Your old DID will no longer be valid.</p>
|
||||||
|
<input v-model="rotatePassword" type="password" placeholder="Enter your password to confirm" class="w-full bg-black/30 border border-white/10 rounded-lg px-3 py-2 text-sm text-white placeholder-white/30 focus:outline-none focus:border-orange-500/50 mb-4" />
|
||||||
|
<p v-if="rotateError" class="text-red-400 text-xs mb-3">{{ rotateError }}</p>
|
||||||
|
<p v-if="rotateSuccess" class="text-green-400 text-xs mb-3">{{ rotateSuccess }}</p>
|
||||||
|
<div class="flex gap-3">
|
||||||
|
<button @click="showRotateModal = false; rotatePassword = ''; rotateError = ''; rotateSuccess = ''" class="flex-1 glass-button px-4 py-2 rounded-lg text-sm">Cancel</button>
|
||||||
|
<button @click="rotateDid" :disabled="rotatingDid || !rotatePassword" class="flex-1 glass-button px-4 py-2 rounded-lg text-sm font-medium bg-orange-500/20 border-orange-500/30 disabled:opacity-50">
|
||||||
|
{{ rotatingDid ? 'Rotating...' : 'Rotate & Notify Peers' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</Teleport>
|
||||||
|
|
||||||
<!-- View Tabs -->
|
<!-- View Tabs -->
|
||||||
<div v-if="nodes.length > 0" class="flex gap-1 mb-6 p-1 bg-black/20 rounded-lg w-fit">
|
<div v-if="nodes.length > 0" class="flex gap-1 mb-6 p-1 bg-black/20 rounded-lg w-fit">
|
||||||
<button
|
<button
|
||||||
@ -679,6 +715,52 @@ function formatBytes(bytes?: number): string {
|
|||||||
return val.toFixed(1) + ' ' + units[i]
|
return val.toFixed(1) + ' ' + units[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DID rotation
|
||||||
|
const showRotateModal = ref(false)
|
||||||
|
const rotatePassword = ref('')
|
||||||
|
const rotatingDid = ref(false)
|
||||||
|
const rotateError = ref('')
|
||||||
|
const rotateSuccess = ref('')
|
||||||
|
|
||||||
|
function copyDid() {
|
||||||
|
if (selfDid.value) {
|
||||||
|
navigator.clipboard.writeText(selfDid.value).catch(() => {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function rotateDid() {
|
||||||
|
if (!rotatePassword.value) return
|
||||||
|
rotatingDid.value = true
|
||||||
|
rotateError.value = ''
|
||||||
|
rotateSuccess.value = ''
|
||||||
|
try {
|
||||||
|
const result = await rpcClient.call<{
|
||||||
|
old_did: string; new_did: string; proof_signature: string; proof_message: string
|
||||||
|
}>({ method: 'node.rotate-did', params: { password: rotatePassword.value } })
|
||||||
|
|
||||||
|
selfDid.value = result.new_did
|
||||||
|
rotateSuccess.value = `DID rotated. Notifying peers...`
|
||||||
|
|
||||||
|
// Notify federation peers
|
||||||
|
const notify = await rpcClient.call<{ notified: number; failed: number }>({
|
||||||
|
method: 'federation.notify-did-change',
|
||||||
|
params: {
|
||||||
|
old_did: result.old_did,
|
||||||
|
new_did: result.new_did,
|
||||||
|
proof_signature: result.proof_signature,
|
||||||
|
proof_message: result.proof_message,
|
||||||
|
},
|
||||||
|
timeout: 120000,
|
||||||
|
})
|
||||||
|
rotateSuccess.value = `DID rotated successfully. ${notify.notified} peers notified${notify.failed > 0 ? `, ${notify.failed} failed` : ''}.`
|
||||||
|
rotatePassword.value = ''
|
||||||
|
} catch (err: unknown) {
|
||||||
|
rotateError.value = err instanceof Error ? err.message : 'Rotation failed'
|
||||||
|
} finally {
|
||||||
|
rotatingDid.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function formatUptime(secs: number): string {
|
function formatUptime(secs: number): string {
|
||||||
const days = Math.floor(secs / 86400)
|
const days = Math.floor(secs / 86400)
|
||||||
const hours = Math.floor((secs % 86400) / 3600)
|
const hours = Math.floor((secs % 86400) / 3600)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user