Compare commits

..

2 Commits

Author SHA1 Message Date
7d72c2de0e feat: Add namehash redirect and remove status panel
All checks were successful
Build Docker / BuildImage (push) Successful in 46s
Check Code Quality / RuffCheck (push) Successful in 1m1s
2025-11-20 17:23:05 +11:00
7cb71baf00 feat: Add name info 2025-11-20 17:20:51 +11:00

View File

@@ -20,10 +20,6 @@
<main class="container"> <main class="container">
<!-- Status Cards --> <!-- Status Cards -->
<section class="status-section"> <section class="status-section">
<div class="card status-card">
<h3>Node Status</h3>
<div id="node-status" class="status-content">Loading...</div>
</div>
<div class="card status-card"> <div class="card status-card">
<h3>Chain Info</h3> <h3>Chain Info</h3>
<div id="chain-status" class="status-content">Loading...</div> <div id="chain-status" class="status-content">Loading...</div>
@@ -522,6 +518,228 @@
return html; return html;
} }
// Format name info nicely
function formatNameInfo(data) {
if (!data || data.error) {
return `<div class="error">Error: ${data.error || 'Invalid name data'}</div>`;
}
const info = data.info;
const start = data.start;
const formatValue = (value) => (value / 1e6).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) + ' HNS';
const formatDate = (timestamp) => new Date(timestamp * 1000).toLocaleString();
let html = `
<div class="tx-details">
<div class="tx-section">
<h4>Name: ${info.name}</h4>
<div class="info-grid">
<div class="info-item"><strong>State:</strong> <span style="color: #f7931e;">${info.state}</span></div>
<div class="info-item"><strong>Registered:</strong> ${info.registered ? 'Yes' : 'No'}</div>
<div class="info-item"><strong>Height:</strong> ${info.height.toLocaleString()}</div>
<div class="info-item"><strong>Value:</strong> ${formatValue(info.value)}</div>
<div class="info-item"><strong>Highest Bid:</strong> ${formatValue(info.highest)}</div>
<div class="info-item"><strong>Renewals:</strong> ${info.renewals}</div>
<div class="info-item"><strong>Next Renewal:</strong> Height ${info.renewal.toLocaleString()}</div>
<div class="info-item"><strong>Expired:</strong> ${info.expired ? 'Yes' : 'No'}</div>
<div class="info-item"><strong>Revoked:</strong> ${info.revoked > 0 ? 'Yes' : 'No'}</div>
<div class="info-item"><strong>Weak:</strong> ${info.weak ? 'Yes' : 'No'}</div>
</div>
</div>
${info.stats ? `
<div class="tx-section">
<h4>Expiration Info</h4>
<div class="info-grid">
<div class="info-item"><strong>Blocks Until Expire:</strong> ${info.stats.blocksUntilExpire.toLocaleString()}</div>
<div class="info-item"><strong>Days Until Expire:</strong> ${info.stats.daysUntilExpire.toFixed(2)}</div>
<div class="info-item"><strong>Renewal Period Start:</strong> ${info.stats.renewalPeriodStart.toLocaleString()}</div>
<div class="info-item"><strong>Renewal Period End:</strong> ${info.stats.renewalPeriodEnd.toLocaleString()}</div>
</div>
</div>
` : ''}
<div class="tx-section">
<h4>Owner</h4>
<div style="cursor: pointer; padding: 1rem; background: rgba(255, 107, 53, 0.1); border-radius: 8px;" onclick="window.location.href='/tx/${info.owner.hash}'">
<div class="mono" style="word-break: break-all; color: #ff6b35;">TX: ${info.owner.hash}</div>
<div style="color: #b0b0b0; margin-top: 0.5rem;">Output Index: ${info.owner.index}</div>
</div>
</div>
<div class="tx-section">
<h4>Name Hash</h4>
<div class="mono" style="word-break: break-all; padding: 1rem; background: rgba(255, 107, 53, 0.05); border-radius: 8px;">${info.nameHash}</div>
</div>
${start ? `
<div class="tx-section">
<h4>Auction Info</h4>
<div class="info-grid">
<div class="info-item"><strong>Start Height:</strong> ${start.start.toLocaleString()}</div>
<div class="info-item"><strong>Week:</strong> ${start.week}</div>
<div class="info-item"><strong>Reserved:</strong> ${start.reserved ? 'Yes' : 'No'}</div>
<div class="info-item"><strong>Locked:</strong> ${start.locked ? 'Yes' : 'No'}</div>
</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(data, null, 2)}</pre>
</div>
</div>
`;
return html;
}
// Format name resource records nicely
function formatNameResource(data) {
if (!data || data.error) {
return `<div class="error">Error: ${data.error || 'Invalid resource data'}</div>`;
}
if (!data.records || data.records.length === 0) {
return `<div class="error">No resource records found</div>`;
}
const records = data.records;
let html = `
<div class="tx-details">
<div class="tx-section">
<h4>DNS Records (${records.length})</h4>
<div class="tx-io-list">
${records.map((record, i) => {
let recordDetails = `<strong>${record.type}</strong>`;
switch(record.type) {
case 'NS':
recordDetails += `${record.ns}`;
break;
case 'DS':
recordDetails += `<br><span style="font-size: 0.9rem;">KeyTag: ${record.keyTag}, Algorithm: ${record.algorithm}, DigestType: ${record.digestType}</span><br><span class="mono" style="font-size: 0.85rem; color: #b0b0b0; word-break: break-all;">${record.digest}</span>`;
break;
case 'TXT':
recordDetails += `<br><span style="color: #b0b0b0;">${record.txt.map(t => `"${t}"`).join('<br>')}</span>`;
break;
case 'GLUE4':
case 'GLUE6':
recordDetails += `${record.ns}<br><span style="color: #f7931e;">${record.address}</span>`;
break;
case 'SYNTH4':
case 'SYNTH6':
recordDetails += ` → <span style="color: #f7931e;">${record.address}</span>`;
break;
}
return `
<div class="tx-io-item">
<div class="tx-io-header">
<span class="tx-io-index">Record #${i}</span>
</div>
<div style="margin-top: 0.5rem;">${recordDetails}</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(data, null, 2)}</pre>
</div>
</div>
`;
return html;
}
// Format name summary nicely
function formatNameSummary(data) {
if (!data || data.error) {
return `<div class="error">Error: ${data.error || 'Invalid summary data'}</div>`;
}
const formatValue = (value) => (value).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) + ' HNS';
const formatDate = (timestamp) => new Date(timestamp * 1000).toLocaleString();
let html = `
<div class="tx-details">
<div class="tx-section">
<h4>Name: ${data.name}</h4>
<div class="info-grid">
<div class="info-item"><strong>State:</strong> <span style="color: #f7931e;">${data.state}</span></div>
<div class="info-item"><strong>Height:</strong> ${data.height.toLocaleString()}</div>
<div class="info-item"><strong>Value:</strong> ${formatValue(data.value)}</div>
<div class="info-item"><strong>Blocks Until Expire:</strong> ${data.blocksUntilExpire.toLocaleString()}</div>
<div class="info-item"><strong>Mint Date:</strong> ${formatDate(data.mintTimestamp)}</div>
</div>
</div>
<div class="tx-section">
<h4>Owner Address</h4>
<div style="cursor: pointer; padding: 1rem; background: rgba(255, 107, 53, 0.1); border-radius: 8px;" onclick="window.location.href='/address/${data.owner}'">
<div class="mono" style="word-break: break-all; color: #ff6b35;">${data.owner}</div>
</div>
</div>
<div class="tx-section">
<h4>Name Hash</h4>
<div class="mono" style="word-break: break-all; padding: 1rem; background: rgba(255, 107, 53, 0.05); border-radius: 8px;">${data.hash}</div>
</div>
${data.resources && data.resources[0] && data.resources[0].records ? `
<div class="tx-section">
<h4>DNS Records (${data.resources[0].records.length})</h4>
<div class="tx-io-list">
${data.resources[0].records.map((record, i) => {
let recordDetails = `<strong>${record.type}</strong>`;
switch(record.type) {
case 'NS':
recordDetails += `${record.ns}`;
break;
case 'DS':
recordDetails += `<br><span style="font-size: 0.9rem;">KeyTag: ${record.keyTag}, Algorithm: ${record.algorithm}</span>`;
break;
case 'TXT':
recordDetails += `<br><span style="color: #b0b0b0;">${record.txt.map(t => `"${t}"`).join('<br>')}</span>`;
break;
case 'GLUE4':
case 'GLUE6':
recordDetails += `${record.ns}<br><span style="color: #f7931e;">${record.address}</span>`;
break;
case 'SYNTH4':
case 'SYNTH6':
recordDetails += ` → <span style="color: #f7931e;">${record.address}</span>`;
break;
}
return `
<div class="tx-io-item">
<div class="tx-io-header">
<span class="tx-io-index">Record #${i}</span>
</div>
<div style="margin-top: 0.5rem;">${recordDetails}</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(data, null, 2)}</pre>
</div>
</div>
`;
return html;
}
// Format transaction data nicely // Format transaction data nicely
function formatTransactionData(tx) { function formatTransactionData(tx) {
if (!tx || tx.error) { if (!tx || tx.error) {
@@ -650,9 +868,6 @@
// Load status on page load // Load status on page load
async function loadStatus() { async function loadStatus() {
const nodeStatus = await apiCall('status');
displayResult('node-status', nodeStatus, 'status');
const chainStatus = await apiCall('chain'); const chainStatus = await apiCall('chain');
if (chainStatus.chain) { if (chainStatus.chain) {
document.getElementById('chain-status').innerHTML = formatChainData(chainStatus.chain); document.getElementById('chain-status').innerHTML = formatChainData(chainStatus.chain);
@@ -767,7 +982,14 @@
} }
updateURL('name', name); updateURL('name', name);
const data = await apiCall(`name/${name}`); const data = await apiCall(`name/${name}`);
displayResult('name-result', data);
// Use formatted display
const resultElement = document.getElementById('name-result');
if (data.error) {
resultElement.innerHTML = `<div class="error">Error: ${data.error}</div>`;
} else {
resultElement.innerHTML = formatNameInfo(data);
}
} }
async function searchNameResource() { async function searchNameResource() {
@@ -777,7 +999,14 @@
return; return;
} }
const data = await apiCall(`nameresource/${name}`); const data = await apiCall(`nameresource/${name}`);
displayResult('name-result', data);
// Use formatted display
const resultElement = document.getElementById('name-result');
if (data.error) {
resultElement.innerHTML = `<div class="error">Error: ${data.error}</div>`;
} else {
resultElement.innerHTML = formatNameResource(data);
}
} }
async function searchNameSummary() { async function searchNameSummary() {
@@ -787,7 +1016,14 @@
return; return;
} }
const data = await apiCall(`namesummary/${name}`); const data = await apiCall(`namesummary/${name}`);
displayResult('name-result', data);
// Use formatted display
const resultElement = document.getElementById('name-result');
if (data.error) {
resultElement.innerHTML = `<div class="error">Error: ${data.error}</div>`;
} else {
resultElement.innerHTML = formatNameSummary(data);
}
} }
async function searchNameHash() { async function searchNameHash() {
@@ -797,7 +1033,17 @@
return; return;
} }
const data = await apiCall(`namehash/${nameHash}`); const data = await apiCall(`namehash/${nameHash}`);
displayResult('name-result', data);
// Check if result is valid and redirect to name page
const resultElement = document.getElementById('name-result');
if (data.error) {
resultElement.innerHTML = `<div class="error">Error: ${data.error.message ? data.error.message : "Failed to lookup hash"}</div>`;
} else if (data.result && typeof data.result === 'string') {
// Valid name found, redirect to name page
window.location.href = `/name/${data.result}`;
} else {
resultElement.innerHTML = `<div class="error">No name found for this hash</div>`;
}
} }
async function searchCoin() { async function searchCoin() {