Add available anchor capacity to main dashboard

**Motivations:**
- Afficher la capacité d'ancrage restante directement sur le dashboard principal
- Permettre un suivi rapide du nombre d'UTXOs disponibles pour l'ancrage

**Root causes:**
- La capacité d'ancrage restante n'était visible que sur la page de liste des UTXOs
- Pas de visibilité immédiate sur le dashboard principal

**Correctifs:**
- Ajout d'une carte "Capacité d'Ancrage Restante" dans le dashboard principal
- Affichage du nombre d'ancrages disponibles et du nombre d'UTXOs confirmés

**Evolutions:**
- Nouvelle carte dans la section "État de la Blockchain" affichant :
  - Le nombre d'ancrages restants (format: "X ancrages")
  - Le nombre d'UTXOs confirmés disponibles (sous-titre)
- Fonction loadAvailableForAnchor() qui charge les données depuis /api/utxo/list
- Spinner de chargement pendant la récupération des données
- Gestion d'erreur avec valeurs par défaut

**Pages affectées:**
- signet-dashboard/public/index.html: Ajout de la carte "Capacité d'Ancrage Restante"
- signet-dashboard/public/app.js: Fonction loadAvailableForAnchor() et intégration dans loadData()
This commit is contained in:
ncantu 2026-01-25 23:31:29 +01:00
parent e86ac5a0d9
commit 15edb1bac1
2 changed files with 125 additions and 24 deletions

View File

@ -69,6 +69,7 @@ async function loadData() {
loadLatestBlock(),
loadWalletBalance(),
loadAnchorCount(),
loadAvailableForAnchor(),
loadNetworkPeers(),
loadMiningDifficulty(),
loadAvgBlockTime(),
@ -187,6 +188,53 @@ async function loadAnchorCount() {
}
}
/**
* Charge la capacité d'ancrage restante
*/
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) {
console.error('Elements for available-for-anchor not found in DOM');
return;
}
// Afficher le spinner
availableForAnchorSpinner.style.display = 'inline';
availableForAnchorValue.textContent = '...';
confirmedAvailableForAnchorValue.textContent = '...';
try {
const response = await fetch(`${API_BASE_URL}/api/utxo/list`);
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;
// Masquer le spinner et mettre à jour les valeurs
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
availableForAnchorSpinner.style.display = 'none';
// Afficher une valeur par défaut en cas d'erreur
const currentValue = availableForAnchorValue.textContent;
if (currentValue === '-' || currentValue === 'Erreur' || currentValue === '...') {
availableForAnchorValue.textContent = '0 ancrages';
}
confirmedAvailableForAnchorValue.textContent = '0';
}
}
/**
* Charge les informations sur les pairs
*/
@ -476,11 +524,21 @@ function handleFileSelect(event) {
if (file) {
selectedFile = file;
const fileInfo = document.getElementById('file-info');
fileInfo.innerHTML = `
const maxSize = 100 * 1024 * 1024; // 100 MB en bytes
const fileSize = file.size;
const isOverLimit = fileSize > maxSize;
let infoHtml = `
<strong>Fichier sélectionné :</strong> ${file.name}<br>
<strong>Taille :</strong> ${formatFileSize(file.size)}<br>
<strong>Type :</strong> ${file.type || 'Non spécifié'}
`;
if (isOverLimit) {
infoHtml += `<br><span style="color: #d32f2f; font-weight: bold;">⚠️ Fichier trop volumineux (limite : 100 MB)</span>`;
}
fileInfo.innerHTML = infoHtml;
}
}
@ -537,34 +595,64 @@ async function generateHashFromFile() {
return;
}
try {
const reader = new FileReader();
reader.onload = async (e) => {
const fileContent = e.target.result;
// Vérifier la taille du fichier (limite : 100 MB)
const maxSize = 100 * 1024 * 1024; // 100 MB en bytes
if (selectedFile.size > maxSize) {
showResult('anchor-result', 'error', `Le fichier est trop volumineux (${formatFileSize(selectedFile.size)}). La limite est de 100 MB.`);
return;
}
try {
const reader = new FileReader();
await new Promise((resolve, reject) => {
reader.onload = async (e) => {
try {
const arrayBuffer = e.target.result;
// Convertir l'ArrayBuffer en base64 pour l'envoi au backend
// Utiliser une boucle pour éviter les limites de taille des arguments de fonction
const uint8Array = new Uint8Array(arrayBuffer);
let binaryString = '';
for (let i = 0; i < uint8Array.length; i++) {
binaryString += String.fromCharCode(uint8Array[i]);
}
const base64 = btoa(binaryString);
const response = await fetch(`${API_BASE_URL}/api/hash/generate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ fileContent }),
body: JSON.stringify({ fileContent: base64, isBase64: true }),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({ error: `HTTP ${response.status}: ${response.statusText}` }));
throw new Error(errorData.error || `HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.hash) {
document.getElementById('anchor-hash').value = data.hash;
showResult('anchor-result', 'success', `Hash généré avec succès : ${data.hash}`);
} else {
showResult('anchor-result', 'error', 'Erreur lors de la génération du hash.');
showResult('anchor-result', 'error', data.error || 'Erreur lors de la génération du hash.');
}
resolve();
} catch (error) {
showResult('anchor-result', 'error', `Erreur : ${error.message}`);
reject(error);
}
};
reader.readAsText(selectedFile);
reader.onerror = (error) => {
showResult('anchor-result', 'error', `Erreur lors de la lecture du fichier : ${error.message || 'Erreur inconnue'}`);
reject(error);
};
reader.readAsArrayBuffer(selectedFile);
});
} catch (error) {
showResult('anchor-result', 'error', `Erreur lors de la lecture du fichier : ${error.message}`);
}

View File

@ -65,6 +65,16 @@
<span id="anchor-count-spinner" class="spinner" style="display: none;"></span>
</p>
</div>
<div class="card">
<h3>Capacité d'Ancrage Restante</h3>
<p class="value" id="available-for-anchor">
<span id="available-for-anchor-value">-</span>
<span id="available-for-anchor-spinner" class="spinner" style="display: none;"></span>
</p>
<p class="sub-value" id="confirmed-available-for-anchor" style="font-size: 0.9em; color: #666; margin-top: 5px;">
<span id="confirmed-available-for-anchor-value">-</span> UTXOs confirmés
</p>
</div>
<div class="card">
<h3>Nombre de Pairs</h3>
<p class="value" id="peer-count">-</p>
@ -118,6 +128,9 @@
<div id="file-tab" class="tab-content">
<label for="anchor-file">Fichier à ancrer :</label>
<input type="file" id="anchor-file" onchange="handleFileSelect(event)">
<p class="file-limit-info" style="font-size: 0.9em; color: #666; margin-top: 5px; margin-bottom: 10px;">
<strong>Limite de taille :</strong> 100 MB maximum
</p>
<div id="file-info" class="file-info"></div>
<button onclick="generateHashFromFile()">Générer le Hash</button>
</div>
@ -209,8 +222,8 @@
<p>Bitcoin Ancrage Dashboard - Équipe 4NK</p>
<p>Dernière mise à jour : <span id="last-update">-</span></p>
<a href="https://git.4nkweb.com/4nk/anchorage_layer_simple.git" target="_blank" rel="noopener noreferrer" class="git-link" title="Voir le code source sur Git">
<svg class="git-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
<svg class="git-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path d="M23.546 10.93L13.067.452c-.604-.603-1.582-.603-2.188 0L8.708 2.627l2.76 2.76c.645-.215 1.379-.07 1.889.441.516.515.658 1.258.438 1.9l2.658 2.66c.645-.223 1.387-.083 1.9.435.721.72.721 1.884 0 2.604-.719.719-1.881.719-2.6 0-.539-.541-.674-1.337-.404-1.996L12.86 8.955v6.525c.176.086.342.203.488.348.713.721.713 1.883 0 2.6-.719.721-1.884.721-2.599 0-.72-.719-.72-1.879 0-2.598.182-.18.387-.316.605-.406V8.835c-.217-.091-.424-.222-.6-.401-.545-.545-.676-1.342-.396-2.011L7.636 3.7.45 10.881c-.6.605-.6 1.584 0 2.189l10.48 10.477c.604.604 1.582.604 2.186 0l10.43-10.43c.605-.603.605-1.582 0-2.187"/>
</svg>
</a>
</footer>