archy/neode-ui/src/views/settings/TransportPrefsCard.vue
Dorian 2f327183eb fix(ui,ops): TransportPrefsCard import path + fleet unpair script
- TransportPrefsCard.vue: import from '@/api/rpc-client' (not
  '@/api/rpc') so vue-tsc resolves the module during build.
- scripts/fleet-fips-unpair.sh: companion to the fleet-pair script —
  rewrites each node's fips.yaml to anchor-only (fips.v0l.io) so we
  can prove the general-case deployment works without the LAN
  fast-path. Prints per-node peer counts + DHT AAAA resolution for
  every cross-node pair after the change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 02:08:32 -04:00

122 lines
4.2 KiB
Vue

<template>
<div class="glass-card p-6 transition-all">
<div class="flex items-center gap-3 mb-2">
<svg class="w-6 h-6 text-cyan-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
<h2 class="text-xl font-semibold text-white">Transport Preferences</h2>
</div>
<p class="text-white/60 text-sm mb-5">
Pick how each service reaches federated peers. FIPS is the overlay mesh
(fast, authenticated). Tor is the anonymous hidden-service fallback.
<span class="text-white/40">Auto = FIPS first, Tor on failure.</span>
</p>
<div v-if="loading" class="text-white/50 text-sm">Loading</div>
<div v-else-if="error" class="text-red-400 text-sm">{{ error }}</div>
<div v-else class="space-y-3">
<div
v-for="svc in services"
:key="svc.key"
class="flex items-center justify-between gap-4 p-3 bg-white/5 rounded-lg"
>
<div class="min-w-0">
<div class="text-sm font-medium text-white">{{ svc.label }}</div>
<div class="text-xs text-white/50 truncate">{{ svc.hint }}</div>
</div>
<div class="flex items-center gap-1 shrink-0">
<button
v-for="opt in options"
:key="opt.value"
type="button"
class="px-3 py-1.5 rounded text-xs font-medium transition-colors"
:class="classesFor(svc.key, opt.value)"
:disabled="saving === svc.key"
@click="setPref(svc.key, opt.value)"
>
{{ opt.label }}
</button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { rpcClient } from '@/api/rpc-client'
type Pref = 'auto' | 'fips' | 'tor'
type Service = 'federation' | 'peers' | 'peer_files' | 'messaging' | 'mesh_file_sharing'
interface Preferences {
federation: Pref
peers: Pref
peer_files: Pref
messaging: Pref
mesh_file_sharing: Pref
}
const services: { key: Service; label: string; hint: string }[] = [
{ key: 'federation', label: 'Federation', hint: 'State sync, invites, peer notifications' },
{ key: 'peers', label: 'Peer Linking', hint: 'Address + DID rotation broadcasts' },
{ key: 'peer_files', label: 'Peer Files', hint: 'Content catalog download / browse' },
{ key: 'messaging', label: 'Archipelago Messaging', hint: 'Node-to-node chat + mesh bridge' },
{ key: 'mesh_file_sharing', label: 'Mesh File Sharing', hint: 'Blob fetches for shared mesh content' },
]
const options: { value: Pref; label: string }[] = [
{ value: 'auto', label: 'Auto' },
{ value: 'fips', label: 'FIPS' },
{ value: 'tor', label: 'Tor' },
]
const prefs = ref<Preferences | null>(null)
const loading = ref(true)
const saving = ref<Service | null>(null)
const error = ref<string | null>(null)
async function load() {
loading.value = true
error.value = null
try {
prefs.value = await rpcClient.call<Preferences>({ method: 'transport.preferences' })
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to load transport preferences'
} finally {
loading.value = false
}
}
function classesFor(service: Service, value: Pref): string {
const active = prefs.value?.[service] === value
if (active) {
if (value === 'fips') return 'bg-cyan-500/30 text-cyan-200 ring-1 ring-cyan-400/50'
if (value === 'tor') return 'bg-purple-500/30 text-purple-200 ring-1 ring-purple-400/50'
return 'bg-white/20 text-white ring-1 ring-white/30'
}
return 'bg-white/5 text-white/60 hover:bg-white/10 hover:text-white/80'
}
async function setPref(service: Service, pref: Pref) {
if (!prefs.value) return
if (prefs.value[service] === pref) return
saving.value = service
const previous = prefs.value[service]
prefs.value[service] = pref
try {
prefs.value = await rpcClient.call<Preferences>({
method: 'transport.set-preference',
params: { service, pref },
})
} catch (e) {
if (prefs.value) prefs.value[service] = previous
error.value = e instanceof Error ? e.message : 'Failed to save preference'
} finally {
saving.value = null
}
}
onMounted(load)
</script>