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:
@@ -435,6 +435,7 @@ const ROUTE_BACKGROUNDS: Record<string, string> = {
|
||||
'/dashboard/mesh': 'bg-mesh.jpg',
|
||||
'/dashboard/server': 'bg-network.jpg',
|
||||
'/dashboard/web5': 'bg-web5.jpg',
|
||||
'/dashboard/federation': 'bg-web5.jpg',
|
||||
'/dashboard/settings': 'bg-settings.jpg',
|
||||
'/dashboard/chat': 'bg-aiui.jpg',
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<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="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>
|
||||
</div>
|
||||
<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 -->
|
||||
<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">
|
||||
<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>
|
||||
</div>
|
||||
<button @click="copyDid" class="glass-button px-2.5 py-1 rounded text-[10px]">{{ didCopied ? 'Copied!' : 'Copy' }}</button>
|
||||
@@ -163,21 +163,32 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Invite Code Display -->
|
||||
<div v-if="inviteCode" class="glass-card p-6 mb-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-lg font-semibold text-white">Invite Code</h2>
|
||||
<button @click="inviteCode = ''" class="text-white/40 hover:text-white/70 transition-colors text-sm">Dismiss</button>
|
||||
</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>
|
||||
<!-- Invite Code Modal -->
|
||||
<Teleport to="body">
|
||||
<Transition name="modal">
|
||||
<div v-if="inviteCode" class="fixed inset-0 z-[3000] flex items-center justify-center p-4" @click.self="inviteCode = ''">
|
||||
<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">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-lg font-semibold text-white">{{ inviteType === 'trusted' ? 'Link Your Nodes — Invite Code' : 'Peer Invite Code' }}</h2>
|
||||
<button @click="inviteCode = ''" class="text-white/40 hover:text-white/70 transition-colors">
|
||||
<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-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 -->
|
||||
<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">
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-3">
|
||||
<div v-else class="space-y-3 overflow-y-auto">
|
||||
<div
|
||||
v-for="node in trustedNodes"
|
||||
:key="node.did"
|
||||
@@ -266,7 +277,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 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">
|
||||
<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
|
||||
@@ -282,7 +293,7 @@
|
||||
<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>
|
||||
</div>
|
||||
<div v-else class="space-y-3">
|
||||
<div v-else class="space-y-3 overflow-y-auto">
|
||||
<div
|
||||
v-for="node in peerNodes"
|
||||
:key="node.did"
|
||||
@@ -339,7 +350,7 @@
|
||||
>
|
||||
<option value="trusted">Trusted</option>
|
||||
<option value="observer">Observer</option>
|
||||
<option value="untrusted">Untrusted</option>
|
||||
<option value="untrusted">Blocked</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -436,44 +447,49 @@
|
||||
</div>
|
||||
|
||||
<!-- 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">
|
||||
<div class="glass-card p-6 w-full max-w-md">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-xl font-semibold text-white">Join Federation</h2>
|
||||
<button @click="showJoinModal = false" class="text-white/40 hover:text-white/70 transition-colors">
|
||||
<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>
|
||||
<Teleport to="body">
|
||||
<Transition name="modal">
|
||||
<div v-if="showJoinModal" class="fixed inset-0 z-[3000] flex items-center justify-center p-4" @click.self="showJoinModal = 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">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-xl font-semibold text-white">Join Federation</h2>
|
||||
<button @click="showJoinModal = false" class="text-white/40 hover:text-white/70 transition-colors">
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
@@ -483,10 +499,12 @@ import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { rpcClient } from '@/api/rpc-client'
|
||||
import { useTransportStore } from '@/stores/transport'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import NetworkMap from '@/components/federation/NetworkMap.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const transportStore = useTransportStore()
|
||||
const appStore = useAppStore()
|
||||
|
||||
interface AppStatus {
|
||||
id: string
|
||||
@@ -568,7 +586,7 @@ const mapNodes = computed(() => {
|
||||
if (selfDid.value) {
|
||||
result.push({
|
||||
did: selfDid.value,
|
||||
label: 'This Node',
|
||||
label: appStore.serverName,
|
||||
trust_level: 'trusted' as const,
|
||||
online: true,
|
||||
app_count: 0,
|
||||
|
||||
Reference in New Issue
Block a user