diff --git a/services/targo-hub/lib/olt-ops.js b/services/targo-hub/lib/olt-ops.js index b98cf95..3ec35bf 100644 --- a/services/targo-hub/lib/olt-ops.js +++ b/services/targo-hub/lib/olt-ops.js @@ -39,15 +39,16 @@ const RC = { const rcOltId = (s, p, o) => Number(s) * 10000000 + Number(p) * 100000 + Number(o) const rcOnuId = (s, p, o) => (Number(s) + 32) * 8388608 + Number(p) * 65536 + Number(o) -// action → verbe n8n (tech-3), champs requis, techs supportées. tech-3 = n8n ; tech-2 = SNMP natif (G3). +// action → verbe n8n (tech-3), champs requis, techs supportées, effet (affiché dans le plan). +// tech-3 = n8n ; tech-2 = SNMP natif (G3). const ACTIONS = { - activate: { verb: 'POST', label: 'Activer / provisionner l’ONU', needs: ['sn', 'olt', 'profileid'], techs: [3] }, - replace: { verb: 'PUT', label: 'Remplacer l’ONU (échange de série)', needs: ['old_sn', 'new_sn', 'olt'], techs: [3] }, - speed: { verb: 'PATCH', label: 'Changer le forfait / la vitesse', needs: ['sn', 'olt', 'profileid'], extra: { trigger: 'speedchange' }, techs: [3, 2] }, - suspend: { verb: 'PATCH', label: 'Suspendre l’internet (VLAN)', needs: ['sn', 'olt'], extra: { profileid: '', trigger: 'suspend' }, techs: [3, 2] }, - unsuspend: { verb: 'PATCH', label: 'Rétablir l’internet (VLAN)', needs: ['sn', 'olt'], extra: { profileid: '', trigger: 'unsuspend' }, techs: [3, 2] }, - remove: { verb: 'DELETE', label: 'Retirer / désactiver l’ONU', needs: ['sn', 'olt'], techs: [3] }, - reboot: { verb: null, label: 'Redémarrer l’ONU', needs: ['sn', 'olt'], techs: [2] }, // tech-3 : utiliser le reboot TR-069 + activate: { verb: 'POST', label: 'Activer / provisionner l’ONU', needs: ['sn', 'olt', 'profileid'], techs: [3], effect: 'Provisionne l’ONU sur l’OLT + met Service Equipment « Actif ».' }, + replace: { verb: 'PUT', label: 'Remplacer l’ONU (échange de série)', needs: ['old_sn', 'new_sn', 'olt'], techs: [3], effect: 'Ré-authentifie la nouvelle série sur l’OLT + met à jour le numéro de série de Service Equipment.' }, + speed: { verb: 'PATCH', label: 'Changer le forfait / la vitesse', needs: ['sn', 'olt', 'profileid'], extra: { trigger: 'speedchange' }, techs: [3, 2], effect: 'Change le profil de ligne (l’ONU redémarre).' }, + suspend: { verb: 'PATCH', label: 'Suspendre l’internet (VLAN)', needs: ['sn', 'olt'], extra: { profileid: '', trigger: 'suspend' }, techs: [3, 2], effect: 'Bascule le VLAN internet → coupe l’accès (facturation inchangée).' }, + unsuspend: { verb: 'PATCH', label: 'Rétablir l’internet (VLAN)', needs: ['sn', 'olt'], extra: { profileid: '', trigger: 'unsuspend' }, techs: [3, 2], effect: 'Rétablit le VLAN internet.' }, + remove: { verb: 'DELETE', label: 'Retirer / désactiver l’ONU', needs: ['sn', 'olt'], techs: [3], effect: 'Retire l’ONU de l’OLT + délie Service Equipment.' }, + reboot: { verb: null, label: 'Redémarrer l’ONU', needs: ['sn', 'olt'], techs: [2], effect: 'Redémarre l’ONU.' }, // tech-3 : utiliser le reboot TR-069 } // SNMP SET Raisecom (tech-2) : applique l'action + SAVE, via net-snmp. Renvoie {ok,error}. LECTURE : voir olt-snmp. @@ -119,25 +120,33 @@ async function resolveEquip (serial) { return equip } -// Appel n8n dostuff avec un VERBE arbitraire + body JSON (le module https, comme dostuffStatus). -function dostuff (verb, payload, timeoutMs = 60000) { +// Requête n8n : VERBE HTTP arbitraire + body JSON via le module https (fetch refuse un body en GET). Renvoie le +// JSON brut. Base commune de dostuff() et wifiClients() (F fait pareil). +function n8nRequest (verb, targetUrl, body, timeoutMs) { return new Promise((resolve) => { let https; try { https = require('https') } catch (e) { return resolve({ ok: false, error: 'https indisponible' }) } - const body = JSON.stringify(payload) - const req = https.request(N8N_DOSTUFF, { method: verb, headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }, timeout: timeoutMs }, (res) => { + const payload = JSON.stringify(body) + const req = https.request(targetUrl, { method: verb, headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) }, timeout: timeoutMs }, (res) => { let d = ''; res.on('data', c => { d += c }); res.on('end', () => { let parsed; try { parsed = JSON.parse(d) } catch (e) { parsed = d } - const r = Array.isArray(parsed) ? parsed[0] : parsed - const recognized = !(r && r.ErrorCode) - resolve({ ok: recognized && res.statusCode < 400, status: res.statusCode, recognized, error: (r && r.ErrorCode) || null, data: r }) + resolve({ ok: res.statusCode < 400, status: res.statusCode, data: parsed }) }) }) req.on('error', e => resolve({ ok: false, error: e.message })) req.on('timeout', () => { req.destroy(); resolve({ ok: false, error: 'délai n8n dépassé (~' + Math.round(timeoutMs / 1000) + ' s)' }) }) - req.write(body); req.end() + req.write(payload); req.end() }) } +// dostuff : verbe = action ; « OLT non reconnue » (ErrorCode) = tech ≠ 3 → non reconnu. +async function dostuff (verb, payload, timeoutMs = 60000) { + const res = await n8nRequest(verb, N8N_DOSTUFF, payload, timeoutMs) + if (res.error) return { ok: false, error: res.error } + const r = Array.isArray(res.data) ? res.data[0] : res.data + const recognized = !(r && r.ErrorCode) + return { ok: recognized && res.ok, status: res.status, recognized, error: (r && r.ErrorCode) || null, data: r } +} + // Construit le payload n8n EXACT pour une action, à partir du contexte résolu + overrides. function buildPayload (action, equip, opts = {}) { const spec = ACTIONS[action] @@ -179,13 +188,7 @@ async function plan ({ serial, action, ...opts } = {}) { const missing = method === 'n8n' ? spec.needs.filter(k => !payload[k] && payload[k] !== 0) : [] if (missing.length) warnings.push('Champs manquants : ' + missing.join(', ')) const canRun = supportsTech && !!payload.olt && (method === 'n8n' ? (tech === 3 && !missing.length) : snmpReady) - const effects = [] - if (action === 'activate') effects.push('Provisionne l’ONU sur l’OLT + met Service Equipment « Actif ».') - if (action === 'replace') effects.push('Ré-authentifie la nouvelle série sur l’OLT + met à jour le numéro de série de Service Equipment.') - if (action === 'speed') effects.push('Change le profil de ligne (l’ONU redémarre).') - if (action === 'suspend') effects.push('Bascule le VLAN internet → coupe l’accès (facturation inchangée).') - if (action === 'unsuspend') effects.push('Rétablit le VLAN internet.') - if (action === 'remove') effects.push('Retire l’ONU de l’OLT + délie Service Equipment.') + const effects = spec.effect ? [spec.effect] : [] return { ok: canRun, action, label: spec.label, verb: spec.verb, tech, method, @@ -233,18 +236,12 @@ async function run ({ serial, action, confirm, idempotencyKey, actor, ...opts } } // G1 reste : clients WiFi connectés d'un ONU (n8n get_wifi_info) — LECTURE. -function wifiClients ({ sn } = {}) { - return new Promise((resolve) => { - if (!sn) return resolve({ ok: false, error: 'sn requis' }) - let https; try { https = require('https') } catch (e) { return resolve({ ok: false, error: 'https indisponible' }) } - const body = JSON.stringify({ sn: String(sn) }) - const req = https.request('https://n8napi.targo.ca/webhook/get_wifi_info', { method: 'GET', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }, timeout: 20000 }, (res) => { - let d = ''; res.on('data', c => { d += c }); res.on('end', () => { let j; try { j = JSON.parse(d) } catch (e) { j = null } resolve({ ok: !!j, clients: Array.isArray(j) ? j : (j && j.clients) || [], raw: j }) }) - }) - req.on('error', e => resolve({ ok: false, error: e.message })) - req.on('timeout', () => { req.destroy(); resolve({ ok: false, error: 'délai dépassé' }) }) - req.write(body); req.end() - }) +async function wifiClients ({ sn } = {}) { + if (!sn) return { ok: false, error: 'sn requis' } + const res = await n8nRequest('GET', 'https://n8napi.targo.ca/webhook/get_wifi_info', { sn: String(sn) }, 20000) + if (res.error) return { ok: false, error: res.error } + const j = res.data + return { ok: !!j, clients: Array.isArray(j) ? j : (j && j.clients) || [], raw: j } } module.exports = { plan, run, wifiClients, ACTIONS }