feat: Add coin rendering
All checks were successful
Build Docker / BuildImage (push) Successful in 34s
Check Code Quality / RuffCheck (push) Successful in 47s

This commit is contained in:
2025-11-20 17:14:01 +11:00
parent 65eea87568
commit ba2002574c
2 changed files with 156 additions and 2 deletions

View File

@@ -103,6 +103,12 @@ def name_route(name):
return render_template("index.html", datetime=current_datetime)
@app.route("/coin/<path:coin_hash>/<int:index>")
def coin_route(coin_hash, index):
current_datetime = datetime.now().strftime("%d %b %Y %I:%M %p")
return render_template("index.html", datetime=current_datetime)
@app.route("/<path:path>")
def catch_all(path: str):
if os.path.isfile("templates/" + path):

View File

@@ -169,6 +169,11 @@
searchName();
break;
}
} else if (parts.length === 3 && parts[0] === 'coin') {
// Handle coin URLs: /coin/hash/index
document.getElementById('coin-hash').value = parts[1];
document.getElementById('coin-index').value = parts[2];
searchCoin();
}
}
@@ -390,6 +395,133 @@
return html;
}
// Format address coins nicely
function formatAddressCoins(coins) {
if (!coins || coins.error) {
return `<div class="error">Error: ${coins.error || 'Invalid coin data'}</div>`;
}
if (!Array.isArray(coins) || coins.length === 0) {
return `<div class="error">No coins found for this address</div>`;
}
const formatValue = (value) => (value / 1e6).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) + ' HNS';
const totalValue = coins.reduce((sum, coin) => sum + coin.value, 0);
let html = `
<div class="tx-details">
<div class="tx-section">
<h4>Coins (${coins.length}) - Total: ${formatValue(totalValue)}</h4>
<div class="tx-io-list">
${coins.map((coin, i) => `
<div class="tx-io-item">
<div class="tx-io-header">
<span class="tx-io-index">Output #${coin.index}</span>
<span class="tx-io-value">${formatValue(coin.value)}</span>
</div>
<div class="tx-io-address">${coin.address}</div>
<div style="cursor: pointer;" onclick="window.location.href='/tx/${coin.hash}'">
<div class="tx-io-hash mono">${coin.hash}</div>
</div>
<div style="display: flex; justify-content: space-between; margin-top: 0.5rem; font-size: 0.85rem; color: #b0b0b0;">
<span>Height: ${coin.height.toLocaleString()}</span>
<span>Coinbase: ${coin.coinbase ? 'Yes' : 'No'}</span>
<span>Covenant: ${coin.covenant.action}</span>
</div>
</div>
`).join('')}
</div>
</div>
<div class="tx-section">
<button class="secondary-btn" onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? 'block' : 'none'">Show Raw JSON</button>
<pre style="display: none;">${JSON.stringify(coins, null, 2)}</pre>
</div>
</div>
`;
return html;
}
// Format individual coin nicely
function formatCoin(coin) {
if (!coin || coin.error) {
return `<div class="error">Error: ${coin.error || 'Invalid coin data'}</div>`;
}
const formatValue = (value) => (value / 1e6).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) + ' HNS';
let html = `
<div class="tx-details">
<div class="tx-section">
<h4>Coin Information</h4>
<div class="info-grid">
<div class="info-item">
<label>Value:</label>
<span>${formatValue(coin.value)}</span>
</div>
<div class="info-item">
<label>Output Index:</label>
<span>${coin.index}</span>
</div>
<div class="info-item">
<label>Height:</label>
<span>${coin.height.toLocaleString()}</span>
</div>
<div class="info-item">
<label>Coinbase:</label>
<span>${coin.coinbase ? 'Yes' : 'No'}</span>
</div>
<div class="info-item">
<label>Version:</label>
<span>${coin.version}</span>
</div>
<div class="info-item">
<label>Covenant:</label>
<span>${coin.covenant.action}</span>
</div>
</div>
</div>
<div class="tx-section">
<h4>Transaction Hash</h4>
<div style="cursor: pointer; padding: 1rem; background: rgba(255, 107, 53, 0.1); border-radius: 8px;" onclick="window.location.href='/tx/${coin.hash}'">
<div class="mono" style="word-break: break-all; color: #ff6b35;">${coin.hash}</div>
</div>
</div>
<div class="tx-section">
<h4>Address</h4>
<div style="cursor: pointer; padding: 1rem; background: rgba(255, 107, 53, 0.1); border-radius: 8px;" onclick="window.location.href='/address/${coin.address}'">
<div class="mono" style="word-break: break-all; color: #ff6b35;">${coin.address}</div>
</div>
</div>
${coin.covenant.items && coin.covenant.items.length > 0 ? `
<div class="tx-section">
<h4>Covenant Items</h4>
<div style="background: rgba(255, 107, 53, 0.05); padding: 1rem; border-radius: 8px;">
${coin.covenant.items.map((item, i) => `
<div style="margin-bottom: 0.5rem;">
<span style="color: #b0b0b0;">Item ${i}:</span>
<span class="mono" style="word-break: break-all;">${item}</span>
</div>
`).join('')}
</div>
</div>
` : ''}
<div class="tx-section">
<button class="secondary-btn" onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? 'block' : 'none'">Show Raw JSON</button>
<pre style="display: none;">${JSON.stringify(coin, null, 2)}</pre>
</div>
</div>
`;
return html;
}
// Format transaction data nicely
function formatTransactionData(tx) {
if (!tx || tx.error) {
@@ -599,8 +731,16 @@
alert('Please enter an address');
return;
}
updateURL('address', address);
const data = await apiCall(`coin/address/${address}`);
displayResult('address-result', data);
// Use formatted display instead of raw JSON
const resultElement = document.getElementById('address-result');
if (data.error) {
resultElement.innerHTML = `<div class="error">Error: ${data.error}</div>`;
} else {
resultElement.innerHTML = formatAddressCoins(data);
}
}
async function searchName() {
@@ -651,8 +791,16 @@
alert('Please enter both coin hash and index');
return;
}
updateURL('coin', `${coinHash}/${coinIndex}`);
const data = await apiCall(`coin/${coinHash}/${coinIndex}`);
displayResult('coin-result', data);
// Use formatted display instead of raw JSON
const resultElement = document.getElementById('coin-result');
if (data.error) {
resultElement.innerHTML = `<div class="error">Error: ${data.error}</div>`;
} else {
resultElement.innerHTML = formatCoin(data);
}
}
// Load status when page loads