fix(openwrt): TollGate apk-native install for OpenWrt 25.x
- WISP wizard: step-by-step flow for WiFi, DHCP, masquerade config - WAN status: expose lan_ip, dhcp_start/limit, masq, sta_state, wifi_log - wifi_scan: detect CCMP as WPA2 (psk2) so association succeeds - opkg: PkgManager enum — detect apk-native mode when opkg not in repos - tollgate: apk-native install path using manual ipk extraction - arch detection: read DISTRIB_ARCH from /etc/openwrt_release; normalise bare mipsel/mips from uname -m to mipsel_24kc/mips_24kc - install_ipk: install binutils via apk when ar not in BusyBox - install_ipk: wget --no-check-certificate for routers without CA bundle - install_ipk: ar fallback to tar -xzf for non-standard ipk formats - install_ipk: 5MB overlay space check with clear user-facing error - middleware: allow "Not enough flash/space" errors through sanitizer Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,11 @@ interface WanStatus {
|
||||
sta_iface: string
|
||||
sta_state: string
|
||||
wifi_log: string
|
||||
lan_ip: string
|
||||
lan_netmask: string
|
||||
dhcp_start: string
|
||||
dhcp_limit: string
|
||||
masq: boolean
|
||||
}
|
||||
|
||||
interface RouterStatus {
|
||||
@@ -76,13 +81,18 @@ const provisionError = ref('')
|
||||
const provisionSuccess = ref(false)
|
||||
|
||||
// WAN setup flow
|
||||
type WanStep = 'idle' | 'scan' | 'scanning' | 'list' | 'password' | 'connecting' | 'done'
|
||||
type WanStep = 'idle' | 'scan' | 'scanning' | 'list' | 'password' | 'dhcp' | 'connecting' | 'done'
|
||||
const wanStep = ref<WanStep>('idle')
|
||||
const scannedNetworks = ref<ScannedNetwork[]>([])
|
||||
const selectedNetwork = ref<ScannedNetwork | null>(null)
|
||||
const wanPassword = ref('')
|
||||
const wanError = ref('')
|
||||
|
||||
// DHCP/masq settings (step 3 of wizard)
|
||||
const dhcpStart = ref(100)
|
||||
const dhcpLimit = ref(150)
|
||||
const masqEnabled = ref(true)
|
||||
|
||||
async function load(params?: Record<string, string>) {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
@@ -143,6 +153,9 @@ function startWanSetup() {
|
||||
scannedNetworks.value = []
|
||||
selectedNetwork.value = null
|
||||
wanPassword.value = ''
|
||||
dhcpStart.value = Number(status.value?.wan?.dhcp_start) || 100
|
||||
dhcpLimit.value = Number(status.value?.wan?.dhcp_limit) || 150
|
||||
masqEnabled.value = true
|
||||
}
|
||||
|
||||
async function scanWifi() {
|
||||
@@ -186,6 +199,9 @@ async function configureWan() {
|
||||
ssid: selectedNetwork.value.ssid,
|
||||
password: wanPassword.value,
|
||||
encryption: selectedNetwork.value.encryption,
|
||||
dhcp_start: dhcpStart.value,
|
||||
dhcp_limit: dhcpLimit.value,
|
||||
masq: masqEnabled.value,
|
||||
}
|
||||
await rpcClient.call({ method: 'openwrt.configure-wan', params, timeout: 30000 })
|
||||
wanStep.value = 'done'
|
||||
@@ -196,7 +212,7 @@ async function configureWan() {
|
||||
}, 8000)
|
||||
} catch (e) {
|
||||
wanError.value = e instanceof Error ? e.message : String(e)
|
||||
wanStep.value = 'password'
|
||||
wanStep.value = 'dhcp'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,6 +391,25 @@ onMounted(() => load())
|
||||
Not configured — router has no internet access.
|
||||
</div>
|
||||
|
||||
<!-- Quick summary of DHCP + masq when connected -->
|
||||
<dl v-if="status.wan?.configured && status.wan.internet"
|
||||
class="grid grid-cols-3 gap-x-4 gap-y-1 text-xs text-white/40 mb-3">
|
||||
<div>
|
||||
<dt class="text-white/25">LAN</dt>
|
||||
<dd class="text-white/60 font-mono">{{ status.wan.lan_ip }}/24</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-white/25">DHCP</dt>
|
||||
<dd class="text-white/60 font-mono">.{{ status.wan.dhcp_start }}+{{ status.wan.dhcp_limit }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-white/25">NAT</dt>
|
||||
<dd :class="status.wan.masq ? 'text-green-400' : 'text-red-400'">
|
||||
{{ status.wan.masq ? 'on' : 'off' }}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
<!-- Diagnostics (shown when not connected) -->
|
||||
<dl v-if="status.wan?.configured && !status.wan.internet"
|
||||
class="grid grid-cols-2 gap-x-4 gap-y-2 text-xs mt-1 mb-3 border-t border-white/10 pt-3">
|
||||
@@ -478,22 +513,80 @@ onMounted(() => load())
|
||||
type="password"
|
||||
placeholder="WiFi password"
|
||||
class="w-full px-4 py-3 mb-3 bg-transparent border border-white/20 rounded-lg text-white placeholder-white/30 focus:outline-none focus:border-white/40 transition-colors"
|
||||
@keyup.enter="configureWan"
|
||||
@keyup.enter="wanStep = 'dhcp'"
|
||||
/>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="glass-button glass-button-success flex-1 text-sm font-medium"
|
||||
:disabled="selectedNetwork?.encryption !== 'none' && !wanPassword"
|
||||
:class="selectedNetwork?.encryption !== 'none' && !wanPassword ? 'opacity-40 cursor-not-allowed' : ''"
|
||||
@click="configureWan"
|
||||
@click="wanStep = 'dhcp'"
|
||||
>
|
||||
Connect
|
||||
Next →
|
||||
</button>
|
||||
<button class="text-xs text-white/40 hover:text-white px-3 py-2" @click="wanStep = 'list'">←</button>
|
||||
</div>
|
||||
<p v-if="wanError" class="mt-2 text-xs text-red-400">{{ wanError }}</p>
|
||||
</template>
|
||||
|
||||
<!-- Step: DHCP + masquerade config -->
|
||||
<template v-else-if="wanStep === 'dhcp'">
|
||||
<p class="text-xs text-white/50 mb-4">
|
||||
Configure how this router hands out addresses to WiFi clients.
|
||||
</p>
|
||||
|
||||
<!-- LAN info -->
|
||||
<div class="mb-4 text-xs font-mono bg-black/20 rounded-lg px-3 py-2 text-white/50">
|
||||
<span class="text-white/30">LAN: </span>{{ status?.wan?.lan_ip || '192.168.1.1' }}/24
|
||||
</div>
|
||||
|
||||
<!-- DHCP range -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs text-white/40 mb-2">DHCP range for clients</label>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span class="text-white/40 font-mono text-xs">{{ status?.wan?.lan_ip?.split('.').slice(0,3).join('.') || '192.168.1' }}.</span>
|
||||
<input
|
||||
v-model.number="dhcpStart"
|
||||
type="number"
|
||||
min="2" max="250"
|
||||
class="w-20 px-2 py-1.5 bg-transparent border border-white/20 rounded text-white text-sm text-center focus:outline-none focus:border-white/40"
|
||||
/>
|
||||
<span class="text-white/30">–</span>
|
||||
<span class="text-white/60 font-mono text-xs">{{ Math.min(254, dhcpStart + dhcpLimit - 1) }}</span>
|
||||
</div>
|
||||
<p class="text-xs text-white/30 mt-1">{{ dhcpLimit }} addresses</p>
|
||||
</div>
|
||||
|
||||
<!-- Masquerade -->
|
||||
<div class="flex items-center justify-between mb-4 py-3 border-t border-white/10">
|
||||
<div>
|
||||
<div class="text-sm text-white">Enable NAT masquerade</div>
|
||||
<div class="text-xs text-white/40">Routes LAN traffic through the WiFi uplink</div>
|
||||
</div>
|
||||
<button
|
||||
class="relative w-11 h-6 rounded-full transition-colors flex-shrink-0"
|
||||
:class="masqEnabled ? 'bg-green-500/60' : 'bg-white/15'"
|
||||
@click="masqEnabled = !masqEnabled"
|
||||
>
|
||||
<span
|
||||
class="absolute top-0.5 w-5 h-5 rounded-full bg-white transition-transform"
|
||||
:class="masqEnabled ? 'translate-x-5' : 'translate-x-0.5'"
|
||||
></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p v-if="wanError" class="mb-3 text-xs text-red-400">{{ wanError }}</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="glass-button glass-button-success flex-1 text-sm font-medium"
|
||||
@click="configureWan"
|
||||
>
|
||||
Apply Settings
|
||||
</button>
|
||||
<button class="text-xs text-white/40 hover:text-white px-3 py-2" @click="wanStep = 'password'">←</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Step: applying -->
|
||||
<template v-else-if="wanStep === 'connecting'">
|
||||
<div class="flex items-center gap-3 py-2">
|
||||
|
||||
Reference in New Issue
Block a user