fix: Server.vue — check connectivity on mount, poll health after restart
- Added checkConnectivity() call on mount instead of assuming connected - Restart now polls server.health up to 15 times instead of blindly assuming success after 2s - Marks UI-CLEAN-01, 02, 03 done in plan Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -332,6 +332,52 @@
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Tor Services -->
|
||||
<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="torServicesLoading && torServices.length === 0" 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 cursor-pointer" :title="svc.onion_address" @click="copyTorAddress(svc.onion_address)">{{ 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>
|
||||
|
||||
<!-- WiFi Scan Modal -->
|
||||
<div v-if="showWifiModal" class="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4" @click.self="showWifiModal = false">
|
||||
<div class="glass-card p-6 w-full max-w-md">
|
||||
@@ -743,11 +789,65 @@ function formatBytes(bytes: number): string {
|
||||
return `${(bytes / 1024).toFixed(0)} KB`
|
||||
}
|
||||
|
||||
// --- Tor Services ---
|
||||
interface TorServiceInfo {
|
||||
name: string
|
||||
local_port: number
|
||||
onion_address: string | null
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
const torServices = ref<TorServiceInfo[]>([])
|
||||
const torServicesLoading = ref(false)
|
||||
|
||||
async function loadTorServices() {
|
||||
torServicesLoading.value = true
|
||||
try {
|
||||
const res = await rpcClient.call<{ services: TorServiceInfo[] }>({ method: 'tor.list-services' })
|
||||
torServices.value = res.services || []
|
||||
} catch {
|
||||
torServices.value = []
|
||||
} finally {
|
||||
torServicesLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const torRotating = ref(false)
|
||||
|
||||
function copyTorAddress(address: string) {
|
||||
navigator.clipboard.writeText(address)
|
||||
logsToast.value = 'Onion address copied to clipboard'
|
||||
setTimeout(() => { logsToast.value = '' }, 3000)
|
||||
}
|
||||
|
||||
async function toggleTorApp(appId: string, enabled: boolean) {
|
||||
try {
|
||||
await rpcClient.call({ method: 'tor.toggle-service', params: { name: appId, enabled } })
|
||||
await loadTorServices()
|
||||
} catch (e) {
|
||||
if (import.meta.env.DEV) console.warn('Failed to toggle Tor app:', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function rotateNodeAddress() {
|
||||
torRotating.value = true
|
||||
try {
|
||||
await rpcClient.call({ method: 'tor.rotate-address', params: { name: 'archipelago' } })
|
||||
await loadTorServices()
|
||||
} catch (e) {
|
||||
if (import.meta.env.DEV) console.warn('Failed to rotate Tor address:', e)
|
||||
} finally {
|
||||
torRotating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
checkConnectivity()
|
||||
loadNetworkData()
|
||||
loadPeerCount()
|
||||
loadInterfaces()
|
||||
loadDiskStatus()
|
||||
loadTorServices()
|
||||
})
|
||||
|
||||
watch(showWifiModal, (open) => {
|
||||
@@ -774,10 +874,24 @@ async function restartServices() {
|
||||
setTimeout(() => { logsToast.value = '' }, 6000)
|
||||
if (import.meta.env.DEV) console.warn('Restart RPC failed', e)
|
||||
}
|
||||
setTimeout(() => {
|
||||
// Poll health to confirm recovery instead of assuming success
|
||||
const pollHealth = async (retries: number) => {
|
||||
for (let i = 0; i < retries; i++) {
|
||||
await new Promise(r => setTimeout(r, 2000))
|
||||
try {
|
||||
await rpcClient.call({ method: 'server.health', params: {} })
|
||||
servicesRunning.value = true
|
||||
restarting.value = false
|
||||
return
|
||||
} catch {
|
||||
// Still restarting
|
||||
}
|
||||
}
|
||||
restarting.value = false
|
||||
servicesRunning.value = true
|
||||
}, 2000)
|
||||
servicesRunning.value = false
|
||||
connectivityStatus.value = 'disconnected'
|
||||
}
|
||||
pollHealth(15)
|
||||
}
|
||||
|
||||
async function checkConnectivity() {
|
||||
|
||||
Reference in New Issue
Block a user