From: Steph Ponzo Date: Sun, 1 Mar 2026 13:08:50 +0000 (+0100) Subject: fix(web): script.js corrections API - champs corrects + SERVER_URL dynamique X-Git-Url: https://git.digitality.be/?a=commitdiff_plain;h=57cefd91be6d085138df82589b6587cf55843aca;p=pdw25-26 fix(web): script.js corrections API - champs corrects + SERVER_URL dynamique --- diff --git a/Wallette/web/script.js b/Wallette/web/script.js index b820286..3e80fa4 100644 --- a/Wallette/web/script.js +++ b/Wallette/web/script.js @@ -1,213 +1,278 @@ // script.js (module) import { - connectToAlerts, - onAlert, - disconnectFromAlerts, - isConnected + connectToAlerts, + onAlert, + disconnectFromAlerts, + isConnected } from './socketService.js'; -const SERVER_URL = 'http://localhost:3000'; +// URL dynamique — fonctionne en local ET en production +const SERVER_URL = window.location.origin; -// Exemple : récupérer un userId (ici exemple statique, adapte-toi) -const userId = 'user-123'; +// userId courant +let userId = localStorage.getItem('wallette_userId') || 'user-123'; + +// Pré-remplir le champ userId +const userInput = document.getElementById("userIdInput"); +if (userInput) userInput.value = userId; // Connecter automatiquement à l'ouverture connectToAlerts(userId); - -//function api - -// Charger historique alertes -async function loadAlertHistory() { - try { - const res = await fetch(`${SERVER_URL}/api/alerts/history?userId=${userId}`); - const data = await res.json(); - - data.forEach(alert => { - // on réutilise EXACTEMENT la même logique que socket - handleAlert(alert); - }); - - } catch (err) { - console.error("Erreur historique alertes :", err); - } +// ───────────────────────────────────────────────────────────── +// UTILITAIRE : appel API générique +// ───────────────────────────────────────────────────────────── +async function apiFetch(path) { + const res = await fetch(SERVER_URL + path); + const json = await res.json(); + if (!json.ok) throw new Error(json.error?.message || 'Erreur API'); + return json.data; } -// Charger prix actuel +// ───────────────────────────────────────────────────────────── +// PRIX ACTUEL +// ───────────────────────────────────────────────────────────── async function loadCurrentPrice(pair = "BTC/EUR") { - try { - const res = await fetch(`${SERVER_URL}/api/prices/current?pair=${pair}`); - const data = await res.json(); - - const priceEl = document.getElementById("price"); - if (priceEl && data.price !== undefined) { - const currency = pair.includes("USD") ? "USD" : "EUR"; - priceEl.textContent = Number(data.price).toLocaleString('fr-FR', { - style: 'currency', - currency: currency - }); + try { + const data = await apiFetch(`/api/price/current?pair=${encodeURIComponent(pair)}`); + const priceEl = document.getElementById("price"); + if (!priceEl) return; + const currency = pair.includes("USD") ? "USD" : "EUR"; + priceEl.textContent = Number(data.current_price).toLocaleString('fr-FR', { + style: 'currency', currency + }); + } catch (err) { + console.error("Erreur prix actuel :", err); + const priceEl = document.getElementById("price"); + if (priceEl) priceEl.textContent = "Indisponible"; } - - } catch (err) { - console.error("Erreur prix actuel :", err); - } } -// Charger wallet utilisateur -async function loadWallet() { - try { - const res = await fetch(`${SERVER_URL}/api/wallet/${userId}`); - const data = await res.json(); +// ───────────────────────────────────────────────────────────── +// WALLETS +// ───────────────────────────────────────────────────────────── +async function loadWallets() { + const walletList = document.getElementById("walletList"); + if (!walletList) return; + + try { + const result = await apiFetch(`/api/wallets?userId=${encodeURIComponent(userId)}`); + const wallets = result.wallets || result || []; + + walletList.innerHTML = ''; + + if (!wallets || wallets.length === 0) { + walletList.innerHTML = `
  • Aucun wallet chargé
  • `; + return; + } + + const first = wallets[0]; + const balanceEl = document.getElementById("balance"); + if (balanceEl) balanceEl.textContent = `${first.balance ?? '0'} ${first.asset_symbol || 'BTC'}`; + + wallets.forEach(wallet => { + const li = document.createElement('li'); + li.className = 'list-group-item bg-transparent text-white'; + li.style.cursor = 'pointer'; + li.textContent = `${wallet.asset_symbol || '?'} — ${wallet.balance ?? '—'}`; + li.onclick = () => loadWalletEvents(wallet.wallet_id); + walletList.appendChild(li); + }); + + if (first.wallet_id) loadWalletEvents(first.wallet_id); + + } catch (err) { + console.error("Erreur wallet :", err); + walletList.innerHTML = `
  • Erreur : ${err.message}
  • `; + } +} - const balanceEl = document.getElementById("balance"); - if (balanceEl && data.balance !== undefined) { - balanceEl.textContent = data.balance + " BTC"; +// ───────────────────────────────────────────────────────────── +// HISTORIQUE TRANSACTIONS +// ───────────────────────────────────────────────────────────── +async function loadWalletEvents(walletId) { + const eventsList = document.getElementById("walletEvents"); + if (!eventsList) return; + + try { + const events = await apiFetch(`/api/wallets/${walletId}/events`); + + eventsList.innerHTML = ''; + + if (!events || events.length === 0) { + eventsList.innerHTML = `
  • Aucune transaction
  • `; + return; + } + + events.forEach(ev => { + const li = document.createElement('li'); + li.className = 'list-group-item bg-transparent text-white d-flex justify-content-between'; + const date = new Date(ev.timestamp_ms || ev.created_at_ms || Date.now()).toLocaleString('fr-FR'); + const typeClass = ev.event_type === 'CREDIT' ? 'text-success' : 'text-danger'; + li.innerHTML = ` + ${ev.event_type || '?'} + ${date} + ${ev.amount ?? '—'} ${ev.asset_symbol || ''} + `; + eventsList.appendChild(li); + }); + + } catch (err) { + console.error("Erreur historique transactions :", err); } +} - } catch (err) { - console.error("Erreur wallet :", err); - } +// ───────────────────────────────────────────────────────────── +// SIGNAL ACTUEL +// ───────────────────────────────────────────────────────────── +async function loadCurrentSignal() { + try { + const signal = await apiFetch(`/api/signal/current?userId=${encodeURIComponent(userId)}&pair=BTC/EUR`); + + const box = document.getElementById('signalBox'); + const actionEl = document.getElementById('signalAction'); + const critEl = document.getElementById('signalCriticality'); + const confEl = document.getElementById('signalConfidence'); + const reasonEl = document.getElementById('signalReason'); + + if (!box) return; + + const action = signal.action || 'HOLD'; + box.className = 'signal-box ' + action.toLowerCase(); + if (actionEl) actionEl.textContent = action; + if (critEl) critEl.textContent = signal.criticality || signal.alertLevel || 'INFO'; + if (confEl) confEl.textContent = typeof signal.confidence === 'number' + ? Math.round(signal.confidence * 100) + '%' : '—'; + if (reasonEl) reasonEl.textContent = signal.reason || '—'; + + } catch (err) { + const reasonEl = document.getElementById('signalReason'); + if (reasonEl) reasonEl.textContent = 'Signal non disponible'; + } } -// Quand une alerte arrive, l'ajouter dans la liste #alertList +// ───────────────────────────────────────────────────────────── +// GESTION ALERTES SOCKET.IO +// ───────────────────────────────────────────────────────────── function handleIncomingAlert(alert) { + console.log('Nouvelle alerte reçue :', alert); - console.log('Nouvelle alerte reçue dans main.js :', alert); - - const list = document.getElementById('alertList'); - if (!list) return; - - const li = document.createElement('li'); + const list = document.getElementById('alertList'); + if (!list) return; - li.className = 'list-group-item bg-transparent text-white'; + const placeholder = list.querySelector('li'); + if (placeholder && placeholder.textContent.includes('Aucune')) placeholder.remove(); - // Si l'alerte est un objet, essaye d'afficher message et type - if (typeof alert === 'object') { - li.textContent = (alert.message ? alert.message : JSON.stringify(alert)); - } else { - li.textContent = String(alert); - } - - // 1) Supprimer "Aucune alerte" si présent - const placeholder = document.getElementById('noAlerts'); - if (placeholder) placeholder.remove(); - - // 2) Appliquer couleur selon BUY / SELL / HOLD - if (typeof alert === 'object' && alert.action) { - const action = alert.action.toUpperCase(); - if (action === 'BUY') li.classList.add('buy'); - else if (action === 'SELL') li.classList.add('sell'); - else li.classList.add('hold'); - } - - // 3) Mettre à jour le bloc signal (si présent) - if (typeof alert === 'object') { - const box = document.getElementById('signalBox'); + const box = document.getElementById('signalBox'); const actionEl = document.getElementById('signalAction'); - const critEl = document.getElementById('signalCriticality'); - const confEl = document.getElementById('signalConfidence'); + const critEl = document.getElementById('signalCriticality'); + const confEl = document.getElementById('signalConfidence'); const reasonEl = document.getElementById('signalReason'); - if (box && actionEl && critEl && confEl && reasonEl) { - const action = alert.action || 'HOLD'; - box.className = 'signal-box ' + action.toLowerCase(); - actionEl.textContent = action; - critEl.textContent = alert.alertLevel || 'INFO'; - confEl.textContent = - typeof alert.confidence === 'number' - ? Math.round(alert.confidence * 100) + '%' - : '—'; - reasonEl.textContent = alert.reason || alert.message || '—'; + if (typeof alert === 'object') { + const action = (alert.action || 'HOLD').toUpperCase(); + if (box) box.className = 'signal-box ' + action.toLowerCase(); + if (actionEl) actionEl.textContent = action; + if (critEl) critEl.textContent = alert.alertLevel || 'INFO'; + if (confEl) confEl.textContent = typeof alert.confidence === 'number' + ? Math.round(alert.confidence * 100) + '%' : '—'; + if (reasonEl) reasonEl.textContent = alert.reason || alert.message || '—'; } - } - - // 4) Notification popup simple (si container existe) - const popupContainer = document.getElementById('popupContainer'); - if (popupContainer) { - const pop = document.createElement('div'); - pop.className = 'notification-popup'; - pop.textContent = li.textContent; - popupContainer.appendChild(pop); - - setTimeout(() => pop.remove(), 6000); - } - - if (typeof alert === 'object') { - - // Date & heure - const d = new Date(alert.timestamp || Date.now()); - const dateStr = d.toLocaleString('fr-FR', { - day: '2-digit', - month: '2-digit', - year: 'numeric', - hour: '2-digit', - minute: '2-digit' - }); - - // Level - const level = alert.alertLevel || alert.level || 'INFO'; - // Action - const action = alert.action || 'HOLD'; + const li = document.createElement('li'); + li.className = 'list-group-item bg-transparent text-white'; + + if (typeof alert === 'object') { + const d = new Date(alert.timestamp || Date.now()); + const dateStr = d.toLocaleString('fr-FR', { + day:'2-digit', month:'2-digit', year:'numeric', hour:'2-digit', minute:'2-digit' + }); + + const level = (alert.alertLevel || alert.level || 'INFO').toUpperCase(); + const action = (alert.action || 'HOLD').toUpperCase(); + + let badgeClass = 'bg-info text-dark'; + if (level === 'CRITICAL') badgeClass = 'bg-danger'; + else if (level === 'WARNING') badgeClass = 'bg-warning text-dark'; + + let actionClass = 'text-warning'; + if (action === 'BUY') actionClass = 'text-success'; + if (action === 'SELL' || action === 'STOP_LOSS') actionClass = 'text-danger'; + + const currency = (alert.pair || '').includes('USD') ? 'USD' : 'EUR'; + const priceStr = alert.price != null + ? Number(alert.price).toLocaleString('fr-FR', { style:'currency', currency }) : ''; + + const pair = alert.pair || ''; + const conf = typeof alert.confidence === 'number' + ? ` • confiance ${Math.round(alert.confidence * 100)}%` : ''; + + li.innerHTML = ` +
    + ${dateStr} + ${level} + ${action} + ${priceStr ? `${priceStr}` : ''} +
    +
    + ${pair}${conf} +
    + `; + } else { + li.textContent = String(alert); + } - // Prix (si présent) - let priceStr = ''; - if (alert.price !== undefined) { - const currency = (alert.pair && alert.pair.includes('USD')) ? 'USD' : 'EUR'; - priceStr = Number(alert.price).toLocaleString('fr-FR', { - style: 'currency', - currency: currency - }); + const popupContainer = document.getElementById('popupContainer'); + if (popupContainer) { + const pop = document.createElement('div'); + pop.className = 'notification-popup'; + pop.textContent = `${alert.action || ''} ${alert.pair || ''} — ${alert.reason || ''}`; + popupContainer.appendChild(pop); + setTimeout(() => pop.remove(), 6000); } - // Pair + confiance - const pair = alert.pair || ''; - const conf = typeof alert.confidence === 'number' - ? ` • confiance ${Math.round(alert.confidence * 100)}%` - : ''; - - // Badge couleur level - let badgeClass = 'bg-primary'; - const lvl = level.toUpperCase(); - - if (lvl.includes('DANGER') || lvl.includes('CRITICAL')) badgeClass = 'bg-danger'; - else if (lvl.includes('WARNING')) badgeClass = 'bg-warning text-dark'; - else if (lvl.includes('INFO')) badgeClass = 'bg-info text-dark'; - - // Appliquer affichage formaté (remplace le texte brut) - li.innerHTML = ` -
    - ${dateStr} - ${lvl} - ${action} - ${priceStr ? `${priceStr}` : ''} -
    -
    - ${pair}${conf} -
    - `; - } - - - // Préfixer pour voir les nouvelles alertes en haut - list.prepend(li); + list.prepend(li); } -// Socket temps réel -onAlert(function(alert) { - handleIncomingAlert(alert); -}); +onAlert(handleIncomingAlert); + +// ───────────────────────────────────────────────────────────── +// BOUTONS +// ───────────────────────────────────────────────────────────── +document.getElementById("saveUserBtn")?.addEventListener("click", () => { + const input = document.getElementById("userIdInput"); + if (input?.value.trim()) { + userId = input.value.trim(); + localStorage.setItem('wallette_userId', userId); + alert("User ID sauvegardé : " + userId); + loadWallets(); + loadCurrentSignal(); + } + }); -loadAlertHistory(); -loadCurrentPrice(); -loadWallet(); + document.getElementById("connectBtn")?.addEventListener("click", () => { + connectToAlerts(userId); + const st = document.getElementById("connStatus"); + if (st) { st.className = "badge bg-success"; st.textContent = "Statut : on"; } + }); -// Déconnexion propre à la fermeture de la page -window.addEventListener('beforeunload', () => { - if (isConnected()) { + document.getElementById("disconnectBtn")?.addEventListener("click", () => { disconnectFromAlerts(); - } + const st = document.getElementById("connStatus"); + if (st) { st.className = "badge bg-secondary"; st.textContent = "Statut : off"; } + }); + + // ───────────────────────────────────────────────────────────── + // CHARGEMENT INITIAL + // ───────────────────────────────────────────────────────────── + loadCurrentPrice(); + loadWallets(); + loadCurrentSignal(); + setInterval(loadCurrentPrice, 30000); + setInterval(loadCurrentSignal, 15000); + + window.addEventListener('beforeunload', () => { + if (isConnected()) disconnectFromAlerts(); }); -// Exposer pour debug dans console -window._alertsService = { connectToAlerts, disconnectFromAlerts, isConnected }; \ No newline at end of file +window._alertsService = { connectToAlerts, disconnectFromAlerts, isConnected };