fix: resolve did:dht compilation errors

- Simplify DHT encoding: use JSON instead of DNS packets (drop simple-dns)
- Fix mainline crate API: SigningKey takes 32 bytes, get_mutable returns Result
- Add missing dht_did field to IdentityRecord constructor
- Store DID Document as JSON in DHT (DNS encoding deferred)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-14 04:14:04 +00:00
parent 0e9df969f1
commit 4a3611f3b4
26 changed files with 451 additions and 590 deletions

View File

@@ -812,13 +812,6 @@ function launchApp() {
'tailscale': { dev: 'http://localhost:8240', prod: 'http://localhost:8240' },
'lnd': { dev: 'http://localhost:8081', prod: 'http://localhost:8081' },
'bitcoin-knots': { dev: 'http://localhost:8334', prod: 'http://localhost:8334' },
'botfights': { dev: 'https://botfights.net', prod: 'https://botfights.net' },
'nwnn': { dev: 'https://nwnn.l484.com', prod: 'https://nwnn.l484.com' },
'484-kitchen': { dev: 'https://484.kitchen', prod: 'https://484.kitchen' },
'call-the-operator': { dev: 'https://cta.tx1138.com', prod: 'https://cta.tx1138.com' },
'arch-presentation': { dev: 'https://present.l484.com', prod: 'https://present.l484.com' },
'syntropy-institute': { dev: 'https://syntropy.institute', prod: 'https://syntropy.institute' },
't-zero': { dev: 'https://teeminuszero.net', prod: 'https://teeminuszero.net' }
}
if (appUrls[id]) {

View File

@@ -1,8 +1,14 @@
<template>
<div class="pb-6">
<div class="hidden md:block mb-8">
<h1 class="text-3xl font-bold text-white mb-2">{{ t('apps.title') }}</h1>
<p class="text-white/70">{{ t('apps.subtitle') }}</p>
<div class="hidden md:flex items-start justify-between mb-8 gap-4">
<div>
<h1 class="text-3xl font-bold text-white mb-2">{{ t('apps.title') }}</h1>
<p class="text-white/70">{{ t('apps.subtitle') }}</p>
</div>
<div class="mode-switcher flex-shrink-0">
<RouterLink to="/dashboard/apps" class="mode-switcher-btn mode-switcher-btn-active">My Apps</RouterLink>
<RouterLink to="/dashboard/marketplace" class="mode-switcher-btn">App Store</RouterLink>
</div>
</div>
<!-- Search Bar -->

View File

@@ -1,6 +1,6 @@
<template>
<div class="chat-fullscreen">
<!-- Close button + connection indicator (desktop: top-right pill) -->
<!-- Close button (desktop: top-right pill) -->
<div class="chat-mode-pill flex">
<button class="chat-close-btn" :aria-label="t('chat.closeAssistant')" @click="closeChat">
<svg class="w-4 h-4" aria-hidden="true" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -8,11 +8,6 @@
</svg>
<span class="text-xs font-medium">{{ t('chat.close') }}</span>
</button>
<div
v-if="aiuiConnected"
class="w-2 h-2 rounded-full bg-green-400 ml-2 shadow-[0_0_6px_rgba(74,222,128,0.5)]"
:title="t('chat.aiuiConnected')"
/>
</div>
<!-- Loading indicator while checking availability -->
@@ -68,7 +63,6 @@ const { t } = useI18n()
const router = useRouter()
const aiuiFrame = ref<HTMLIFrameElement | null>(null)
const aiuiConnected = ref(false)
let broker: ContextBroker | null = null
const aiuiAvailable = ref<boolean | null>(null) // null = checking, true/false = result
@@ -115,9 +109,9 @@ function onAiuiMessage(event: MessageEvent) {
const expected = new URL(aiuiUrl.value, window.location.origin).origin
if (event.origin !== expected) return
} catch { return }
const msg = event.data
if (msg && msg.type === 'ready') {
aiuiConnected.value = true
// Listen for ready messages from AIUI iframe
if (event.data?.type === 'ready') {
// AIUI connected - could use for future features
}
}

View File

@@ -56,35 +56,42 @@
<span v-else-if="sectionCounts[section.id] !== undefined" class="text-white/30">{{ sectionCounts[section.id] }} items</span>
</div>
</div>
</div>
<!-- Peer Files Card -->
<div
v-if="hasFederatedPeers"
data-controller-container
tabindex="0"
class="glass-card p-6 cursor-pointer transition-all hover:-translate-y-1 hover:bg-white/10 mt-4"
@click="router.push({ name: 'peer-files' })"
>
<div class="flex items-center gap-4 mb-4">
<div class="flex-shrink-0 w-12 h-12 rounded-xl flex items-center justify-center bg-purple-500/15">
<svg class="w-7 h-7 text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
<!-- Peer Files Card -->
<div
data-controller-container
tabindex="0"
class="glass-card p-6 cursor-pointer transition-all hover:-translate-y-1 hover:bg-white/10"
@click="router.push({ name: 'peer-files' })"
>
<div class="flex items-center gap-4 mb-4">
<div class="flex-shrink-0 w-12 h-12 rounded-xl flex items-center justify-center bg-purple-500/15">
<svg class="w-7 h-7 text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
</svg>
</div>
<div class="flex-1 min-w-0">
<h3 class="text-lg font-semibold text-white mb-0.5 truncate">Peer Files</h3>
<p class="text-xs text-white/50">Browse files shared by federated nodes</p>
</div>
<svg class="w-5 h-5 text-white/30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</div>
<div class="flex-1 min-w-0">
<h3 class="text-lg font-semibold text-white mb-0.5 truncate">Peer Files</h3>
<p class="text-xs text-white/50">Browse files shared by federated nodes</p>
<div class="flex items-center gap-2 text-xs">
<template v-if="hasFederatedPeers">
<span class="inline-flex items-center gap-1.5 px-2 py-1 rounded-full bg-purple-500/15 text-purple-400">
<span class="w-1.5 h-1.5 rounded-full bg-purple-400"></span>
{{ peerCount }} peers
</span>
</template>
<template v-else>
<span class="inline-flex items-center gap-1.5 px-2 py-1 rounded-full bg-white/5 text-white/40">
<span class="w-1.5 h-1.5 rounded-full bg-white/30"></span>
No peers yet
</span>
<span class="text-white/30">Set up federation to share files</span>
</template>
</div>
<svg class="w-5 h-5 text-white/30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
</div>
<div class="flex items-center gap-2 text-xs">
<span class="inline-flex items-center gap-1.5 px-2 py-1 rounded-full bg-purple-500/15 text-purple-400">
<span class="w-1.5 h-1.5 rounded-full bg-purple-400"></span>
{{ peerCount }} peers
</span>
</div>
</div>

View File

@@ -82,7 +82,8 @@
:key="item.path"
:to="item.path"
class="sidebar-nav-item flex items-center gap-3 px-4 py-3 rounded-lg text-white/80 hover:bg-white/10 hover:text-white transition-colors"
exact-active-class="nav-tab-active"
:class="{ 'nav-tab-active': item.isCombined && (route.path.includes('/apps') || route.path.includes('/marketplace')) }"
:exact-active-class="item.isCombined ? undefined : 'nav-tab-active'"
:style="{ '--nav-stagger': idx }"
>
<svg class="w-5 h-5" aria-hidden="true" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -278,8 +279,8 @@
:class="[
'px-4 pt-4 md:pt-8 md:px-8 overflow-y-auto h-full',
needsMobileBackButtonSpace
? 'pb-[calc(var(--mobile-tab-bar-height,_72px)+96px)] md:pb-8'
: 'pb-4 md:pb-8'
? 'pb-[calc(var(--mobile-tab-bar-height,_72px)+96px)] md:pb-24'
: 'pb-[calc(var(--mobile-tab-bar-height,_72px)+48px)] md:pb-24'
]"
:style="mobileTabPaddingTop ? { paddingTop: (mobileTabPaddingTop + 16) + 'px' } : undefined"
>
@@ -649,8 +650,7 @@ interface NavItem {
const gamerDesktopNav: NavItem[] = [
{ path: '/dashboard', label: 'Home', icon: 'home' },
{ path: '/dashboard/apps', label: 'My Apps', icon: 'apps' },
{ path: '/dashboard/marketplace', label: 'App Store', icon: 'marketplace' },
{ path: '/dashboard/apps', label: 'Apps', icon: 'apps', isCombined: true },
{ path: '/dashboard/cloud', label: 'Cloud', icon: 'cloud' },
{ path: '/dashboard/server', label: 'Network', icon: 'server' },
{ path: '/dashboard/web5', label: 'Web5', icon: 'web5' },
@@ -869,15 +869,28 @@ function getTransitionName(currentRoute: RouteLocationNormalizedLoaded) {
return transitionName
}
// Health notifications from WebSocket data
// Health notifications from WebSocket data — deduplicated by container name
const dismissedNotifications = ref<Set<string>>(new Set())
const healthNotifications = computed(() => {
const notifs = store.data?.notifications ?? []
return notifs.filter(n => !dismissedNotifications.value.has(n.id)).slice(-5)
const visible = notifs.filter(n => !dismissedNotifications.value.has(n.id))
// Deduplicate: keep only the latest notification per container/title
const seen = new Map<string, typeof visible[0]>()
for (const n of visible) {
seen.set(n.title, n)
}
return [...seen.values()].slice(-3)
})
function dismissNotification(id: string) {
// Dismiss all notifications with the same title (container name)
const notif = (store.data?.notifications ?? []).find(n => n.id === id)
if (notif) {
for (const n of store.data?.notifications ?? []) {
if (n.title === notif.title) dismissedNotifications.value.add(n.id)
}
}
dismissedNotifications.value.add(id)
}
</script>

View File

@@ -1,6 +1,15 @@
<template>
<div>
<div class="hidden md:block mb-8">
<div class="mb-8">
<button
@click="router.push('/dashboard/web5')"
class="flex items-center gap-2 text-white/50 hover:text-white/80 transition-colors text-sm mb-4"
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg>
Back to Web5
</button>
<h1 class="text-3xl font-bold text-white mb-2">Federation</h1>
<p class="text-white/70">Manage trusted node clusters and sync state across your network</p>
<p class="text-sm text-white/60 mt-2">{{ nodes.length }} federated node{{ nodes.length !== 1 ? 's' : '' }}</p>
@@ -357,9 +366,12 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { rpcClient } from '@/api/rpc-client'
import NetworkMap from '@/components/federation/NetworkMap.vue'
const router = useRouter()
interface AppStatus {
id: string
status: string

View File

@@ -17,7 +17,7 @@
<!-- Info Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<!-- Server Name Card -->
<!-- Server Name Card (editable) -->
<div class="bg-black/20 rounded-xl px-5 py-4 border border-white/10">
<div class="flex items-center gap-3 mb-2">
<svg class="w-5 h-5 text-white/70" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -25,7 +25,31 @@
</svg>
<p class="text-xs font-semibold text-white/60 uppercase tracking-wide">{{ t('settings.serverName') }}</p>
</div>
<p class="text-lg font-semibold text-white/95">{{ serverName }}</p>
<div v-if="editingServerName" class="flex items-center gap-2">
<input
ref="serverNameInput"
v-model="serverNameDraft"
type="text"
maxlength="64"
class="flex-1 px-3 py-1.5 bg-white/10 border border-white/20 rounded-lg text-white text-lg font-semibold focus:outline-none focus:border-white/40 transition-colors"
@keydown.enter="saveServerName"
@keydown.escape="editingServerName = false"
/>
<button
class="px-3 py-1.5 bg-white/10 border border-white/20 rounded-lg text-white/70 hover:text-white hover:bg-white/15 transition-colors text-sm"
@click="saveServerName"
>Save</button>
<button
class="px-3 py-1.5 text-white/50 hover:text-white/70 transition-colors text-sm"
@click="editingServerName = false"
>Cancel</button>
</div>
<div v-else class="flex items-center gap-2 group cursor-pointer" @click="startEditServerName">
<p class="text-lg font-semibold text-white/95">{{ serverName }}</p>
<svg class="w-4 h-4 text-white/30 group-hover:text-white/60 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
</svg>
</div>
</div>
<!-- Version Card -->
@@ -572,51 +596,6 @@
</div>
</div>
<!-- Tor Services Section -->
<div class="glass-card px-6 py-6 mb-6">
<div class="flex items-center justify-between mb-4">
<div>
<h2 class="text-xl font-semibold text-white/96">Tor Services</h2>
<p class="text-sm text-white/60 mt-1">Manage hidden service addresses for your node and apps</p>
</div>
<button @click="loadTorServices" class="glass-button px-4 py-2 rounded-lg text-sm flex items-center gap-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
Refresh
</button>
</div>
<div v-if="torLoading" class="text-sm text-white/40 py-4 text-center">Loading Tor services...</div>
<div v-else-if="torServices.length === 0" class="text-sm text-white/40 py-4 text-center">No Tor services configured</div>
<div v-else class="space-y-2">
<div v-for="svc in torServices" :key="svc.name" class="bg-black/20 rounded-xl border border-white/10 p-3 flex items-center justify-between gap-3">
<div class="flex-1 min-w-0">
<p class="text-white text-sm font-medium">{{ svc.name }}</p>
<p v-if="svc.onion_address" class="text-amber-300/80 text-xs font-mono truncate">{{ svc.onion_address }}</p>
<p v-else class="text-white/30 text-xs">No .onion address</p>
</div>
<div class="flex items-center gap-2 shrink-0">
<button
v-if="svc.name === 'archipelago'"
@click="rotateNodeAddress"
:disabled="torRotating"
class="glass-button px-3 py-1.5 rounded-lg text-xs"
>
{{ torRotating ? 'Rotating...' : 'Rotate' }}
</button>
<label class="tor-toggle-label">
<input
type="checkbox"
:checked="svc.enabled"
@change="toggleTorApp(svc.name, !svc.enabled)"
class="tor-toggle-input"
/>
<span class="tor-toggle-slider"></span>
</label>
</div>
</div>
</div>
</div>
<!-- Webhook Notifications Section -->
<div class="glass-card px-6 py-6 mb-6">
@@ -836,7 +815,7 @@
</template>
<script setup lang="ts">
import { computed, ref, onMounted } from 'vue'
import { computed, ref, onMounted, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '../stores/app'
@@ -887,6 +866,30 @@ const interfaceModes = computed<{ id: UIMode; label: string; description: string
])
const serverName = computed(() => store.serverName)
const editingServerName = ref(false)
const serverNameDraft = ref('')
const serverNameInput = ref<HTMLInputElement | null>(null)
function startEditServerName() {
serverNameDraft.value = serverName.value
editingServerName.value = true
nextTick(() => serverNameInput.value?.select())
}
async function saveServerName() {
const name = serverNameDraft.value.trim()
if (!name || name === serverName.value) {
editingServerName.value = false
return
}
try {
await rpcClient.call({ method: 'server.set-name', params: { name } })
} catch (e) {
console.error('Failed to rename server:', e)
}
editingServerName.value = false
}
const version = computed(() => store.serverInfo?.version || '0.0.0')
const serverTorAddressFromStore = computed(() => store.serverInfo?.['tor-address'] || null)
const torAddressFromRpc = ref<string | null>(null)
@@ -1166,7 +1169,6 @@ onMounted(async () => {
checkClaudeStatus()
loadTotpStatus()
loadBackups()
loadTorServices()
loadWebhookConfig()
if (!serverTorAddressFromStore.value) {
try {
@@ -1204,16 +1206,6 @@ const restoringBackup = ref(false)
const verifyingBackupId = ref<string | null>(null)
const deletingBackupId = ref<string | null>(null)
// Tor services state
interface TorServiceInfo {
name: string
local_port: number
onion_address: string | null
enabled: boolean
}
const torServices = ref<TorServiceInfo[]>([])
const torLoading = ref(false)
const torRotating = ref(false)
const backupStatusMsg = ref('')
const backupStatusType = ref<'success' | 'error'>('success')
@@ -1230,43 +1222,6 @@ function showBackupStatus(msg: string, type: 'success' | 'error') {
setTimeout(() => { backupStatusMsg.value = '' }, 5000)
}
async function loadTorServices() {
torLoading.value = true
try {
const res = await rpcClient.torListServices()
torServices.value = res.services || []
} catch {
torServices.value = []
} finally {
torLoading.value = false
}
}
async function toggleTorApp(appId: string, enabled: boolean) {
try {
const res = await rpcClient.torToggleApp(appId, enabled)
if (res.changed) {
await loadTorServices()
}
} catch (e) {
if (import.meta.env.DEV) console.warn('Failed to toggle Tor app:', e)
}
}
async function rotateNodeAddress() {
if (torRotating.value) return
if (!confirm('This will generate a new .onion address. The old address will work for 24 hours during transition. Federated peers will be notified automatically.')) return
torRotating.value = true
try {
await rpcClient.torRotateService('archipelago')
await loadTorServices()
} catch (e) {
if (import.meta.env.DEV) console.warn('Failed to rotate Tor address:', e)
} finally {
torRotating.value = false
}
}
async function loadBackups() {
loadingBackups.value = true
try {