diff --git a/anchor_count.txt b/anchor_count.txt index 1fadca7..46e5dc4 100644 --- a/anchor_count.txt +++ b/anchor_count.txt @@ -1 +1 @@ -2026-01-25T01:33:13.690Z;7182;00000002fe6bb5f10aa5f01688bc0e6f862df0e4a4571babd2df5dd30d919b0b;9752 \ No newline at end of file +2026-01-25T02:55:07.388Z;7419;00000016a18555e0e95e3214e128029e8c86e8bbbe68c62d240821a6a2951061;10558 \ No newline at end of file diff --git a/api-faucet/faucet-api.service b/api-faucet/faucet-api.service new file mode 100644 index 0000000..e28756a --- /dev/null +++ b/api-faucet/faucet-api.service @@ -0,0 +1,21 @@ +[Unit] +Description=Bitcoin Signet Faucet API +After=network.target + +[Service] +Type=simple +User=ncantu +WorkingDirectory=/home/ncantu/Bureau/code/bitcoin/api-faucet +Environment=NODE_ENV=production +ExecStart=/usr/bin/node /home/ncantu/Bureau/code/bitcoin/api-faucet/src/server.js +Restart=always +RestartSec=10 +StandardOutput=journal +StandardError=journal + +# Sécurité +NoNewPrivileges=true +PrivateTmp=true + +[Install] +WantedBy=multi-user.target diff --git a/signet-dashboard/public/app.js b/signet-dashboard/public/app.js index eb7ba2d..1b4492e 100644 --- a/signet-dashboard/public/app.js +++ b/signet-dashboard/public/app.js @@ -15,13 +15,50 @@ if (window.location.hostname.includes('dashboard.certificator.4nkweb.com')) { } let selectedFile = null; +let lastBlockHeight = null; +let blockPollingInterval = null; // Initialisation document.addEventListener('DOMContentLoaded', () => { loadData(); setInterval(loadData, 30000); // Rafraîchir toutes les 30 secondes + + // Démarrer le polling pour détecter les nouveaux blocs + startBlockPolling(); }); +/** + * Démarrer le polling pour détecter les nouveaux blocs + */ +function startBlockPolling() { + // Vérifier la hauteur du bloc toutes les 5 secondes + blockPollingInterval = setInterval(async () => { + try { + const response = await fetch(`${API_BASE_URL}/api/blockchain/info`); + if (!response.ok) return; + + const data = await response.json(); + const currentHeight = data.blocks; + + // Si la hauteur a changé, actualiser les métriques + if (lastBlockHeight !== null && currentHeight > lastBlockHeight) { + console.log(`Nouveau bloc détecté: ${lastBlockHeight} -> ${currentHeight}`); + // Actualiser uniquement les 4 métriques spécifiques + await Promise.all([ + loadAvgFee(), + loadAvgTxAmount(), + loadMiningDifficulty(), + loadAvgBlockTime(), + ]); + } + + lastBlockHeight = currentHeight; + } catch (error) { + console.error('Error checking block height:', error); + } + }, 5000); // Vérifier toutes les 5 secondes +} + /** * Charge toutes les données de supervision */ @@ -35,6 +72,8 @@ async function loadData() { loadNetworkPeers(), loadMiningDifficulty(), loadAvgBlockTime(), + loadAvgFee(), + loadAvgTxAmount(), ]); updateLastUpdateTime(); @@ -52,6 +91,10 @@ async function loadBlockchainInfo() { const data = await response.json(); document.getElementById('block-height').textContent = data.blocks || 0; + // Initialiser lastBlockHeight si ce n'est pas déjà fait + if (lastBlockHeight === null && data.blocks !== undefined) { + lastBlockHeight = data.blocks; + } } catch (error) { console.error('Error loading blockchain info:', error); document.getElementById('block-height').textContent = 'Erreur'; @@ -165,12 +208,24 @@ async function loadNetworkPeers() { async function loadMiningDifficulty() { try { const response = await fetch(`${API_BASE_URL}/api/mining/difficulty`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } const data = await response.json(); - if (data.difficulty !== undefined) { - // Formater la difficulté avec séparateurs de milliers - const formatted = formatDifficulty(data.difficulty); - document.getElementById('mining-difficulty').textContent = formatted; + if (data.difficulty !== undefined && data.difficulty !== null) { + // Convertir en nombre si nécessaire (sécurité supplémentaire) + const difficulty = typeof data.difficulty === 'string' + ? parseFloat(data.difficulty) + : Number(data.difficulty); + + if (!isNaN(difficulty)) { + // Formater la difficulté avec séparateurs de milliers + const formatted = formatDifficulty(difficulty); + document.getElementById('mining-difficulty').textContent = formatted; + } else { + document.getElementById('mining-difficulty').textContent = '-'; + } } else { document.getElementById('mining-difficulty').textContent = '-'; } @@ -184,20 +239,42 @@ async function loadMiningDifficulty() { * Charge le temps moyen entre blocs */ async function loadAvgBlockTime() { + const avgBlockTimeValue = document.getElementById('avg-block-time-value'); + const avgBlockTimeSpinner = document.getElementById('avg-block-time-spinner'); + + if (!avgBlockTimeValue || !avgBlockTimeSpinner) { + console.error('Elements avg-block-time-value or avg-block-time-spinner not found in DOM'); + return; + } + + // Afficher le spinner + avgBlockTimeSpinner.style.display = 'inline'; + avgBlockTimeValue.textContent = '...'; + try { const response = await fetch(`${API_BASE_URL}/api/mining/avg-block-time`); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + // Masquer le spinner et mettre à jour la valeur + avgBlockTimeSpinner.style.display = 'none'; + if (data.formatted) { - document.getElementById('avg-block-time').textContent = data.formatted; + avgBlockTimeValue.textContent = data.formatted; } else if (data.timeAvgSeconds !== undefined) { - document.getElementById('avg-block-time').textContent = formatBlockTime(data.timeAvgSeconds); + avgBlockTimeValue.textContent = formatBlockTime(data.timeAvgSeconds); } else { - document.getElementById('avg-block-time').textContent = '-'; + avgBlockTimeValue.textContent = '-'; } } catch (error) { console.error('Error loading average block time:', error); - document.getElementById('avg-block-time').textContent = 'Erreur'; + // Masquer le spinner + avgBlockTimeSpinner.style.display = 'none'; + avgBlockTimeValue.textContent = 'Erreur'; } } @@ -230,6 +307,129 @@ function formatBlockTime(seconds) { } } +/** + * Formate un montant en sats avec l'emoji ✅ + */ +function formatSats(sats) { + if (sats === 0) return '0 ✅'; + if (sats < 1000) return sats.toLocaleString('fr-FR') + ' ✅'; + if (sats < 1000000) return (sats / 1000).toFixed(2) + ' K ✅'; + if (sats < 1000000000) return (sats / 1000000).toFixed(2) + ' M ✅'; + return (sats / 1000000000).toFixed(2) + ' G ✅'; +} + +/** + * Formate un montant en sats avec l'emoji ✅ (sans préfixes K/M/G) + */ +function formatSatsSimple(sats) { + if (sats === 0) return '0 ✅'; + return sats.toLocaleString('fr-FR') + ' ✅'; +} + +/** + * Charge les frais moyens + */ +async function loadAvgFee() { + const avgFeeValue = document.getElementById('avg-fee-value'); + const avgFeeSpinner = document.getElementById('avg-fee-spinner'); + + if (!avgFeeValue || !avgFeeSpinner) { + console.error('Elements avg-fee-value or avg-fee-spinner not found in DOM'); + return; + } + + // Afficher le spinner + avgFeeSpinner.style.display = 'inline'; + avgFeeValue.textContent = '...'; + + try { + const response = await fetch(`${API_BASE_URL}/api/transactions/avg-fee`); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + // Masquer le spinner et mettre à jour la valeur + avgFeeSpinner.style.display = 'none'; + + if (data.avgFee !== undefined && data.avgFee !== null) { + const avgFee = Number(data.avgFee); + if (!isNaN(avgFee) && avgFee >= 0) { + const formatted = formatSatsSimple(avgFee); + avgFeeValue.textContent = formatted; + } else { + avgFeeValue.textContent = '-'; + } + } else { + avgFeeValue.textContent = '-'; + } + } catch (error) { + console.error('Error loading average fee', error); + // Masquer le spinner + avgFeeSpinner.style.display = 'none'; + avgFeeValue.textContent = 'Erreur'; + } +} + +/** + * Charge le montant moyen des transactions + */ +async function loadAvgTxAmount() { + const avgTxAmountValue = document.getElementById('avg-tx-amount-value'); + const avgTxAmountSpinner = document.getElementById('avg-tx-amount-spinner'); + + if (!avgTxAmountValue || !avgTxAmountSpinner) { + console.error('Elements avg-tx-amount-value or avg-tx-amount-spinner not found in DOM'); + return; + } + + // Afficher le spinner + avgTxAmountSpinner.style.display = 'inline'; + avgTxAmountValue.textContent = '...'; + + try { + const response = await fetch(`${API_BASE_URL}/api/transactions/avg-amount`, { + signal: AbortSignal.timeout(60000) // Timeout de 60 secondes + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + if (data.error) { + throw new Error(data.error); + } + + // Masquer le spinner et mettre à jour la valeur + avgTxAmountSpinner.style.display = 'none'; + + if (data.avgAmount !== undefined && data.avgAmount !== null) { + // Convertir en nombre si nécessaire + const avgAmount = typeof data.avgAmount === 'string' + ? parseFloat(data.avgAmount) + : Number(data.avgAmount); + + if (!isNaN(avgAmount) && avgAmount >= 0) { + const formatted = formatSatsSimple(avgAmount); + avgTxAmountValue.textContent = formatted; + } else { + avgTxAmountValue.textContent = '-'; + } + } else { + avgTxAmountValue.textContent = '-'; + } + } catch (error) { + console.error('Error loading average transaction amount', error); + // Masquer le spinner + avgTxAmountSpinner.style.display = 'none'; + avgTxAmountValue.textContent = 'Erreur'; + } +} + /** * Formate un montant en BTC */ diff --git a/signet-dashboard/public/index.html b/signet-dashboard/public/index.html index bead7ef..de4a58c 100644 --- a/signet-dashboard/public/index.html +++ b/signet-dashboard/public/index.html @@ -60,7 +60,24 @@
-
++ - + +
++ - + +
++ - + +