refactor: split Marketplace, Server, Home, AppDetails views; minor frontend quality fixes
- F29-F32: Split 4 views (Marketplace 1293→505, Server 1132→486, Home 1059→394, AppDetails 1036→386) - F20: Add aria-current="page" to Dashboard nav links - F21: Add 150ms search debounce in Marketplace and Apps views - F22: Reduce backdrop-filter blur to 8px on mobile for GPU performance - F23: Track and clear WebSocket connect check interval in all paths Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
afd7405b1a
commit
69e25410b0
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div class="glass-card p-6 mb-6">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<!-- Service Status -->
|
||||
<div data-controller-container tabindex="0" class="flex flex-col gap-3 p-4 bg-white/5 rounded-lg min-w-0">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<div class="relative shrink-0">
|
||||
<div class="w-3 h-3 rounded-full" :class="servicesRunning ? 'bg-green-400' : 'bg-red-400'"></div>
|
||||
<div v-if="servicesRunning" class="absolute inset-0 w-3 h-3 rounded-full bg-green-400 animate-ping opacity-75"></div>
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-medium text-white">Services</p>
|
||||
<p class="text-xs text-white/60">{{ servicesRunning ? 'All Running' : 'Some Stopped' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@click="$emit('restartServices')"
|
||||
class="w-full min-h-[44px] glass-button rounded-lg text-sm font-medium text-white/90 hover:text-white transition-colors disabled:opacity-50 flex items-center justify-center"
|
||||
:disabled="restarting"
|
||||
>
|
||||
{{ restarting ? 'Restarting...' : 'Restart' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Tor Status -->
|
||||
<div data-controller-container tabindex="0" class="flex flex-col gap-3 p-4 bg-white/5 rounded-lg min-w-0">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<div class="relative shrink-0">
|
||||
<div class="w-3 h-3 rounded-full" :class="torStatusColor"></div>
|
||||
<div v-if="torStatusLabel === 'running'" class="absolute inset-0 w-3 h-3 rounded-full bg-green-400 animate-ping opacity-75"></div>
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-medium text-white">Tor Status</p>
|
||||
<p class="text-xs text-white/60 capitalize">{{ torStatusLabel }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@click="$emit('checkTor')"
|
||||
class="w-full min-h-[44px] glass-button rounded-lg text-sm font-medium text-white/90 hover:text-white transition-colors disabled:opacity-50 flex items-center justify-center"
|
||||
:disabled="checkingTor"
|
||||
>
|
||||
{{ checkingTor ? 'Checking...' : 'Check Tor' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Auto-Sync Toggle -->
|
||||
<div data-controller-container tabindex="0" class="flex flex-col gap-3 p-4 bg-white/5 rounded-lg min-w-0">
|
||||
<div class="flex items-center justify-between min-w-0">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<svg class="w-5 h-5 text-white/60 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z" />
|
||||
</svg>
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-medium text-white">Auto-Sync</p>
|
||||
<p class="text-xs text-white/60">{{ autoSyncEnabled ? 'Enabled' : 'Disabled' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<ToggleSwitch :model-value="autoSyncEnabled" @update:model-value="$emit('update:autoSyncEnabled', $event)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logs & Diagnostics -->
|
||||
<div data-controller-container tabindex="0" class="flex flex-col gap-3 p-4 bg-white/5 rounded-lg min-w-0">
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<svg class="w-5 h-5 text-white/60 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-medium text-white">Logs</p>
|
||||
<p class="text-xs text-white/60">{{ logCount > 0 ? `${logCount} new` : 'No new logs' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@click="$emit('viewLogs')"
|
||||
class="w-full min-h-[44px] glass-button rounded-lg text-sm font-medium text-white/90 hover:text-white transition-colors flex items-center justify-center"
|
||||
>
|
||||
View
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ToggleSwitch from '@/components/ToggleSwitch.vue'
|
||||
|
||||
defineProps<{
|
||||
servicesRunning: boolean
|
||||
restarting: boolean
|
||||
torStatusLabel: string
|
||||
torStatusColor: string
|
||||
checkingTor: boolean
|
||||
autoSyncEnabled: boolean
|
||||
logCount: number
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
restartServices: []
|
||||
checkTor: []
|
||||
'update:autoSyncEnabled': [value: boolean]
|
||||
viewLogs: []
|
||||
}>()
|
||||
</script>
|
||||
@@ -0,0 +1,260 @@
|
||||
<template>
|
||||
<!-- Add Tor Service Modal -->
|
||||
<Teleport to="body">
|
||||
<div v-if="showAddServiceModal" class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-md" @click.self="$emit('closeAddService')" @keydown.escape="$emit('closeAddService')">
|
||||
<div class="glass-card p-6 max-w-md w-full">
|
||||
<h3 class="text-lg font-semibold text-white mb-4">Add Tor Hidden Service</h3>
|
||||
|
||||
<!-- Tabs: Installed Apps | Manual -->
|
||||
<div class="flex gap-1 mb-4 border-b border-white/10">
|
||||
<button
|
||||
@click="localTab = 'apps'"
|
||||
class="px-4 py-2 text-sm font-medium rounded-t-lg transition-colors"
|
||||
:class="localTab === 'apps' ? 'bg-white/10 text-white' : 'text-white/60 hover:text-white/80 hover:bg-white/5'"
|
||||
>
|
||||
Installed Apps
|
||||
</button>
|
||||
<button
|
||||
@click="localTab = 'manual'"
|
||||
class="px-4 py-2 text-sm font-medium rounded-t-lg transition-colors"
|
||||
:class="localTab === 'manual' ? 'bg-white/10 text-white' : 'text-white/60 hover:text-white/80 hover:bg-white/5'"
|
||||
>
|
||||
Manual
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Installed Apps tab -->
|
||||
<div v-if="localTab === 'apps'">
|
||||
<p class="text-white/60 text-sm mb-3">Select an installed app to create a .onion address for it.</p>
|
||||
<div v-if="availableAppsForTor.length === 0" class="p-4 text-center text-white/40 text-sm">
|
||||
All installed apps already have Tor services.
|
||||
</div>
|
||||
<div v-else class="space-y-2 max-h-64 overflow-y-auto">
|
||||
<button
|
||||
v-for="app in availableAppsForTor"
|
||||
:key="app.id"
|
||||
@click="$emit('createServiceForApp', app.id)"
|
||||
:disabled="addingService"
|
||||
class="w-full flex items-center justify-between p-3 bg-white/5 rounded-lg hover:bg-white/10 transition-colors text-left"
|
||||
>
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-medium text-white">{{ app.title }}</p>
|
||||
<p class="text-xs text-white/40">{{ app.id }}</p>
|
||||
</div>
|
||||
<span class="text-xs text-orange-400 shrink-0">+ Add</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Manual tab -->
|
||||
<div v-if="localTab === 'manual'">
|
||||
<p class="text-white/60 text-sm mb-3">Create a .onion address for any local service.</p>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-1">Service Name</label>
|
||||
<input
|
||||
v-model="localName"
|
||||
class="w-full px-3 py-2 rounded-lg bg-white/10 text-white border border-white/20 focus:border-orange-500 focus:ring-1 focus:ring-orange-500"
|
||||
placeholder="my-app"
|
||||
maxlength="64"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-1">Local Port</label>
|
||||
<input
|
||||
v-model.number="localPort"
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
class="w-full px-3 py-2 rounded-lg bg-white/10 text-white border border-white/20 focus:border-orange-500 focus:ring-1 focus:ring-orange-500"
|
||||
placeholder="8080"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-3 mt-6">
|
||||
<button
|
||||
@click="$emit('createService', localName.trim(), localPort)"
|
||||
:disabled="!localName.trim() || !localPort || addingService"
|
||||
class="flex-1 px-4 py-2 rounded-lg bg-orange-500 text-white font-medium hover:bg-orange-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
||||
>
|
||||
{{ addingService ? 'Creating...' : 'Create Service' }}
|
||||
</button>
|
||||
<button
|
||||
@click="$emit('closeAddService')"
|
||||
class="px-4 py-2 rounded-lg bg-white/10 text-white font-medium hover:bg-white/20 transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="addServiceError" class="mt-3 text-sm text-red-400">{{ addServiceError }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
|
||||
<!-- WiFi Scan Modal -->
|
||||
<div v-if="showWifiModal" class="fixed inset-0 bg-black/10 backdrop-blur-md z-50 flex items-center justify-center p-4" @click.self="$emit('closeWifi')">
|
||||
<div class="glass-card p-6 w-full max-w-md">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-white">WiFi Networks</h3>
|
||||
<button @click="$emit('closeWifi')" class="text-white/40 hover:text-white 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>
|
||||
<template v-if="wifiScanning">
|
||||
<div class="space-y-3">
|
||||
<div v-for="i in 4" :key="i" class="p-3 bg-white/5 rounded-lg animate-pulse h-12"></div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="wifiNetworks.length > 0">
|
||||
<div class="space-y-2 max-h-72 overflow-y-auto">
|
||||
<button
|
||||
v-for="net in wifiNetworks"
|
||||
:key="net.ssid"
|
||||
class="w-full flex items-center justify-between p-3 bg-white/5 rounded-lg hover:bg-white/10 transition-colors text-left"
|
||||
@click="$emit('selectWifi', net.ssid)"
|
||||
>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-white">{{ net.ssid }}</p>
|
||||
<p class="text-xs text-white/50">{{ net.security || 'Open' }}</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex gap-0.5">
|
||||
<div v-for="bar in 4" :key="bar" class="w-1 rounded-full" :class="bar <= Math.ceil(net.signal / 25) ? 'bg-white/80' : 'bg-white/20'" :style="{ height: (bar * 3 + 4) + 'px' }"></div>
|
||||
</div>
|
||||
<span class="text-xs text-white/50">{{ net.signal }}%</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p class="text-sm text-white/50 text-center py-8">No networks found</p>
|
||||
</template>
|
||||
|
||||
<!-- WiFi password prompt -->
|
||||
<div v-if="wifiConnecting" class="mt-4 pt-4 border-t border-white/10">
|
||||
<p class="text-sm text-white/80 mb-2">Connect to <span class="font-medium text-white">{{ wifiSelectedSsid }}</span></p>
|
||||
<input
|
||||
v-model="localWifiPassword"
|
||||
type="password"
|
||||
placeholder="WiFi password"
|
||||
class="w-full px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-white text-sm placeholder-white/30 focus:outline-none focus:border-white/30 mb-3"
|
||||
@keyup.enter="$emit('connectWifi', localWifiPassword)"
|
||||
/>
|
||||
<p v-if="wifiError" class="text-sm text-red-400 mb-3">{{ wifiError }}</p>
|
||||
<div class="flex gap-2">
|
||||
<button @click="$emit('cancelWifiConnect')" :disabled="wifiSubmitting" class="flex-1 px-3 py-2 glass-button rounded-lg text-sm">Cancel</button>
|
||||
<button @click="$emit('connectWifi', localWifiPassword)" class="flex-1 px-3 py-2 glass-button rounded-lg text-sm font-medium disabled:opacity-50 disabled:cursor-not-allowed" :disabled="!localWifiPassword || wifiSubmitting">{{ wifiSubmitting ? 'Connecting...' : 'Connect' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DNS Configuration Modal -->
|
||||
<div v-if="showDnsModal" class="fixed inset-0 bg-black/10 backdrop-blur-md z-50 flex items-center justify-center p-4" @click.self="$emit('closeDns')">
|
||||
<div class="glass-card p-6 w-full max-w-md">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-white">DNS Configuration</h3>
|
||||
<button @click="$emit('closeDns')" class="text-white/40 hover:text-white 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">Choose a DNS provider. Providers with DoH encrypt your DNS queries.</p>
|
||||
|
||||
<div class="space-y-2 mb-4">
|
||||
<button
|
||||
v-for="opt in dnsProviderOptions"
|
||||
:key="opt.value"
|
||||
class="w-full flex items-center justify-between p-3 rounded-lg transition-colors text-left"
|
||||
:class="dnsSelectedProvider === opt.value ? 'bg-white/15 border border-white/20' : 'bg-white/5 border border-transparent hover:bg-white/10'"
|
||||
@click="$emit('selectDnsProvider', opt.value)"
|
||||
>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-white">{{ opt.label }}</p>
|
||||
<p class="text-xs text-white/50">{{ opt.description }}</p>
|
||||
</div>
|
||||
<span v-if="opt.doh" class="text-xs px-2 py-0.5 rounded-full bg-green-400/20 text-green-400">DoH</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Custom servers input -->
|
||||
<div v-if="dnsSelectedProvider === 'custom'" class="mb-4">
|
||||
<label class="block text-sm text-white/70 mb-1">DNS Servers (comma-separated)</label>
|
||||
<input
|
||||
v-model="localCustomServers"
|
||||
type="text"
|
||||
placeholder="1.1.1.1, 8.8.8.8"
|
||||
class="w-full px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-white text-sm placeholder-white/30 focus:outline-none focus:border-white/30"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Current servers info -->
|
||||
<div v-if="dnsServers.length > 0" class="mb-4 p-3 bg-white/5 rounded-lg">
|
||||
<p class="text-xs text-white/50 mb-1">Current resolv.conf servers</p>
|
||||
<p class="text-sm text-white/80">{{ dnsServers.join(', ') }}</p>
|
||||
</div>
|
||||
|
||||
<p v-if="dnsError" class="text-sm text-red-400 mb-3">{{ dnsError }}</p>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button @click="$emit('closeDns')" class="flex-1 px-3 py-2 glass-button rounded-lg text-sm">Cancel</button>
|
||||
<button
|
||||
@click="$emit('applyDns', localCustomServers)"
|
||||
class="flex-1 px-3 py-2 glass-button rounded-lg text-sm font-medium"
|
||||
:disabled="dnsApplying || (dnsSelectedProvider === 'custom' && !localCustomServers.trim())"
|
||||
>
|
||||
{{ dnsApplying ? 'Applying...' : 'Apply' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps<{
|
||||
showAddServiceModal: boolean
|
||||
showWifiModal: boolean
|
||||
showDnsModal: boolean
|
||||
availableAppsForTor: { id: string; title: string }[]
|
||||
addingService: boolean
|
||||
addServiceError: string
|
||||
wifiScanning: boolean
|
||||
wifiNetworks: { ssid: string; signal: number; security: string }[]
|
||||
wifiConnecting: boolean
|
||||
wifiSubmitting: boolean
|
||||
wifiSelectedSsid: string
|
||||
wifiError: string
|
||||
dnsSelectedProvider: string
|
||||
dnsServers: string[]
|
||||
dnsApplying: boolean
|
||||
dnsError: string
|
||||
dnsProviderOptions: { value: string; label: string; description: string; doh: boolean }[]
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
closeAddService: []
|
||||
createServiceForApp: [appId: string]
|
||||
createService: [name: string, port: number | null]
|
||||
closeWifi: []
|
||||
selectWifi: [ssid: string]
|
||||
connectWifi: [password: string]
|
||||
cancelWifiConnect: []
|
||||
closeDns: []
|
||||
selectDnsProvider: [provider: string]
|
||||
applyDns: [customServers: string]
|
||||
}>()
|
||||
|
||||
const localTab = ref<'apps' | 'manual'>('apps')
|
||||
const localName = ref('')
|
||||
const localPort = ref<number | null>(null)
|
||||
const localWifiPassword = ref('')
|
||||
const localCustomServers = ref('')
|
||||
</script>
|
||||
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div class="glass-card p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="relative shrink-0">
|
||||
<div class="w-3 h-3 rounded-full" :class="torDaemonRunning ? 'bg-green-400' : 'bg-red-400'"></div>
|
||||
<div v-if="torDaemonRunning" class="absolute inset-0 w-3 h-3 rounded-full bg-green-400 animate-ping opacity-75"></div>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button @click="$emit('restartTor')" :disabled="torRestarting" class="glass-button px-3 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>
|
||||
{{ torRestarting ? 'Restarting...' : 'Restart Tor' }}
|
||||
</button>
|
||||
<button @click="$emit('showAddService')" class="glass-button px-3 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="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
Add Service
|
||||
</button>
|
||||
</div>
|
||||
</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">
|
||||
<div class="flex items-center gap-2">
|
||||
<p class="text-white text-sm font-medium">{{ svc.name }}</p>
|
||||
<span class="text-white/30 text-xs">:{{ svc.local_port }}</span>
|
||||
<span v-if="svc.protocol" class="text-xs px-1.5 py-0.5 rounded bg-blue-500/20 text-blue-400">protocol</span>
|
||||
<span v-else-if="!svc.unauthenticated" class="text-xs px-1.5 py-0.5 rounded bg-green-500/20 text-green-400">auth</span>
|
||||
<span v-else class="text-xs px-1.5 py-0.5 rounded bg-amber-500/20 text-amber-400">open</span>
|
||||
</div>
|
||||
<p v-if="svc.onion_address" class="text-amber-300/80 text-xs font-mono truncate cursor-pointer" :title="svc.onion_address" @click="$emit('copyAddress', svc.onion_address)">{{ svc.onion_address }}</p>
|
||||
<p v-else-if="svc.enabled" class="text-white/30 text-xs">Waiting for .onion address...</p>
|
||||
<p v-else class="text-white/30 text-xs">Disabled</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<button
|
||||
v-if="svc.onion_address && svc.enabled"
|
||||
@click="$emit('rotateService', svc.name)"
|
||||
:disabled="torRotating === svc.name"
|
||||
class="glass-button px-3 py-1.5 rounded-lg text-xs"
|
||||
>
|
||||
{{ torRotating === svc.name ? 'Rotating...' : 'Rotate' }}
|
||||
</button>
|
||||
<button
|
||||
v-if="svc.name !== 'archipelago'"
|
||||
@click="$emit('deleteService', svc.name)"
|
||||
:disabled="torDeleting === svc.name"
|
||||
class="glass-button px-2 py-1.5 rounded-lg text-xs text-red-400 hover:text-red-300"
|
||||
:title="'Delete ' + svc.name + ' hidden service'"
|
||||
>
|
||||
<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="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
<ToggleSwitch :model-value="svc.enabled" @update:model-value="$emit('toggleApp', svc.name, $event)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ToggleSwitch from '@/components/ToggleSwitch.vue'
|
||||
|
||||
export interface TorServiceInfo {
|
||||
name: string
|
||||
local_port: number
|
||||
onion_address: string | null
|
||||
enabled: boolean
|
||||
unauthenticated: boolean
|
||||
protocol: boolean
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
torServices: TorServiceInfo[]
|
||||
torServicesLoading: boolean
|
||||
torDaemonRunning: boolean
|
||||
torRestarting: boolean
|
||||
torRotating: string | false
|
||||
torDeleting: string | false
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
restartTor: []
|
||||
showAddService: []
|
||||
copyAddress: [address: string]
|
||||
rotateService: [name: string]
|
||||
deleteService: [name: string]
|
||||
toggleApp: [name: string, enabled: boolean]
|
||||
}>()
|
||||
</script>
|
||||
Reference in New Issue
Block a user