frontend: polish app launch and release experience
This commit is contained in:
@@ -80,6 +80,13 @@
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div v-if="networkRefreshing" class="p-2 text-center text-white/45 text-xs flex items-center justify-center gap-2">
|
||||
<svg class="animate-spin h-3.5 w-3.5" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
Refreshing network...
|
||||
</div>
|
||||
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
||||
<div class="flex items-center gap-3">
|
||||
<svg class="w-5 h-5 text-white/60" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg>
|
||||
@@ -135,46 +142,14 @@
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<button disabled title="Coming Soon" class="mt-4 w-full min-h-[44px] glass-button rounded-lg text-sm font-medium opacity-50 cursor-not-allowed flex items-center justify-center">
|
||||
Manage Local Network
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Web3 Card -->
|
||||
<div data-controller-container tabindex="0" class="glass-card p-6 flex flex-col transition-all hover:-translate-y-1">
|
||||
<div class="flex items-start gap-4 mb-4 shrink-0">
|
||||
<div class="flex-shrink-0 w-12 h-12 rounded-lg bg-white/10 flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-white/80" 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">
|
||||
<h2 class="text-xl font-semibold text-white mb-2">Web3</h2>
|
||||
<p class="text-white/70 text-sm mb-4">Decentralized web hosting and services</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-3 flex-1 min-h-0">
|
||||
<div v-for="item in ['Hosted Websites', 'SSL Certificates', 'IPFS Storage', 'ENS Domains']" :key="item" class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
||||
<div class="flex items-center gap-3">
|
||||
<svg class="w-5 h-5 text-white/60" 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>
|
||||
<span class="text-white/80 text-sm">{{ item }}</span>
|
||||
</div>
|
||||
<span class="text-white/40 text-xs px-2 py-0.5 bg-white/5 rounded-full">Coming Soon</span>
|
||||
</div>
|
||||
</div>
|
||||
<button disabled title="Coming Soon" class="mt-4 w-full min-h-[44px] glass-button rounded-lg text-sm font-medium opacity-50 cursor-not-allowed flex items-center justify-center">
|
||||
Manage Web3 Services
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FIPS Mesh (full card) -->
|
||||
<div class="mb-8">
|
||||
<FipsNetworkCard />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 xl:grid-cols-2 gap-6 mb-6">
|
||||
<!-- VPN Card -->
|
||||
<div class="glass-card p-6 transition-all hover:-translate-y-1">
|
||||
<div class="glass-card p-6 flex flex-col transition-all hover:-translate-y-1">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex-shrink-0 w-10 h-10 rounded-lg bg-white/10 flex items-center justify-center">
|
||||
@@ -185,7 +160,7 @@
|
||||
<p class="text-xs text-white/50">Standalone WireGuard VPN</p>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="showAddDeviceModal = true; showingNewDevice = true" class="glass-button px-4 py-2 text-sm">Add Device</button>
|
||||
<button @click="showAddDeviceModal = true; showingNewDevice = true" class="responsive-card-actions-top glass-button px-4 py-2 text-sm">Add Device</button>
|
||||
</div>
|
||||
|
||||
<!-- WireGuard Status -->
|
||||
@@ -221,10 +196,14 @@
|
||||
</div>
|
||||
<div v-else class="text-xs text-white/30 py-2">No devices added yet</div>
|
||||
</div>
|
||||
|
||||
<button @click="showAddDeviceModal = true; showingNewDevice = true" class="responsive-card-actions-bottom mt-4 mobile-card-action glass-button rounded-lg text-sm font-medium">
|
||||
Add Device
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Network Interfaces (second column on desktop) -->
|
||||
<div data-controller-container tabindex="0" class="glass-card p-6 transition-all hover:-translate-y-1">
|
||||
<div data-controller-container tabindex="0" class="glass-card p-6 flex flex-col transition-all hover:-translate-y-1">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-white mb-1">Network Interfaces</h2>
|
||||
@@ -233,7 +212,7 @@
|
||||
<button
|
||||
v-if="wifiAvailable"
|
||||
@click="showWifiModal = true"
|
||||
class="px-3 py-1.5 glass-button rounded text-xs font-medium text-white/90 hover:text-white transition-colors"
|
||||
class="responsive-card-actions-top px-3 py-1.5 glass-button rounded text-xs font-medium text-white/90 hover:text-white transition-colors"
|
||||
>
|
||||
Scan WiFi
|
||||
</button>
|
||||
@@ -246,6 +225,13 @@
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="space-y-3">
|
||||
<div v-if="interfacesRefreshing" class="p-2 text-center text-white/45 text-xs flex items-center justify-center gap-2">
|
||||
<svg class="animate-spin h-3.5 w-3.5" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
Refreshing interfaces...
|
||||
</div>
|
||||
<div
|
||||
v-for="iface in physicalInterfaces"
|
||||
:key="iface.name"
|
||||
@@ -266,6 +252,14 @@
|
||||
<p v-if="physicalInterfaces.length === 0" class="text-sm text-white/50 text-center py-4">No physical interfaces detected</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<button
|
||||
v-if="wifiAvailable"
|
||||
@click="showWifiModal = true"
|
||||
class="responsive-card-actions-bottom mt-4 mobile-card-action glass-button rounded-lg text-sm font-medium text-white/90 hover:text-white transition-colors"
|
||||
>
|
||||
Scan WiFi
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div><!-- close VPN+Network 2-col grid -->
|
||||
@@ -413,6 +407,8 @@ const logCount = ref(0)
|
||||
|
||||
// Network data
|
||||
const networkLoading = ref(true)
|
||||
const networkRefreshing = ref(false)
|
||||
const networkHasLoaded = ref(false)
|
||||
const networkData = ref({
|
||||
wifiCount: 'N/A', wifiSsid: null as string | null, torConnected: false, forwardCount: 'N/A',
|
||||
vpnConnected: false, vpnProvider: '', vpnIp: '', wgIp: '', wgPubkey: '', vpnHostname: '', vpnPeers: 0,
|
||||
@@ -449,7 +445,9 @@ async function loadFipsSummary() {
|
||||
}
|
||||
|
||||
async function loadNetworkData() {
|
||||
networkLoading.value = true
|
||||
const initialLoad = !networkHasLoaded.value
|
||||
networkLoading.value = initialLoad
|
||||
networkRefreshing.value = !initialLoad
|
||||
try {
|
||||
const [diagRes, fwdRes, vpnRes, dnsRes] = await Promise.allSettled([
|
||||
rpcClient.call<{ wan_ip: string | null; nat_type: string; upnp_available: boolean; tor_connected: boolean; wifi_count?: number }>({ method: 'network.diagnostics' }),
|
||||
@@ -461,7 +459,11 @@ async function loadNetworkData() {
|
||||
if (fwdRes.status === 'fulfilled') { const c = fwdRes.value.forwards?.length ?? 0; networkData.value.forwardCount = `${c} rule${c !== 1 ? 's' : ''}` }
|
||||
if (vpnRes.status === 'fulfilled') { networkData.value.vpnConnected = vpnRes.value.connected; networkData.value.vpnProvider = vpnRes.value.provider ?? ''; networkData.value.vpnIp = (vpnRes.value.ip_address ?? '').replace(/\/\d+$/, ''); networkData.value.wgIp = vpnRes.value.wg_ip ?? ''; networkData.value.wgPubkey = (vpnRes.value as Record<string, unknown>).wg_pubkey as string ?? '' }
|
||||
if (dnsRes.status === 'fulfilled') { networkData.value.dnsProvider = dnsRes.value.provider; networkData.value.dnsServers = dnsRes.value.resolv_conf_servers ?? []; networkData.value.dnsDoH = dnsRes.value.doh_enabled }
|
||||
} catch { /* keep defaults */ } finally { networkLoading.value = false }
|
||||
} catch { /* keep existing/default values */ } finally {
|
||||
networkHasLoaded.value = true
|
||||
networkLoading.value = false
|
||||
networkRefreshing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// VPN peer management
|
||||
@@ -548,6 +550,8 @@ interface NetworkInterface { name: string; type: string; state: string; mac: str
|
||||
interface WifiNetwork { ssid: string; signal: number; security: string }
|
||||
|
||||
const interfacesLoading = ref(true)
|
||||
const interfacesRefreshing = ref(false)
|
||||
const interfacesHaveLoaded = ref(false)
|
||||
const allInterfaces = ref<NetworkInterface[]>([])
|
||||
const physicalInterfaces = computed(() => allInterfaces.value.filter(i => i.type === 'ethernet' || i.type === 'wifi'))
|
||||
const wifiAvailable = computed(() => allInterfaces.value.some(i => i.type === 'wifi'))
|
||||
@@ -599,8 +603,11 @@ async function applyDnsConfig(customServers: string) {
|
||||
}
|
||||
|
||||
async function loadInterfaces() {
|
||||
interfacesLoading.value = true
|
||||
try { const res = await rpcClient.call<{ interfaces: NetworkInterface[] }>({ method: 'network.list-interfaces' }); allInterfaces.value = res.interfaces } catch { allInterfaces.value = [] } finally { interfacesLoading.value = false }
|
||||
const initialLoad = !interfacesHaveLoaded.value
|
||||
const hadInterfaces = allInterfaces.value.length > 0
|
||||
interfacesLoading.value = initialLoad
|
||||
interfacesRefreshing.value = !initialLoad
|
||||
try { const res = await rpcClient.call<{ interfaces: NetworkInterface[] }>({ method: 'network.list-interfaces' }); allInterfaces.value = res.interfaces } catch { if (!hadInterfaces) allInterfaces.value = [] } finally { interfacesHaveLoaded.value = true; interfacesLoading.value = false; interfacesRefreshing.value = false }
|
||||
}
|
||||
|
||||
async function scanWifi() {
|
||||
@@ -670,9 +677,10 @@ const availableAppsForTor = computed(() => {
|
||||
})
|
||||
|
||||
async function loadTorServices() {
|
||||
const hadServices = torServices.value.length > 0
|
||||
torServicesLoading.value = true
|
||||
try { const res = await rpcClient.call<{ services: TorServiceInfo[]; tor_running: boolean }>({ method: 'tor.list-services' }); torServices.value = res.services || []; torDaemonRunning.value = res.tor_running ?? false }
|
||||
catch { torServices.value = []; torDaemonRunning.value = false } finally { torServicesLoading.value = false }
|
||||
catch { if (!hadServices) { torServices.value = []; torDaemonRunning.value = false } } finally { torServicesLoading.value = false }
|
||||
}
|
||||
|
||||
async function copyTorAddress(address: string) {
|
||||
@@ -753,4 +761,16 @@ async function checkTorStatus() {
|
||||
|
||||
const logsToast = ref('')
|
||||
function viewLogs() { logCount.value = 0; logsToast.value = 'Server logs are available via SSH: journalctl -u archipelago -f'; setTimeout(() => { logsToast.value = '' }, 6000) }
|
||||
|
||||
defineExpose({
|
||||
allInterfaces,
|
||||
interfacesRefreshing,
|
||||
loadInterfaces,
|
||||
loadNetworkData,
|
||||
loadTorServices,
|
||||
networkData,
|
||||
networkRefreshing,
|
||||
torServices,
|
||||
torServicesLoading,
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user