Optimize home page loading by prioritizing critical data

**Motivations:**
- La home est très lente au premier lancement
- Tous les endpoints sont chargés en parallèle, y compris les plus lents
- L'endpoint /api/utxo/list est très lent car il charge toute la liste

**Root causes:**
- loadData() charge 10 endpoints en parallèle sans priorisation
- loadAvailableForAnchor() charge toute la liste UTXO juste pour obtenir un count
- Les données moins critiques (avg fee, avg tx amount) bloquent l'affichage initial

**Correctifs:**
- Chargement en 2 phases : données critiques d'abord, données secondaires ensuite
- Création d'un endpoint optimisé /api/utxo/count qui lit directement depuis le fichier texte
- loadAvailableForAnchor() utilise maintenant l'endpoint optimisé au lieu de /api/utxo/list
- Les données secondaires (avg fee, avg tx amount, avg block time) sont chargées en arrière-plan

**Evolutions:**
- Affichage initial plus rapide avec les données critiques
- Meilleure expérience utilisateur avec chargement progressif
- Endpoint optimisé pour obtenir le count sans charger toute la liste UTXO

**Pages affectées:**
- signet-dashboard/src/server.js : Nouvel endpoint /api/utxo/count
- signet-dashboard/public/app.js : Optimisation de loadData() et loadAvailableForAnchor()
This commit is contained in:
ncantu 2026-01-26 00:46:02 +01:00
parent bb28499e3f
commit 548eb220da
2 changed files with 73 additions and 14 deletions

View File

@ -61,23 +61,32 @@ function startBlockPolling() {
/** /**
* Charge toutes les données de supervision * Charge toutes les données de supervision
* Optimisé pour charger d'abord les données critiques, puis les données moins critiques
*/ */
async function loadData() { async function loadData() {
try { try {
// Phase 1 : Charger les données critiques rapidement (affichage immédiat)
await Promise.all([ await Promise.all([
loadBlockchainInfo(), loadBlockchainInfo(),
loadLatestBlock(), loadLatestBlock(),
loadWalletBalance(), loadWalletBalance(),
loadAnchorCount(), loadAnchorCount(),
loadAvailableForAnchor(),
loadNetworkPeers(), loadNetworkPeers(),
loadMiningDifficulty(), loadMiningDifficulty(),
loadAvgBlockTime(),
loadAvgFee(),
loadAvgTxAmount(),
]); ]);
updateLastUpdateTime(); updateLastUpdateTime();
// Phase 2 : Charger les données moins critiques en arrière-plan (peuvent être plus lentes)
// Ne pas attendre ces données pour mettre à jour l'heure
Promise.all([
loadAvailableForAnchor(),
loadAvgBlockTime(),
loadAvgFee(),
loadAvgTxAmount(),
]).catch((error) => {
console.error('Error loading secondary data:', error);
});
} catch (error) { } catch (error) {
console.error('Error loading data:', error); console.error('Error loading data:', error);
} }
@ -190,13 +199,13 @@ async function loadAnchorCount() {
/** /**
* Charge la capacité d'ancrage restante * Charge la capacité d'ancrage restante
* Utilise l'endpoint optimisé /api/utxo/count pour éviter de charger toute la liste
*/ */
async function loadAvailableForAnchor() { async function loadAvailableForAnchor() {
const availableForAnchorValue = document.getElementById('available-for-anchor-value'); const availableForAnchorValue = document.getElementById('available-for-anchor-value');
const availableForAnchorSpinner = document.getElementById('available-for-anchor-spinner'); const availableForAnchorSpinner = document.getElementById('available-for-anchor-spinner');
const confirmedAvailableForAnchorValue = document.getElementById('confirmed-available-for-anchor-value');
if (!availableForAnchorValue || !availableForAnchorSpinner || !confirmedAvailableForAnchorValue) { if (!availableForAnchorValue || !availableForAnchorSpinner) {
console.error('Elements for available-for-anchor not found in DOM'); console.error('Elements for available-for-anchor not found in DOM');
return; return;
} }
@ -204,24 +213,21 @@ async function loadAvailableForAnchor() {
// Afficher le spinner // Afficher le spinner
availableForAnchorSpinner.style.display = 'inline'; availableForAnchorSpinner.style.display = 'inline';
availableForAnchorValue.textContent = '...'; availableForAnchorValue.textContent = '...';
confirmedAvailableForAnchorValue.textContent = '...';
try { try {
const response = await fetch(`${API_BASE_URL}/api/utxo/list`); // Utiliser l'endpoint optimisé qui lit directement depuis le fichier texte
const response = await fetch(`${API_BASE_URL}/api/utxo/count`);
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`); throw new Error(`HTTP error! status: ${response.status}`);
} }
const data = await response.json(); const data = await response.json();
const counts = data.counts || {}; const availableForAnchor = data.availableForAnchor || 0;
const availableForAnchor = counts.availableForAnchor || 0;
const confirmedAvailableForAnchor = counts.confirmedAvailableForAnchor || 0;
// Masquer le spinner et mettre à jour les valeurs // Masquer le spinner et mettre à jour la valeur
availableForAnchorSpinner.style.display = 'none'; availableForAnchorSpinner.style.display = 'none';
availableForAnchorValue.textContent = availableForAnchor.toLocaleString('fr-FR') + ' ancrages'; availableForAnchorValue.textContent = availableForAnchor.toLocaleString('fr-FR') + ' ancrages';
confirmedAvailableForAnchorValue.textContent = confirmedAvailableForAnchor.toLocaleString('fr-FR');
} catch (error) { } catch (error) {
console.error('Error loading available for anchor:', error); console.error('Error loading available for anchor:', error);
// Masquer le spinner // Masquer le spinner
@ -231,7 +237,6 @@ async function loadAvailableForAnchor() {
if (currentValue === '-' || currentValue === 'Erreur' || currentValue === '...') { if (currentValue === '-' || currentValue === 'Erreur' || currentValue === '...') {
availableForAnchorValue.textContent = '0 ancrages'; availableForAnchorValue.textContent = '0 ancrages';
} }
confirmedAvailableForAnchorValue.textContent = '0';
} }
} }

View File

@ -273,6 +273,60 @@ app.get('/api/hash/list.txt', async (req, res) => {
} }
}); });
// Route optimisée pour obtenir uniquement les counts UTXO (sans charger toute la liste)
app.get('/api/utxo/count', async (req, res) => {
try {
const { readFileSync, existsSync } = await import('fs');
const utxoListPath = join(__dirname, '../../utxo_list.txt');
if (!existsSync(utxoListPath)) {
return res.json({
availableForAnchor: 0,
confirmedAvailableForAnchor: 0,
anchors: 0,
});
}
// Lire le fichier et compter rapidement sans parser toute la structure
const content = readFileSync(utxoListPath, 'utf8').trim();
const lines = content.split('\n').filter(line => line.trim());
let anchors = 0;
let availableForAnchor = 0;
let confirmedAvailableForAnchor = 0;
const minAnchorAmount = 2000 / 100000000; // 2000 sats en BTC
for (const line of lines) {
const parts = line.split(';');
if (parts.length >= 7) {
const category = parts[0];
const amount = parseFloat(parts[3]) || 0;
const confirmations = parseInt(parts[4], 10) || 0;
const isAnchorChange = parts[5] === 'true';
if (category === 'anchor' && amount >= minAnchorAmount && confirmations > 0) {
anchors++;
// On assume que les UTXOs dans le fichier ne sont pas dépensés (isSpentOnchain serait dans un autre champ)
// Pour être sûr, on vérifie seulement les confirmations
availableForAnchor++;
if (confirmations >= 6) {
confirmedAvailableForAnchor++;
}
}
}
}
res.json({
availableForAnchor,
confirmedAvailableForAnchor,
anchors,
});
} catch (error) {
logger.error('Error getting UTXO count', { error: error.message });
res.status(500).json({ error: error.message });
}
});
// Route pour obtenir la liste des UTXO (fichier texte) // Route pour obtenir la liste des UTXO (fichier texte)
app.get('/api/utxo/list', async (req, res) => { app.get('/api/utxo/list', async (req, res) => {
try { try {