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
* Optimisé pour charger d'abord les données critiques, puis les données moins critiques
*/
async function loadData() {
try {
// Phase 1 : Charger les données critiques rapidement (affichage immédiat)
await Promise.all([
loadBlockchainInfo(),
loadLatestBlock(),
loadWalletBalance(),
loadAnchorCount(),
loadAvailableForAnchor(),
loadNetworkPeers(),
loadMiningDifficulty(),
loadAvgBlockTime(),
loadAvgFee(),
loadAvgTxAmount(),
]);
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) {
console.error('Error loading data:', error);
}
@ -190,13 +199,13 @@ async function loadAnchorCount() {
/**
* Charge la capacité d'ancrage restante
* Utilise l'endpoint optimisé /api/utxo/count pour éviter de charger toute la liste
*/
async function loadAvailableForAnchor() {
const availableForAnchorValue = document.getElementById('available-for-anchor-value');
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');
return;
}
@ -204,24 +213,21 @@ async function loadAvailableForAnchor() {
// Afficher le spinner
availableForAnchorSpinner.style.display = 'inline';
availableForAnchorValue.textContent = '...';
confirmedAvailableForAnchorValue.textContent = '...';
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) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
const counts = data.counts || {};
const availableForAnchor = counts.availableForAnchor || 0;
const confirmedAvailableForAnchor = counts.confirmedAvailableForAnchor || 0;
const availableForAnchor = data.availableForAnchor || 0;
// Masquer le spinner et mettre à jour les valeurs
// Masquer le spinner et mettre à jour la valeur
availableForAnchorSpinner.style.display = 'none';
availableForAnchorValue.textContent = availableForAnchor.toLocaleString('fr-FR') + ' ancrages';
confirmedAvailableForAnchorValue.textContent = confirmedAvailableForAnchor.toLocaleString('fr-FR');
} catch (error) {
console.error('Error loading available for anchor:', error);
// Masquer le spinner
@ -231,7 +237,6 @@ async function loadAvailableForAnchor() {
if (currentValue === '-' || currentValue === 'Erreur' || currentValue === '...') {
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)
app.get('/api/utxo/list', async (req, res) => {
try {