feat(openwrt): add TollGate provision button and direct-download fallback

- OpenWrtGateway.vue: add "Install TollGate" button when not installed;
  tracks connected credentials for reuse in the provision call
- install.rs: fall back to wget download from GitHub releases when the
  package is not in any opkg feed (mips_24kc and other arches supported)
- openwrt.rs: provision-tollgate now falls back to saved router_config
  for credentials, matching the behaviour of get-status

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 17:12:57 +00:00
co-authored by Claude Sonnet 4.6
parent 6c534715ec
commit f054766a58
3 changed files with 99 additions and 12 deletions
+47 -3
View File
@@ -48,6 +48,13 @@ const sshPassword = ref('')
const showConnectForm = ref(false)
const connecting = ref(false)
// Credentials used for the last successful connection (reused for provisioning)
const connectedParams = ref<Record<string, string> | null>(null)
const provisioning = ref(false)
const provisionError = ref('')
const provisionSuccess = ref(false)
async function load(params?: Record<string, string>) {
loading.value = true
error.value = ''
@@ -58,6 +65,7 @@ async function load(params?: Record<string, string>) {
timeout: 30000,
})
showConnectForm.value = false
if (params) connectedParams.value = params
} catch (e) {
const msg = e instanceof Error ? e.message : String(e)
if (msg.includes('No router configured')) {
@@ -81,6 +89,28 @@ async function connect() {
}
}
async function provisionTollgate() {
provisioning.value = true
provisionError.value = ''
provisionSuccess.value = false
try {
const params: Record<string, unknown> = {
// Use explicitly connected creds if available, otherwise fall back to
// host from the loaded status (backend will use saved router_config).
host: connectedParams.value?.host ?? status.value?.host,
ssh_user: connectedParams.value?.ssh_user ?? sshUser.value,
ssh_password: connectedParams.value?.ssh_password ?? sshPassword.value,
}
await rpcClient.call({ method: 'openwrt.provision-tollgate', params, timeout: 180000 })
provisionSuccess.value = true
await load(connectedParams.value ?? undefined)
} catch (e) {
provisionError.value = e instanceof Error ? e.message : String(e)
} finally {
provisioning.value = false
}
}
function formatUptime(secs: number): string {
const d = Math.floor(secs / 86400)
const h = Math.floor((secs % 86400) / 3600)
@@ -229,9 +259,23 @@ onMounted(() => load())
<div class="rounded-xl border border-white/10 bg-white/5 p-5 mb-4">
<h2 class="text-sm font-semibold text-white/80 mb-4">TollGate</h2>
<div v-if="!status.tollgate.installed" class="flex items-center gap-3">
<span class="w-2 h-2 rounded-full bg-white/20 inline-block"></span>
<span class="text-sm text-white/50">Not installed</span>
<div v-if="!status.tollgate.installed">
<div class="flex items-center gap-3 mb-4">
<span class="w-2 h-2 rounded-full bg-white/20 inline-block"></span>
<span class="text-sm text-white/50">Not installed</span>
</div>
<button
:disabled="provisioning"
class="w-full py-2 rounded-lg text-sm font-medium transition-colors"
:class="provisioning
? 'bg-white/5 text-white/30 cursor-not-allowed'
: 'bg-blue-600 hover:bg-blue-500 text-white'"
@click="provisionTollgate"
>
{{ provisioning ? 'Installing TollGate… (may take a few minutes)' : 'Install TollGate' }}
</button>
<p v-if="provisionError" class="mt-2 text-xs text-red-400">{{ provisionError }}</p>
<p v-if="provisionSuccess && !provisioning" class="mt-2 text-xs text-green-400">TollGate provisioned successfully.</p>
</div>
<template v-else>