reorder txns newest on top; show confirmations tooltip
This commit is contained in:
@@ -131,6 +131,34 @@ def list_transactions():
|
||||
return jsonify({"error": str(exc)}), 500
|
||||
|
||||
|
||||
@app.route("/api/network", methods=["GET"])
|
||||
@require_token
|
||||
def get_network_info():
|
||||
try:
|
||||
network_info = rpc_call("getnetworkinfo")
|
||||
peer_info = rpc_call("getpeerinfo")
|
||||
mempool = rpc_call("getrawmempool")
|
||||
blockchain_info = rpc_call("getblockchaininfo")
|
||||
|
||||
return jsonify({
|
||||
"network": {
|
||||
"connections": network_info.get("connections", 0),
|
||||
"networkactive": network_info.get("networkactive", False),
|
||||
"relayfee": network_info.get("relayfee", 0),
|
||||
},
|
||||
"peers": len(peer_info),
|
||||
"mempool_size": len(mempool),
|
||||
"blockchain": {
|
||||
"blocks": blockchain_info.get("blocks", 0),
|
||||
"headers": blockchain_info.get("headers", 0),
|
||||
"difficulty": blockchain_info.get("difficulty", 0),
|
||||
"chain": blockchain_info.get("chain", "unknown"),
|
||||
}
|
||||
})
|
||||
except RuntimeError as exc:
|
||||
return jsonify({"error": str(exc)}), 500
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def root():
|
||||
return send_from_directory(app.static_folder, "index.html")
|
||||
|
||||
@@ -147,6 +147,14 @@
|
||||
background: rgba(255, 152, 0, 0.2);
|
||||
color: #ff9800;
|
||||
}
|
||||
.tx-link {
|
||||
color: #29b6f6;
|
||||
text-decoration: none;
|
||||
word-break: break-all;
|
||||
}
|
||||
.tx-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
body {
|
||||
flex-direction: column;
|
||||
@@ -207,6 +215,13 @@
|
||||
<h2>Recent Activity</h2>
|
||||
<ul class="transaction-list" id="txList"></ul>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Network Status</h2>
|
||||
<div id="networkInfo">
|
||||
<div class="muted">Loading network information...</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
@@ -217,6 +232,7 @@
|
||||
const sendStatus = document.getElementById('sendStatus');
|
||||
const generatedAddressCard = document.getElementById('generatedAddressCard');
|
||||
const generatedAddress = document.getElementById('generatedAddress');
|
||||
const networkInfo = document.getElementById('networkInfo');
|
||||
|
||||
let apiToken = localStorage.getItem('rinWebWalletToken');
|
||||
|
||||
@@ -268,22 +284,25 @@
|
||||
const type = tx.category === 'send' ? 'Sent' : 'Received';
|
||||
const confirmations = tx.confirmations || 0;
|
||||
|
||||
let status, statusClass;
|
||||
let status, statusClass, tooltip = '';
|
||||
if (confirmations === 0) {
|
||||
status = 'Pending';
|
||||
statusClass = 'status-pending';
|
||||
tooltip = 'title="0 confirmations - waiting for network confirmation"';
|
||||
} else if (confirmations < 20) {
|
||||
status = `Immature (${confirmations}/20)`;
|
||||
statusClass = 'status-immature';
|
||||
tooltip = `title="${confirmations} confirmations - needs 20 for full confirmation"`;
|
||||
} else {
|
||||
status = 'Confirmed';
|
||||
statusClass = 'status-confirmed';
|
||||
tooltip = `title="${confirmations} confirmations - fully confirmed"`;
|
||||
}
|
||||
|
||||
li.innerHTML = `
|
||||
<div><strong>${type}</strong> ${amount} RIN <span class="status-badge ${statusClass}">${status}</span></div>
|
||||
<div><strong>${type}</strong> ${amount} RIN <span class="status-badge ${statusClass}" ${tooltip}>${status}</span></div>
|
||||
<div class="muted">${tx.time ? new Date(tx.time * 1000).toLocaleString() : ''}</div>
|
||||
<div class="muted">${tx.txid}</div>
|
||||
<div class="muted"><a href="https://explorer.rin.so/tx/${tx.txid}" target="_blank" class="tx-link">${tx.txid}</a></div>
|
||||
`;
|
||||
return li;
|
||||
}
|
||||
@@ -308,6 +327,8 @@
|
||||
txList.innerHTML = '<li class="muted">No transactions yet.</li>';
|
||||
return;
|
||||
}
|
||||
// Sort transactions by time (newest first)
|
||||
data.transactions.sort((a, b) => (b.time || 0) - (a.time || 0));
|
||||
data.transactions.forEach((tx) => txList.appendChild(renderTx(tx)));
|
||||
}
|
||||
|
||||
@@ -381,8 +402,54 @@
|
||||
await refresh();
|
||||
}
|
||||
|
||||
async function fetchNetworkInfo() {
|
||||
let res = await fetch('/api/network', { headers: authHeaders() });
|
||||
let data = await res.json();
|
||||
|
||||
// If token is invalid, refresh and retry
|
||||
if (data.error === 'invalid_token') {
|
||||
await refreshToken();
|
||||
res = await fetch('/api/network', { headers: authHeaders() });
|
||||
data = await res.json();
|
||||
}
|
||||
|
||||
if (data.error) {
|
||||
networkInfo.innerHTML = `<div class="muted">Error: ${data.error}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const network = data.network;
|
||||
const blockchain = data.blockchain;
|
||||
|
||||
networkInfo.innerHTML = `
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px;">
|
||||
<div>
|
||||
<div class="muted">Network Connections</div>
|
||||
<div style="font-size: 18px; font-weight: 600;">${network.connections}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted">Mempool Size</div>
|
||||
<div style="font-size: 18px; font-weight: 600;">${data.mempool_size}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted">Block Height</div>
|
||||
<div style="font-size: 18px; font-weight: 600;">${blockchain.blocks}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted">Chain</div>
|
||||
<div style="font-size: 18px; font-weight: 600;">${blockchain.chain}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="muted" style="font-size: 12px;">
|
||||
Network Active: ${network.networkactive ? 'Yes' : 'No'} |
|
||||
Relay Fee: ${network.relayfee} RIN |
|
||||
Difficulty: ${blockchain.difficulty.toFixed(2)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
await Promise.all([fetchBalances(), fetchTransactions()]);
|
||||
await Promise.all([fetchBalances(), fetchTransactions(), fetchNetworkInfo()]);
|
||||
}
|
||||
|
||||
document.getElementById('refreshButton').addEventListener('click', refresh);
|
||||
|
||||
Reference in New Issue
Block a user