feat: Federation UI polish — modals, backgrounds, scroll, names, blocked
- Federation page uses bg-web5.jpg background - Invite code in full-screen modal with type label (Link/Peer) - Join modal upgraded to full-screen with backdrop blur - "Untrusted" renamed to "Blocked" in trust selector - Your Nodes / Peers containers: max-h-[60vh] with inner scroll - Server name from Settings shown on DID card + network map - DID sync between Web5 and Federation on rotation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
203b044646
commit
867e56cb84
@ -435,6 +435,7 @@ const ROUTE_BACKGROUNDS: Record<string, string> = {
|
|||||||
'/dashboard/mesh': 'bg-mesh.jpg',
|
'/dashboard/mesh': 'bg-mesh.jpg',
|
||||||
'/dashboard/server': 'bg-network.jpg',
|
'/dashboard/server': 'bg-network.jpg',
|
||||||
'/dashboard/web5': 'bg-web5.jpg',
|
'/dashboard/web5': 'bg-web5.jpg',
|
||||||
|
'/dashboard/federation': 'bg-web5.jpg',
|
||||||
'/dashboard/settings': 'bg-settings.jpg',
|
'/dashboard/settings': 'bg-settings.jpg',
|
||||||
'/dashboard/chat': 'bg-aiui.jpg',
|
'/dashboard/chat': 'bg-aiui.jpg',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@
|
|||||||
<div v-if="selfDid" class="hidden md:block shrink-0">
|
<div v-if="selfDid" class="hidden md:block shrink-0">
|
||||||
<div class="glass-card px-4 py-3 flex items-center gap-3">
|
<div class="glass-card px-4 py-3 flex items-center gap-3">
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<p class="text-[10px] text-white/40 mb-0.5">Your Node</p>
|
<p class="text-[10px] text-white/40 mb-0.5">{{ appStore.serverName }}</p>
|
||||||
<p class="text-xs text-white/80 font-mono cursor-pointer" :title="selfDid" @click="copyDid">{{ didCopied ? 'Copied!' : shortDid(selfDid) }}</p>
|
<p class="text-xs text-white/80 font-mono cursor-pointer" :title="selfDid" @click="copyDid">{{ didCopied ? 'Copied!' : shortDid(selfDid) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<button @click="copyDid" class="glass-button px-2.5 py-1 rounded text-[10px]">{{ didCopied ? 'Copied!' : 'Copy' }}</button>
|
<button @click="copyDid" class="glass-button px-2.5 py-1 rounded text-[10px]">{{ didCopied ? 'Copied!' : 'Copy' }}</button>
|
||||||
@ -30,7 +30,7 @@
|
|||||||
<!-- Mobile: DID below title -->
|
<!-- Mobile: DID below title -->
|
||||||
<div v-if="selfDid" class="md:hidden glass-card px-4 py-3 mt-3 flex items-center gap-3">
|
<div v-if="selfDid" class="md:hidden glass-card px-4 py-3 mt-3 flex items-center gap-3">
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<p class="text-[10px] text-white/40 mb-0.5">Your Node</p>
|
<p class="text-[10px] text-white/40 mb-0.5">{{ appStore.serverName }}</p>
|
||||||
<p class="text-xs text-white/80 font-mono truncate cursor-pointer" :title="selfDid" @click="copyDid">{{ didCopied ? 'Copied!' : shortDid(selfDid) }}</p>
|
<p class="text-xs text-white/80 font-mono truncate cursor-pointer" :title="selfDid" @click="copyDid">{{ didCopied ? 'Copied!' : shortDid(selfDid) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<button @click="copyDid" class="glass-button px-2.5 py-1 rounded text-[10px]">{{ didCopied ? 'Copied!' : 'Copy' }}</button>
|
<button @click="copyDid" class="glass-button px-2.5 py-1 rounded text-[10px]">{{ didCopied ? 'Copied!' : 'Copy' }}</button>
|
||||||
@ -163,21 +163,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Invite Code Display -->
|
<!-- Invite Code Modal -->
|
||||||
<div v-if="inviteCode" class="glass-card p-6 mb-6">
|
<Teleport to="body">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<Transition name="modal">
|
||||||
<h2 class="text-lg font-semibold text-white">Invite Code</h2>
|
<div v-if="inviteCode" class="fixed inset-0 z-[3000] flex items-center justify-center p-4" @click.self="inviteCode = ''">
|
||||||
<button @click="inviteCode = ''" class="text-white/40 hover:text-white/70 transition-colors text-sm">Dismiss</button>
|
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm"></div>
|
||||||
</div>
|
<div class="glass-card p-6 max-w-md w-full relative z-10">
|
||||||
<p class="text-xs text-white/60 mb-3">Share this code with the node you want to federate with. It can only be used once.</p>
|
<div class="flex items-center justify-between mb-4">
|
||||||
<div class="bg-black/30 rounded-lg p-4 font-mono text-xs text-orange-300 break-all select-all">{{ inviteCode }}</div>
|
<h2 class="text-lg font-semibold text-white">{{ inviteType === 'trusted' ? 'Link Your Nodes — Invite Code' : 'Peer Invite Code' }}</h2>
|
||||||
<button
|
<button @click="inviteCode = ''" class="text-white/40 hover:text-white/70 transition-colors">
|
||||||
@click="copyInviteCode"
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
class="mt-3 px-4 py-2 glass-button rounded text-sm text-white/90 hover:text-white transition-colors"
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
>
|
</svg>
|
||||||
{{ copiedInvite ? 'Copied' : 'Copy to Clipboard' }}
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
<p class="text-xs text-white/60 mb-3">Share this code with the node you want to federate with. It can only be used once.</p>
|
||||||
|
<div class="bg-black/30 rounded-lg p-4 font-mono text-xs text-orange-300 break-all select-all">{{ inviteCode }}</div>
|
||||||
|
<button
|
||||||
|
@click="copyInviteCode"
|
||||||
|
class="mt-3 px-4 py-2 glass-button rounded text-sm text-white/90 hover:text-white transition-colors"
|
||||||
|
>
|
||||||
|
{{ copiedInvite ? 'Copied' : 'Copy to Clipboard' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</Teleport>
|
||||||
|
|
||||||
<!-- Sync Results -->
|
<!-- Sync Results -->
|
||||||
<div v-if="syncResults.length > 0" class="glass-card p-6 mb-6">
|
<div v-if="syncResults.length > 0" class="glass-card p-6 mb-6">
|
||||||
@ -204,7 +215,7 @@
|
|||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||||||
|
|
||||||
<!-- Your Nodes (Trusted) -->
|
<!-- Your Nodes (Trusted) -->
|
||||||
<div class="glass-card p-6">
|
<div class="glass-card p-6 max-h-[60vh] flex flex-col">
|
||||||
<h2 class="text-lg font-semibold text-white mb-4">Your Nodes <span v-if="trustedNodes.length > 0" class="text-sm font-normal text-white/50">({{ trustedNodes.length }})</span></h2>
|
<h2 class="text-lg font-semibold text-white mb-4">Your Nodes <span v-if="trustedNodes.length > 0" class="text-sm font-normal text-white/50">({{ trustedNodes.length }})</span></h2>
|
||||||
|
|
||||||
<div v-if="loading" class="flex items-center gap-3 py-8 justify-center">
|
<div v-if="loading" class="flex items-center gap-3 py-8 justify-center">
|
||||||
@ -220,7 +231,7 @@
|
|||||||
<p class="text-white/30 text-xs">Generate an invite code or join an existing federation</p>
|
<p class="text-white/30 text-xs">Generate an invite code or join an existing federation</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="space-y-3">
|
<div v-else class="space-y-3 overflow-y-auto">
|
||||||
<div
|
<div
|
||||||
v-for="node in trustedNodes"
|
v-for="node in trustedNodes"
|
||||||
:key="node.did"
|
:key="node.did"
|
||||||
@ -266,7 +277,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Peers (Observer level) -->
|
<!-- Peers (Observer level) -->
|
||||||
<div class="glass-card p-6">
|
<div class="glass-card p-6 max-h-[60vh] flex flex-col">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<h2 class="text-lg font-semibold text-white">Peers <span v-if="peerNodes.length > 0" class="text-sm font-normal text-white/50">({{ peerNodes.length }})</span></h2>
|
<h2 class="text-lg font-semibold text-white">Peers <span v-if="peerNodes.length > 0" class="text-sm font-normal text-white/50">({{ peerNodes.length }})</span></h2>
|
||||||
<button
|
<button
|
||||||
@ -282,7 +293,7 @@
|
|||||||
<p class="text-white/50 text-sm">No peers yet</p>
|
<p class="text-white/50 text-sm">No peers yet</p>
|
||||||
<p class="text-white/30 text-xs mt-1">Invite a peer to share public content</p>
|
<p class="text-white/30 text-xs mt-1">Invite a peer to share public content</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="space-y-3">
|
<div v-else class="space-y-3 overflow-y-auto">
|
||||||
<div
|
<div
|
||||||
v-for="node in peerNodes"
|
v-for="node in peerNodes"
|
||||||
:key="node.did"
|
:key="node.did"
|
||||||
@ -339,7 +350,7 @@
|
|||||||
>
|
>
|
||||||
<option value="trusted">Trusted</option>
|
<option value="trusted">Trusted</option>
|
||||||
<option value="observer">Observer</option>
|
<option value="observer">Observer</option>
|
||||||
<option value="untrusted">Untrusted</option>
|
<option value="untrusted">Blocked</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -436,44 +447,49 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Join Modal -->
|
<!-- Join Modal -->
|
||||||
<div v-if="showJoinModal" class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/10 backdrop-blur-md" @click.self="showJoinModal = false">
|
<Teleport to="body">
|
||||||
<div class="glass-card p-6 w-full max-w-md">
|
<Transition name="modal">
|
||||||
<div class="flex items-center justify-between mb-6">
|
<div v-if="showJoinModal" class="fixed inset-0 z-[3000] flex items-center justify-center p-4" @click.self="showJoinModal = false">
|
||||||
<h2 class="text-xl font-semibold text-white">Join Federation</h2>
|
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm"></div>
|
||||||
<button @click="showJoinModal = false" class="text-white/40 hover:text-white/70 transition-colors">
|
<div class="glass-card p-6 max-w-md w-full relative z-10">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<div class="flex items-center justify-between mb-6">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
<h2 class="text-xl font-semibold text-white">Join Federation</h2>
|
||||||
</svg>
|
<button @click="showJoinModal = false" class="text-white/40 hover:text-white/70 transition-colors">
|
||||||
</button>
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-sm text-white/60 mb-4">Paste the invite code from the node you want to federate with.</p>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
v-model="joinCode"
|
||||||
|
placeholder="fed1:..."
|
||||||
|
rows="3"
|
||||||
|
class="w-full bg-black/30 text-white text-sm rounded-lg p-3 border border-white/10 focus:border-orange-400/50 focus:outline-none font-mono resize-none"
|
||||||
|
></textarea>
|
||||||
|
|
||||||
|
<div v-if="joinError" class="mt-3 text-sm text-red-400">{{ joinError }}</div>
|
||||||
|
<div v-if="joinSuccess" class="mt-3 text-sm text-green-400">Successfully joined federation</div>
|
||||||
|
|
||||||
|
<div class="flex gap-3 mt-4">
|
||||||
|
<button
|
||||||
|
@click="showJoinModal = false"
|
||||||
|
class="flex-1 px-4 py-2 glass-button rounded text-sm text-white/70"
|
||||||
|
>Cancel</button>
|
||||||
|
<button
|
||||||
|
@click="joinFederation"
|
||||||
|
class="flex-1 px-4 py-2 glass-button rounded text-sm text-white font-medium disabled:opacity-50"
|
||||||
|
:disabled="joining || !joinCode.trim()"
|
||||||
|
>
|
||||||
|
{{ joining ? 'Joining...' : 'Join' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Transition>
|
||||||
<p class="text-sm text-white/60 mb-4">Paste the invite code from the node you want to federate with.</p>
|
</Teleport>
|
||||||
|
|
||||||
<textarea
|
|
||||||
v-model="joinCode"
|
|
||||||
placeholder="fed1:..."
|
|
||||||
rows="3"
|
|
||||||
class="w-full bg-black/30 text-white text-sm rounded-lg p-3 border border-white/10 focus:border-orange-400/50 focus:outline-none font-mono resize-none"
|
|
||||||
></textarea>
|
|
||||||
|
|
||||||
<div v-if="joinError" class="mt-3 text-sm text-red-400">{{ joinError }}</div>
|
|
||||||
<div v-if="joinSuccess" class="mt-3 text-sm text-green-400">Successfully joined federation</div>
|
|
||||||
|
|
||||||
<div class="flex gap-3 mt-4">
|
|
||||||
<button
|
|
||||||
@click="showJoinModal = false"
|
|
||||||
class="flex-1 px-4 py-2 glass-button rounded text-sm text-white/70"
|
|
||||||
>Cancel</button>
|
|
||||||
<button
|
|
||||||
@click="joinFederation"
|
|
||||||
class="flex-1 px-4 py-2 glass-button rounded text-sm text-white font-medium disabled:opacity-50"
|
|
||||||
:disabled="joining || !joinCode.trim()"
|
|
||||||
>
|
|
||||||
{{ joining ? 'Joining...' : 'Join' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -483,10 +499,12 @@ import { ref, computed, onMounted } from 'vue'
|
|||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { rpcClient } from '@/api/rpc-client'
|
import { rpcClient } from '@/api/rpc-client'
|
||||||
import { useTransportStore } from '@/stores/transport'
|
import { useTransportStore } from '@/stores/transport'
|
||||||
|
import { useAppStore } from '@/stores/app'
|
||||||
import NetworkMap from '@/components/federation/NetworkMap.vue'
|
import NetworkMap from '@/components/federation/NetworkMap.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const transportStore = useTransportStore()
|
const transportStore = useTransportStore()
|
||||||
|
const appStore = useAppStore()
|
||||||
|
|
||||||
interface AppStatus {
|
interface AppStatus {
|
||||||
id: string
|
id: string
|
||||||
@ -568,7 +586,7 @@ const mapNodes = computed(() => {
|
|||||||
if (selfDid.value) {
|
if (selfDid.value) {
|
||||||
result.push({
|
result.push({
|
||||||
did: selfDid.value,
|
did: selfDid.value,
|
||||||
label: 'This Node',
|
label: appStore.serverName,
|
||||||
trust_level: 'trusted' as const,
|
trust_level: 'trusted' as const,
|
||||||
online: true,
|
online: true,
|
||||||
app_count: 0,
|
app_count: 0,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user