feat(ops): auto-sync CPE MAC from RADIUS into OPS (no manual step)
MAC updates now flow into OPS automatically. On any signal read (wireless panel OR the assistant's check_signal), the hub's airosSignal resolves the Service Equipment record and, if the RADIUS-authoritative MAC (radacct.callingstationid, auto-updated on WPA2 re-auth) differs from the stored mac_address, it updates Service Equipment server-side — idempotent, fill/correct, no click. UI shows a green "MAC synchronisée automatiquement depuis RADIUS (était …)" note and reflects it in the Identity field; the manual button remains only as a fallback if the auto-update can't run. Manual serial/MAC edit (InlineField) still available. Verified: corrupted EQP-0000100001 mac to 00:00:00:00:00:00 → one airosSignal read self-healed it to RADIUS 24:5A:4C:30:CE:AA (logged). SPA deployed; leak-check 0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
22593a06c3
commit
3f416fe2ec
|
|
@ -130,8 +130,11 @@
|
|||
<div v-else-if="airos && !airos.ok" class="dsf-note">
|
||||
<q-icon name="info" size="12px" /> {{ airos.registered === false ? 'Signal sans-fil en attente : webhook n8n « get_signal_airos » à configurer.' : airos.error }}
|
||||
</div>
|
||||
<!-- MAC RADIUS (WPA2) autoritaire ≠ MAC enregistrée (équipement remplacé ?) → alignement 1-clic. -->
|
||||
<div v-if="airos && airos.ok && macMismatch" class="dsf-macfix">
|
||||
<!-- MAC RADIUS (WPA2) = autoritaire, AUTO-synchronisée côté serveur au remplacement d'équipement. -->
|
||||
<div v-if="airos && airos.ok && airos.macSynced" class="dsf-note" style="color:#166534">
|
||||
<q-icon name="check_circle" size="13px" color="green-7" /> MAC synchronisée automatiquement depuis RADIUS : <code>{{ airos.radiusMac }}</code><span v-if="airos.macWas"> (était {{ airos.macWas }})</span>
|
||||
</div>
|
||||
<div v-else-if="airos && airos.ok && macMismatch" class="dsf-macfix">
|
||||
<q-icon name="sync_problem" size="13px" color="deep-orange-7" /> MAC RADIUS <code>{{ airos.radiusMac }}</code> ≠ enregistrée <code>{{ doc.mac_address || '—' }}</code>
|
||||
<q-btn dense flat no-caps size="sm" color="primary" icon="sync" label="Mettre à jour" :loading="macSyncing" @click="syncMacFromRadius" />
|
||||
</div>
|
||||
|
|
@ -642,6 +645,8 @@ async function fetchAiros () {
|
|||
try {
|
||||
const r = await fetch(`${HUB_URL}/collab/airos-signal?serial=${encodeURIComponent(props.doc.serial_number || '')}&ip=${encodeURIComponent(props.doc.ip_address || '')}`)
|
||||
airos.value = await r.json()
|
||||
// Le hub a auto-synchronisé la MAC depuis RADIUS → refléter localement (l'InlineField Identité suit).
|
||||
if (airos.value && airos.value.macSynced && airos.value.radiusMac) props.doc.mac_address = airos.value.radiusMac
|
||||
} catch (e) { airos.value = { ok: false, error: e.message } } finally { airosLoading.value = false }
|
||||
}
|
||||
// MAC RADIUS (WPA2) = source autoritaire, s'auto-MàJ au remplacement d'équipement. Si elle diffère de la MAC
|
||||
|
|
|
|||
|
|
@ -842,29 +842,39 @@ function httpsGetJson (targetUrl, headers, timeoutMs) {
|
|||
})
|
||||
}
|
||||
|
||||
const macNorm = (m) => String(m || '').replace(/[^0-9a-fA-F]/g, '').toUpperCase()
|
||||
async function airosSignal ({ sn, ip, port, user, mac } = {}) {
|
||||
// Résout ip/mac depuis Service Equipment (source unique du parc) si non fournis.
|
||||
if ((!ip || !mac) && sn) {
|
||||
try {
|
||||
const rows = await erp.list('Service Equipment', { filters: [['serial_number', '=', String(sn)]], fields: ['ip_address', 'mac_address'], limit: 1 })
|
||||
const e = rows && rows[0]
|
||||
if (e) { if (!ip) ip = e.ip_address || ''; if (!mac) mac = e.mac_address || '' }
|
||||
} catch (e) { /* best-effort */ }
|
||||
}
|
||||
// Résout le Service Equipment (nom + IP + MAC enregistrée) — sert à l'AUTO-SYNC de la MAC depuis RADIUS.
|
||||
let seName = null, seMac = ''
|
||||
try {
|
||||
let rows = []
|
||||
if (sn) rows = await erp.list('Service Equipment', { filters: [['serial_number', '=', String(sn)]], fields: ['name', 'ip_address', 'mac_address'], limit: 1 })
|
||||
if ((!rows || !rows.length) && ip) rows = await erp.list('Service Equipment', { filters: [['ip_address', '=', String(ip)]], fields: ['name', 'ip_address', 'mac_address'], limit: 1 })
|
||||
const e = rows && rows[0]
|
||||
if (e) { seName = e.name; seMac = e.mac_address || ''; if (!ip) ip = e.ip_address || ''; if (!mac) mac = e.mac_address || '' }
|
||||
} catch (e) { /* best-effort */ }
|
||||
if (!sn && !ip && !mac) return { ok: false, error: 'serial, ip ou mac requis' }
|
||||
// PRIMAIRE : pont F ops_airos.php (même hôte + token que ops_reassign.php).
|
||||
const token = opsBridgeToken()
|
||||
const base = (process.env.OPS_LEGACY_URL || 'https://facturation.targo.ca/ops_reassign.php').replace(/ops_reassign\.php.*$/, 'ops_airos.php')
|
||||
let out = null
|
||||
if (token && base) {
|
||||
const q = new URLSearchParams()
|
||||
if (sn) q.set('serial', sn); if (ip) q.set('ip', ip); if (mac) q.set('mac', mac); if (port) q.set('port', String(port))
|
||||
const r = await httpsGetJson(base + '?' + q.toString(), { 'X-Ops-Token': token }, 25000)
|
||||
if (r && r.ok === true) return normalizeAiros(r)
|
||||
if (r && r.ok === false && r.error && !r._err) return { ok: false, error: r.error } // réponse F propre (device introuvable / CPE injoignable)
|
||||
if (r && r.ok === true) out = normalizeAiros(r)
|
||||
else if (r && r.ok === false && r.error && !r._err) return { ok: false, error: r.error } // réponse F propre (device introuvable / CPE injoignable)
|
||||
// transport KO (r._err) → tenter n8n en repli
|
||||
}
|
||||
// REPLI : webhook n8n get_signal_airos (post-migration F ; 404 = pas encore créé).
|
||||
return await airosViaN8n({ sn, ip, port, user })
|
||||
if (!out) out = await airosViaN8n({ sn, ip, port, user })
|
||||
// AUTO-SYNC MAC : RADIUS (WPA2) = autoritaire, s'auto-MàJ au remplacement. Si la MAC courante diffère de
|
||||
// l'enregistrée → on met à jour Service Equipment AUTOMATIQUEMENT (côté serveur, fill/correct, idempotent).
|
||||
if (out && out.ok && out.radiusMac && seName && macNorm(out.radiusMac) && macNorm(out.radiusMac) !== macNorm(seMac)) {
|
||||
try { await erp.update('Service Equipment', seName, { mac_address: out.radiusMac }); out.macSynced = true; out.macWas = seMac || null; log('[airos] MAC auto-sync ' + seName + ' ' + (seMac || '∅') + ' → ' + out.radiusMac + ' (RADIUS)') }
|
||||
catch (e) { out.macSyncError = e.message }
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function airosViaN8n ({ sn, ip, port, user } = {}) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user