fix(ops): modern fiber status hero when GenieACS has no data (tech-3 ONUs)

Fibre ONUs on unpolled OLTs (tech-3 XGS-PON, e.g. olt3/172.17.192.6) have no
GenieACS `device`, so the modern topo card AND the "Do Stuff en direct" block —
both gated on `device` — rendered nothing, leaving a stuck "Chargement ACS…", a
stale "Unauthorized" modem-diag, and static titles. But dostuff gives full live
status for these ONUs.

- New modern fiber hero (v-if="isFibre && !device"): topology strip (Internet→OLT→
  ONT) + online/offline pill + colored Rx/Tx signal + distance/uptime/mgmt(link)/
  WAN/VoIP/IPTV/firmware tiles + copy-for-ticket + refresh — driven by the live OLT
  read (dostuff), auto-loaded on mount (2.5s grace for ACS to fill `device` first,
  so ACS-covered ONUs keep their native card; no double fetch).
- Suppressed the misleading stuck "Chargement diagnostic ACS…" for fibre (hero owns
  the state) and confirmed the heavy Playwright modem-diag stays v-if="false".

Verified: build clean, leak 0; dostuff on the shown ONU (TPLGC4160688 @ 172.17.192.6)
returns live state (Hors ligne, uptime 2d12h, mgmt/WAN/VoIP/IPTV) → hero populates.
Deployed index.42eea809.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
louispaulb 2026-07-21 14:22:08 -04:00
parent db03b0cdab
commit 81701724a2

View File

@ -65,6 +65,42 @@
</div>
</div>
<!-- FIBRE sans données ACS (ex. ONU tech-3 sur OLT non supervisée) : vue MODERNE pilotée par le direct OLT
(dostuff), auto-chargée au montage. Remplace l'ancien panneau statique. Statut + signal coloré + faits clés. -->
<div v-if="isFibre && !device" class="topo-summary">
<div class="topo-row">
<div class="topo-node"><q-icon name="public" size="22px" :color="fibOnline === true ? 'green-6' : fibOnline === false ? 'red-5' : 'grey-5'" /><div class="topo-cap">Internet</div></div>
<div class="topo-link" :class="{ ok: fibOnline === true }"></div>
<div class="topo-node"><q-icon name="cell_tower" size="22px" color="blue-grey-6" /><div class="topo-cap">OLT</div><div class="topo-sub">{{ oltLabel }}</div></div>
<div class="topo-link" :class="{ ok: fibOnline === true }"></div>
<div class="topo-node"><div class="topo-modem"><q-icon name="router" size="22px" color="primary" /><span class="topo-dot" :class="fibOnline === true ? 'on' : fibOnline === false ? 'off' : ''"></span></div><div class="topo-cap">ONT</div><div class="topo-sub" v-if="doc.olt_slot != null">{{ doc.olt_slot }}/{{ doc.olt_port }}/{{ doc.olt_ontid }}</div></div>
</div>
<div class="topo-badges">
<q-badge :color="fibOnline === true ? 'green' : fibOnline === false ? 'red' : 'amber'">{{ fibOnline === true ? 'En ligne' : fibOnline === false ? 'Hors ligne' : 'À vérifier' }}</q-badge>
<q-badge v-if="liveF && liveF.ok && liveF.rxPower != null" outline :style="{ color: fibreRxColor(liveF.rxPower), borderColor: fibreRxColor(liveF.rxPower) }">Rx {{ liveF.rxPower }} dBm</q-badge>
<span v-if="liveFLoading" class="text-caption text-deep-purple-6"><q-spinner-dots size="16px" /> Interrogation de l'OLT en direct (~30 s)</span>
<span v-else-if="liveF && !liveF.ok" class="text-caption text-grey-6">{{ liveF.error }}</span>
<span v-else-if="!liveF" class="text-caption text-grey-5">via OLT en direct</span>
<q-space />
<q-btn flat dense no-caps size="sm" color="grey-7" icon="content_copy" label="Ticket" @click="copyForTicket"><q-tooltip>Copier un résumé pour le ticket</q-tooltip></q-btn>
<q-btn flat dense round size="sm" icon="sync" :loading="liveFLoading" @click="fetchLiveF" title="Réinterroger l'OLT" />
</div>
<div v-if="liveF && liveF.ok" class="dv-grid">
<div class="dv-tile" v-if="liveF.rxPower != null"><div class="dv-tl"><q-icon name="network_check" size="14px" /> Signal Rx/Tx</div><div class="dv-tv" :style="{ color: fibreRxColor(liveF.rxPower) }">{{ liveF.rxPower }}<span v-if="liveF.txPower != null"> / {{ liveF.txPower }}</span> dBm</div></div>
<div class="dv-tile" v-if="liveF.distance != null"><div class="dv-tl"><q-icon name="straighten" size="14px" /> Distance</div><div class="dv-tv">{{ liveF.distance }} m</div></div>
<div class="dv-tile" v-if="liveF.uptime"><div class="dv-tl"><q-icon name="schedule" size="14px" /> Uptime</div><div class="dv-tv">{{ liveF.uptime }}</div></div>
<div class="dv-tile" v-if="liveF.managementIp"><div class="dv-tl"><q-icon name="settings" size="14px" /> Gestion</div><a :href="'https://' + liveF.managementIp + '/'" target="_blank" class="dv-tv dv-link">{{ liveF.managementIp }}</a></div>
<div class="dv-tile" v-if="liveF.wanIp"><div class="dv-tl"><q-icon name="public" size="14px" /> WAN</div><div class="dv-tv">{{ liveF.wanIp }}</div></div>
<div class="dv-tile" v-if="liveF.profileId"><div class="dv-tl"><q-icon name="speed" size="14px" /> Profil</div><div class="dv-tv">{{ liveF.profileId }}</div></div>
<div class="dv-tile" v-if="liveF.voip"><div class="dv-tl"><q-icon name="call" size="14px" /> VoIP</div><div class="dv-tv">{{ liveF.voip }}</div></div>
<div class="dv-tile" v-if="liveF.iptv"><div class="dv-tl"><q-icon name="live_tv" size="14px" /> IPTV</div><div class="dv-tv">{{ liveF.iptv }}</div></div>
<div class="dv-tile" v-if="liveF.firmware"><div class="dv-tl"><q-icon name="memory" size="14px" /> Firmware</div><div class="dv-tv">{{ liveF.firmware }}</div></div>
<div class="dv-tile" v-if="liveF.lastDown"><div class="dv-tl dv-warn"><q-icon name="power_off" size="14px" /> Dern. coupure</div><div class="dv-tv">{{ liveF.lastDown }}</div></div>
</div>
<div v-else-if="!liveFLoading && liveF && !liveF.ok" class="dv-wan-ph"><q-icon name="info" size="16px" color="grey-5" /> {{ liveF.error || 'OLT injoignable' }}</div>
<div v-else-if="liveFLoading && !liveF" class="dv-wan-ph"><q-icon name="cell_tower" size="16px" color="grey-4" /> Lecture des données OLT en direct</div>
</div>
<!-- Do Stuff en direct (F) : interroge l'OLT via n8n (comme F) remonte VoIP / IPTV / distance / WAN / profil de
ligne que le poller passif n'expose pas. À la demande (~30 s). + « Copier pour le ticket » (bouton copy de F). -->
<div v-if="device && doc.serial_number && doc.olt_ip" class="dsf">
@ -325,8 +361,8 @@
</div>
</div>
</div>
<div v-else-if="doc.serial_number && !device" class="q-mt-md text-caption text-grey-5">
<q-spinner size="14px" class="q-mr-xs" v-if="deviceLoading" /> Chargement diagnostic ACS...
<div v-else-if="doc.serial_number && !device && !isFibre && deviceLoading" class="q-mt-md text-caption text-grey-5">
<q-spinner size="14px" class="q-mr-xs" /> Chargement diagnostic ACS...
</div>
<!-- Diagnostic modem avancé (modem-bridge / Playwright) DÉSACTIVÉ « pour le moment » : trop lourd (~15 s).
@ -646,6 +682,15 @@ const isWireless = computed(() => {
const s = ((d.brand || '') + ' ' + (d.model || '') + ' ' + (d.equipment_type || '')).toLowerCase()
return /ubiquit|airmax|litebeam|nanostation|nanobridge|rocket|powerbeam|bullet|cambium|airos|\bap\b|sans.?fil|wireless/.test(s)
})
// Fibre (ONT/ONU) : a une OLT (olt_ip) OU un type/modèle fibre OU des données optiques ACS.
const isFibre = computed(() => {
const d = props.doc
if (d.olt_ip) return true
if (/\b(ont|onu|gpon|fibre?|fiber)\b/i.test(String(d.equipment_type || '') + ' ' + String(d.model || ''))) return true
return !!(device.value && device.value.opticalStatus)
})
// Statut fibre MODERNE quand l'ACS (GenieACS) n'a rien : piloté par le direct OLT (liveF/dostuff). En ligne ? état.
const fibOnline = computed(() => (liveF.value && liveF.value.ok) ? liveF.value.online : null)
const airos = ref(null); const airosLoading = ref(false)
async function fetchAiros () {
airos.value = null; airosLoading.value = true
@ -1098,6 +1143,9 @@ onMounted(async () => {
loadWanHistory()
}
loadWStatus() // sans-fil : état rapide RADIUS au chargement (self-guard isWireless)
// Fibre sans ACS (ONU tech-3 non supervisée) : laisse ~2,5 s à l'ACS pour remplir `device` ; sinon auto-interroge
// l'OLT en direct (dostuff) peuple la vue moderne au lieu du panneau statique. Une seule fois (~30 s).
setTimeout(() => { if (isFibre.value && !device.value && props.doc.olt_ip && !liveF.value && !liveFLoading.value) fetchLiveF() }, 2500)
})
// Chargement PARESSEUX : on n'interroge le modem (modem-bridge) que si l'agent déplie « Appareils connectés ».
@ -1112,6 +1160,7 @@ watch(() => props.doc.serial_number, sn => {
loadEvents(); loadWanHistory() // rafraîchir la bande dispo/événements pour le nouvel appareil
}
loadWStatus()
setTimeout(() => { if (isFibre.value && !device.value && props.doc.olt_ip && !liveF.value && !liveFLoading.value) fetchLiveF() }, 2500)
})
onUnmounted(() => { stopWanLive(); stopEthLive() })
</script>