mirror of
https://github.com/Retropex/custom-ocean.xyz-dashboard.git
synced 2025-05-12 19:20:45 +02:00

Introduced a new `getPoolColor(poolName)` function to map mining pool names to specific colors, improving visual representation in the UI. Updated `createBlockCard(block)` and `showBlockDetails(block)` functions to utilize this new function, applying distinct styles for Ocean pools. These changes enhance user experience by providing clear, color-coded cues for different mining pools.
1094 lines
32 KiB
JavaScript
1094 lines
32 KiB
JavaScript
"use strict";
|
|
|
|
// Global variables
|
|
let currentStartHeight = null;
|
|
const mempoolBaseUrl = "https://mempool.space";
|
|
let blocksCache = {};
|
|
let isLoading = false;
|
|
|
|
// DOM ready initialization
|
|
$(document).ready(function() {
|
|
console.log("Blocks page initialized");
|
|
|
|
// Initialize notification badge
|
|
initNotificationBadge();
|
|
|
|
// Load the latest blocks on page load
|
|
loadLatestBlocks();
|
|
|
|
// Set up event listeners
|
|
$("#load-blocks").on("click", function() {
|
|
const height = $("#block-height").val();
|
|
if (height && !isNaN(height)) {
|
|
loadBlocksFromHeight(height);
|
|
} else {
|
|
showToast("Please enter a valid block height");
|
|
}
|
|
});
|
|
|
|
$("#latest-blocks").on("click", loadLatestBlocks);
|
|
|
|
// Handle Enter key on the block height input
|
|
$("#block-height").on("keypress", function(e) {
|
|
if (e.which === 13) {
|
|
const height = $(this).val();
|
|
if (height && !isNaN(height)) {
|
|
loadBlocksFromHeight(height);
|
|
} else {
|
|
showToast("Please enter a valid block height");
|
|
}
|
|
}
|
|
});
|
|
|
|
// Close the modal when clicking the X or outside the modal
|
|
$(".block-modal-close").on("click", closeModal);
|
|
$(window).on("click", function(event) {
|
|
if ($(event.target).hasClass("block-modal")) {
|
|
closeModal();
|
|
}
|
|
});
|
|
|
|
// Initialize BitcoinMinuteRefresh if available
|
|
if (typeof BitcoinMinuteRefresh !== 'undefined' && BitcoinMinuteRefresh.initialize) {
|
|
BitcoinMinuteRefresh.initialize(loadLatestBlocks);
|
|
console.log("BitcoinMinuteRefresh initialized with refresh function");
|
|
}
|
|
});
|
|
|
|
// Update unread notifications badge in navigation
|
|
function updateNotificationBadge() {
|
|
$.ajax({
|
|
url: "/api/notifications/unread_count",
|
|
method: "GET",
|
|
success: function (data) {
|
|
const unreadCount = data.unread_count;
|
|
const badge = $("#nav-unread-badge");
|
|
|
|
if (unreadCount > 0) {
|
|
badge.text(unreadCount).show();
|
|
} else {
|
|
badge.hide();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Initialize notification badge checking
|
|
function initNotificationBadge() {
|
|
// Update immediately
|
|
updateNotificationBadge();
|
|
|
|
// Update every 60 seconds
|
|
setInterval(updateNotificationBadge, 60000);
|
|
}
|
|
// Helper function to format timestamps as readable dates
|
|
function formatTimestamp(timestamp) {
|
|
const date = new Date(timestamp * 1000);
|
|
const options = {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit',
|
|
hour12: true
|
|
};
|
|
return date.toLocaleString('en-US', options);
|
|
}
|
|
|
|
// Helper function to format numbers with commas
|
|
function numberWithCommas(x) {
|
|
if (x == null) return "N/A";
|
|
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
}
|
|
|
|
// Helper function to format file sizes
|
|
function formatFileSize(bytes) {
|
|
if (bytes < 1024) return bytes + " B";
|
|
else if (bytes < 1048576) return (bytes / 1024).toFixed(2) + " KB";
|
|
else return (bytes / 1048576).toFixed(2) + " MB";
|
|
}
|
|
|
|
// Helper function to show toast messages
|
|
function showToast(message) {
|
|
// Check if we already have a toast container
|
|
let toastContainer = $(".toast-container");
|
|
if (toastContainer.length === 0) {
|
|
// Create a new toast container
|
|
toastContainer = $("<div>", {
|
|
class: "toast-container",
|
|
css: {
|
|
position: "fixed",
|
|
bottom: "20px",
|
|
right: "20px",
|
|
zIndex: 9999
|
|
}
|
|
}).appendTo("body");
|
|
}
|
|
|
|
// Create a new toast
|
|
const toast = $("<div>", {
|
|
class: "toast",
|
|
text: message,
|
|
css: {
|
|
backgroundColor: "#f7931a",
|
|
color: "#000",
|
|
padding: "10px 15px",
|
|
borderRadius: "5px",
|
|
marginTop: "10px",
|
|
boxShadow: "0 0 10px rgba(247, 147, 26, 0.5)",
|
|
fontFamily: "var(--terminal-font)",
|
|
opacity: 0,
|
|
transition: "opacity 0.3s ease"
|
|
}
|
|
}).appendTo(toastContainer);
|
|
|
|
// Show the toast
|
|
setTimeout(() => {
|
|
toast.css("opacity", 1);
|
|
|
|
// Hide and remove the toast after 3 seconds
|
|
setTimeout(() => {
|
|
toast.css("opacity", 0);
|
|
setTimeout(() => toast.remove(), 300);
|
|
}, 3000);
|
|
}, 100);
|
|
}
|
|
|
|
// Pool color mapping function - add this after your existing helper functions
|
|
function getPoolColor(poolName) {
|
|
// Normalize the pool name (lowercase and remove special characters)
|
|
const normalizedName = poolName.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
|
|
// Define color mappings for common mining pools with Ocean pool featured prominently
|
|
const poolColors = {
|
|
// OCEAN pool with a distinctive bright cyan color for prominence
|
|
'ocean': '#00ffff', // Bright Cyan for Ocean
|
|
'oceanpool': '#00ffff', // Bright Cyan for Ocean
|
|
'oceanxyz': '#00ffff', // Bright Cyan for Ocean
|
|
'ocean.xyz': '#00ffff', // Bright Cyan for Ocean
|
|
|
|
// Other common mining pools with more muted colors
|
|
'f2pool': '#1a9eff', // Blue
|
|
'antpool': '#ff7e33', // Orange
|
|
'binancepool': '#f3ba2f', // Binance gold
|
|
'foundryusa': '#b150e2', // Purple
|
|
'viabtc': '#ff5c5c', // Red
|
|
'luxor': '#2bae2b', // Green
|
|
'slushpool': '#3355ff', // Bright blue
|
|
'btccom': '#ff3355', // Pink
|
|
'poolin': '#ffaa22', // Amber
|
|
'sbicrypto': '#cc9933', // Bronze
|
|
'mara': '#8844cc', // Violet
|
|
'ultimuspool': '#09c7be', // Teal
|
|
'unknown': '#999999' // Grey for unknown pools
|
|
};
|
|
|
|
// Check for partial matches in pool names (for variations like "F2Pool" vs "F2pool.com")
|
|
for (const [key, color] of Object.entries(poolColors)) {
|
|
if (normalizedName.includes(key)) {
|
|
return color;
|
|
}
|
|
}
|
|
|
|
// If no match is found, generate a consistent color based on the pool name
|
|
let hash = 0;
|
|
for (let i = 0; i < poolName.length; i++) {
|
|
hash = poolName.charCodeAt(i) + ((hash << 5) - hash);
|
|
}
|
|
|
|
// Generate HSL color with fixed saturation and lightness for readability
|
|
// Use the hash to vary the hue only (0-360)
|
|
const hue = Math.abs(hash % 360);
|
|
return `hsl(${hue}, 70%, 60%)`;
|
|
}
|
|
|
|
// Function to create a block card
|
|
function createBlockCard(block) {
|
|
const timestamp = formatTimestamp(block.timestamp);
|
|
const formattedSize = formatFileSize(block.size);
|
|
const formattedTxCount = numberWithCommas(block.tx_count);
|
|
|
|
// Get the pool name or "Unknown"
|
|
const poolName = block.extras && block.extras.pool ? block.extras.pool.name : "Unknown";
|
|
|
|
// Get pool color
|
|
const poolColor = getPoolColor(poolName);
|
|
|
|
// Check if this is an Ocean pool block for special styling
|
|
const isOceanPool = poolName.toLowerCase().includes('ocean');
|
|
|
|
// Calculate total fees in BTC
|
|
const totalFees = block.extras ? (block.extras.totalFees / 100000000).toFixed(8) : "N/A";
|
|
|
|
// Create the block card
|
|
const blockCard = $("<div>", {
|
|
class: "block-card",
|
|
"data-height": block.height,
|
|
"data-hash": block.id
|
|
});
|
|
|
|
// Apply pool color border - with special emphasis for Ocean pool
|
|
if (isOceanPool) {
|
|
// Give Ocean pool blocks a more prominent styling
|
|
blockCard.css({
|
|
"border": `2px solid ${poolColor}`,
|
|
"box-shadow": `0 0 10px ${poolColor}`,
|
|
"background": `linear-gradient(to bottom, rgba(0, 255, 255, 0.1), rgba(0, 0, 0, 0))`
|
|
});
|
|
} else {
|
|
// Standard styling for other pools
|
|
blockCard.css({
|
|
"border-left": `4px solid ${poolColor}`,
|
|
"border-top": `1px solid ${poolColor}30`,
|
|
"border-right": `1px solid ${poolColor}30`,
|
|
"border-bottom": `1px solid ${poolColor}30`
|
|
});
|
|
}
|
|
|
|
// Create the block header
|
|
const blockHeader = $("<div>", {
|
|
class: "block-header"
|
|
});
|
|
|
|
blockHeader.append($("<div>", {
|
|
class: "block-height",
|
|
text: "#" + block.height
|
|
}));
|
|
|
|
blockHeader.append($("<div>", {
|
|
class: "block-time",
|
|
text: timestamp
|
|
}));
|
|
|
|
blockCard.append(blockHeader);
|
|
|
|
// Create the block info section
|
|
const blockInfo = $("<div>", {
|
|
class: "block-info"
|
|
});
|
|
|
|
// Add transaction count with conditional coloring based on count
|
|
const txCountItem = $("<div>", {
|
|
class: "block-info-item"
|
|
});
|
|
txCountItem.append($("<div>", {
|
|
class: "block-info-label",
|
|
text: "Transactions"
|
|
}));
|
|
|
|
// Determine transaction count color based on thresholds
|
|
let txCountClass = "green"; // Default for high transaction counts (2000+)
|
|
if (block.tx_count < 500) {
|
|
txCountClass = "red"; // Less than 500 transactions
|
|
} else if (block.tx_count < 2000) {
|
|
txCountClass = "yellow"; // Between 500 and 1999 transactions
|
|
}
|
|
|
|
txCountItem.append($("<div>", {
|
|
class: `block-info-value ${txCountClass}`,
|
|
text: formattedTxCount
|
|
}));
|
|
blockInfo.append(txCountItem);
|
|
|
|
// Add size
|
|
const sizeItem = $("<div>", {
|
|
class: "block-info-item"
|
|
});
|
|
sizeItem.append($("<div>", {
|
|
class: "block-info-label",
|
|
text: "Size"
|
|
}));
|
|
sizeItem.append($("<div>", {
|
|
class: "block-info-value white",
|
|
text: formattedSize
|
|
}));
|
|
blockInfo.append(sizeItem);
|
|
|
|
// Add miner/pool with custom color
|
|
const minerItem = $("<div>", {
|
|
class: "block-info-item"
|
|
});
|
|
minerItem.append($("<div>", {
|
|
class: "block-info-label",
|
|
text: "Miner"
|
|
}));
|
|
|
|
// Apply the custom pool color with special styling for Ocean pool
|
|
const minerValue = $("<div>", {
|
|
class: "block-info-value",
|
|
text: poolName,
|
|
css: {
|
|
color: poolColor,
|
|
textShadow: isOceanPool ? `0 0 8px ${poolColor}` : `0 0 6px ${poolColor}80`,
|
|
fontWeight: isOceanPool ? "bold" : "normal"
|
|
}
|
|
});
|
|
|
|
// Add a special indicator icon for Ocean pool
|
|
if (isOceanPool) {
|
|
minerValue.prepend($("<span>", {
|
|
html: "★ ",
|
|
css: { color: poolColor }
|
|
}));
|
|
}
|
|
|
|
minerItem.append(minerValue);
|
|
blockInfo.append(minerItem);
|
|
|
|
// Add Avg Fee Rate
|
|
const feesItem = $("<div>", {
|
|
class: "block-info-item"
|
|
});
|
|
feesItem.append($("<div>", {
|
|
class: "block-info-label",
|
|
text: "Avg Fee Rate"
|
|
}));
|
|
feesItem.append($("<div>", {
|
|
class: "block-info-value yellow",
|
|
text: block.extras && block.extras.avgFeeRate ? block.extras.avgFeeRate + " sat/vB" : "N/A"
|
|
}));
|
|
blockInfo.append(feesItem);
|
|
|
|
blockCard.append(blockInfo);
|
|
|
|
// Add event listener for clicking on the block card
|
|
blockCard.on("click", function () {
|
|
showBlockDetails(block);
|
|
});
|
|
|
|
return blockCard;
|
|
}
|
|
|
|
// Function to load blocks from a specific height
|
|
function loadBlocksFromHeight(height) {
|
|
if (isLoading) return;
|
|
|
|
// Convert to integer
|
|
height = parseInt(height);
|
|
if (isNaN(height) || height < 0) {
|
|
showToast("Please enter a valid block height");
|
|
return;
|
|
}
|
|
|
|
isLoading = true;
|
|
currentStartHeight = height;
|
|
|
|
// Check if we already have this data in cache
|
|
if (blocksCache[height]) {
|
|
displayBlocks(blocksCache[height]);
|
|
isLoading = false;
|
|
return;
|
|
}
|
|
|
|
// Show loading state
|
|
$("#blocks-grid").html('<div class="loader"><span class="loader-text">Loading blocks from height ' + height + '<span class="terminal-cursor"></span></span></div>');
|
|
|
|
// Fetch blocks from the API
|
|
$.ajax({
|
|
url: `${mempoolBaseUrl}/api/v1/blocks/${height}`,
|
|
method: "GET",
|
|
dataType: "json",
|
|
timeout: 10000,
|
|
success: function(data) {
|
|
// Cache the data
|
|
blocksCache[height] = data;
|
|
|
|
// Display the blocks
|
|
displayBlocks(data);
|
|
|
|
// Update latest block stats
|
|
if (data.length > 0) {
|
|
updateLatestBlockStats(data[0]);
|
|
}
|
|
},
|
|
error: function(xhr, status, error) {
|
|
console.error("Error fetching blocks:", error);
|
|
$("#blocks-grid").html('<div class="error">Error fetching blocks. Please try again later.</div>');
|
|
|
|
// Show error toast
|
|
showToast("Failed to load blocks. Please try again later.");
|
|
},
|
|
complete: function() {
|
|
isLoading = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Function to load the latest blocks and return a promise with the latest block height
|
|
function loadLatestBlocks() {
|
|
if (isLoading) return Promise.resolve(null);
|
|
|
|
isLoading = true;
|
|
|
|
// Show loading state
|
|
$("#blocks-grid").html('<div class="loader"><span class="loader-text">Loading latest blocks<span class="terminal-cursor"></span></span></div>');
|
|
|
|
// Fetch the latest blocks from the API
|
|
return $.ajax({
|
|
url: `${mempoolBaseUrl}/api/v1/blocks`,
|
|
method: "GET",
|
|
dataType: "json",
|
|
timeout: 10000,
|
|
success: function (data) {
|
|
// Cache the data (use the first block's height as the key)
|
|
if (data.length > 0) {
|
|
currentStartHeight = data[0].height;
|
|
blocksCache[currentStartHeight] = data;
|
|
|
|
// Update the block height input with the latest height
|
|
$("#block-height").val(currentStartHeight);
|
|
|
|
// Update latest block stats
|
|
updateLatestBlockStats(data[0]);
|
|
}
|
|
|
|
// Display the blocks
|
|
displayBlocks(data);
|
|
},
|
|
error: function (xhr, status, error) {
|
|
console.error("Error fetching latest blocks:", error);
|
|
$("#blocks-grid").html('<div class="error">Error fetching blocks. Please try again later.</div>');
|
|
|
|
// Show error toast
|
|
showToast("Failed to load latest blocks. Please try again later.");
|
|
},
|
|
complete: function () {
|
|
isLoading = false;
|
|
}
|
|
}).then(data => data.length > 0 ? data[0].height : null);
|
|
}
|
|
|
|
// Refresh blocks page every 60 seconds if there are new blocks
|
|
setInterval(function () {
|
|
console.log("Checking for new blocks at " + new Date().toLocaleTimeString());
|
|
loadLatestBlocks().then(latestHeight => {
|
|
if (latestHeight && latestHeight > currentStartHeight) {
|
|
console.log("New blocks detected, refreshing the page");
|
|
location.reload();
|
|
} else {
|
|
console.log("No new blocks detected");
|
|
}
|
|
});
|
|
}, 60000);
|
|
|
|
|
|
// Function to update the latest block stats section
|
|
function updateLatestBlockStats(block) {
|
|
if (!block) return;
|
|
|
|
$("#latest-height").text(block.height);
|
|
$("#latest-time").text(formatTimestamp(block.timestamp));
|
|
$("#latest-tx-count").text(numberWithCommas(block.tx_count));
|
|
$("#latest-size").text(formatFileSize(block.size));
|
|
$("#latest-difficulty").text(numberWithCommas(Math.round(block.difficulty)));
|
|
|
|
// Pool info
|
|
if (block.extras && block.extras.pool) {
|
|
$("#latest-pool").text(block.extras.pool.name);
|
|
} else {
|
|
$("#latest-pool").text("Unknown");
|
|
}
|
|
|
|
// Average Fee Rate
|
|
if (block.extras && block.extras.avgFeeRate) {
|
|
$("#latest-fee-rate").text(block.extras.avgFeeRate + " sat/vB");
|
|
} else {
|
|
$("#latest-fee-rate").text("N/A");
|
|
}
|
|
}
|
|
|
|
// Function to display the blocks in the grid
|
|
function displayBlocks(blocks) {
|
|
const blocksGrid = $("#blocks-grid");
|
|
|
|
// Clear the grid
|
|
blocksGrid.empty();
|
|
|
|
if (!blocks || blocks.length === 0) {
|
|
blocksGrid.html('<div class="no-blocks">No blocks found</div>');
|
|
return;
|
|
}
|
|
|
|
// Create a card for each block
|
|
blocks.forEach(function(block) {
|
|
const blockCard = createBlockCard(block);
|
|
blocksGrid.append(blockCard);
|
|
});
|
|
|
|
// Add navigation controls if needed
|
|
addNavigationControls(blocks);
|
|
}
|
|
|
|
// Function to create a block card
|
|
function createBlockCard(block) {
|
|
const timestamp = formatTimestamp(block.timestamp);
|
|
const formattedSize = formatFileSize(block.size);
|
|
const formattedTxCount = numberWithCommas(block.tx_count);
|
|
|
|
// Get the pool name or "Unknown"
|
|
const poolName = block.extras && block.extras.pool ? block.extras.pool.name : "Unknown";
|
|
|
|
// Calculate total fees in BTC
|
|
const totalFees = block.extras ? (block.extras.totalFees / 100000000).toFixed(8) : "N/A";
|
|
|
|
// Create the block card
|
|
const blockCard = $("<div>", {
|
|
class: "block-card",
|
|
"data-height": block.height,
|
|
"data-hash": block.id
|
|
});
|
|
|
|
// Create the block header
|
|
const blockHeader = $("<div>", {
|
|
class: "block-header"
|
|
});
|
|
|
|
blockHeader.append($("<div>", {
|
|
class: "block-height",
|
|
text: "#" + block.height
|
|
}));
|
|
|
|
blockHeader.append($("<div>", {
|
|
class: "block-time",
|
|
text: timestamp
|
|
}));
|
|
|
|
blockCard.append(blockHeader);
|
|
|
|
// Create the block info section
|
|
const blockInfo = $("<div>", {
|
|
class: "block-info"
|
|
});
|
|
|
|
// Add transaction count with conditional coloring based on count
|
|
const txCountItem = $("<div>", {
|
|
class: "block-info-item"
|
|
});
|
|
txCountItem.append($("<div>", {
|
|
class: "block-info-label",
|
|
text: "Transactions"
|
|
}));
|
|
|
|
// Determine transaction count color based on thresholds
|
|
let txCountClass = "green"; // Default for high transaction counts (2000+)
|
|
if (block.tx_count < 500) {
|
|
txCountClass = "red"; // Less than 500 transactions
|
|
} else if (block.tx_count < 2000) {
|
|
txCountClass = "yellow"; // Between 500 and 1999 transactions
|
|
}
|
|
|
|
txCountItem.append($("<div>", {
|
|
class: `block-info-value ${txCountClass}`,
|
|
text: formattedTxCount
|
|
}));
|
|
blockInfo.append(txCountItem);
|
|
|
|
// Add size
|
|
const sizeItem = $("<div>", {
|
|
class: "block-info-item"
|
|
});
|
|
sizeItem.append($("<div>", {
|
|
class: "block-info-label",
|
|
text: "Size"
|
|
}));
|
|
sizeItem.append($("<div>", {
|
|
class: "block-info-value white",
|
|
text: formattedSize
|
|
}));
|
|
blockInfo.append(sizeItem);
|
|
|
|
// Add miner/pool
|
|
const minerItem = $("<div>", {
|
|
class: "block-info-item"
|
|
});
|
|
minerItem.append($("<div>", {
|
|
class: "block-info-label",
|
|
text: "Miner"
|
|
}));
|
|
minerItem.append($("<div>", {
|
|
class: "block-info-value green",
|
|
text: poolName
|
|
}));
|
|
blockInfo.append(minerItem);
|
|
|
|
// Replace it with this code for Avg Fee Rate:
|
|
const feesItem = $("<div>", {
|
|
class: "block-info-item"
|
|
});
|
|
feesItem.append($("<div>", {
|
|
class: "block-info-label",
|
|
text: "Avg Fee Rate"
|
|
}));
|
|
feesItem.append($("<div>", {
|
|
class: "block-info-value yellow",
|
|
text: block.extras && block.extras.avgFeeRate ? block.extras.avgFeeRate + " sat/vB" : "N/A"
|
|
}));
|
|
blockInfo.append(feesItem);
|
|
|
|
blockCard.append(blockInfo);
|
|
|
|
// Add event listener for clicking on the block card
|
|
blockCard.on("click", function() {
|
|
showBlockDetails(block);
|
|
});
|
|
|
|
return blockCard;
|
|
}
|
|
|
|
// Function to add navigation controls to the blocks grid
|
|
function addNavigationControls(blocks) {
|
|
// Get the height of the first and last block in the current view
|
|
const firstBlockHeight = blocks[0].height;
|
|
const lastBlockHeight = blocks[blocks.length - 1].height;
|
|
|
|
// Create navigation controls
|
|
const navControls = $("<div>", {
|
|
class: "block-navigation"
|
|
});
|
|
|
|
// Newer blocks button (if not already at the latest blocks)
|
|
if (firstBlockHeight !== currentStartHeight) {
|
|
const newerButton = $("<button>", {
|
|
class: "block-button",
|
|
text: "Newer Blocks"
|
|
});
|
|
|
|
newerButton.on("click", function() {
|
|
loadBlocksFromHeight(firstBlockHeight + 15);
|
|
});
|
|
|
|
navControls.append(newerButton);
|
|
}
|
|
|
|
// Older blocks button
|
|
const olderButton = $("<button>", {
|
|
class: "block-button",
|
|
text: "Older Blocks"
|
|
});
|
|
|
|
olderButton.on("click", function() {
|
|
loadBlocksFromHeight(lastBlockHeight - 1);
|
|
});
|
|
|
|
navControls.append(olderButton);
|
|
|
|
// Add the navigation controls to the blocks grid
|
|
$("#blocks-grid").append(navControls);
|
|
}
|
|
|
|
// Function to show block details in a modal
|
|
function showBlockDetails(block) {
|
|
const modal = $("#block-modal");
|
|
const blockDetails = $("#block-details");
|
|
|
|
// Clear the details
|
|
blockDetails.empty();
|
|
|
|
// Format the timestamp
|
|
const timestamp = formatTimestamp(block.timestamp);
|
|
|
|
// Create the block header section
|
|
const headerSection = $("<div>", {
|
|
class: "block-detail-section"
|
|
});
|
|
|
|
headerSection.append($("<div>", {
|
|
class: "block-detail-title",
|
|
text: "Block #" + block.height
|
|
}));
|
|
|
|
// Add block hash
|
|
const hashItem = $("<div>", {
|
|
class: "block-detail-item"
|
|
});
|
|
hashItem.append($("<div>", {
|
|
class: "block-detail-label",
|
|
text: "Block Hash"
|
|
}));
|
|
hashItem.append($("<div>", {
|
|
class: "block-hash",
|
|
text: block.id
|
|
}));
|
|
headerSection.append(hashItem);
|
|
|
|
// Add mempool.space link
|
|
const linkItem = $("<div>", {
|
|
class: "block-detail-item"
|
|
});
|
|
linkItem.append($("<div>", {
|
|
class: "block-detail-label",
|
|
text: "Explorer Link"
|
|
}));
|
|
|
|
const mempoolLink = $("<a>", {
|
|
href: `${mempoolBaseUrl}/block/${block.id}`,
|
|
target: "_blank",
|
|
class: "mempool-link",
|
|
text: "View on mempool.space",
|
|
css: {
|
|
color: "#f7931a",
|
|
textDecoration: "none"
|
|
}
|
|
});
|
|
|
|
// Add icon or indicator that it's an external link
|
|
mempoolLink.append($("<span>", {
|
|
html: " ↗",
|
|
css: {
|
|
fontSize: "14px"
|
|
}
|
|
}));
|
|
|
|
const linkContainer = $("<div>", {
|
|
class: "block-detail-value"
|
|
});
|
|
linkContainer.append(mempoolLink);
|
|
linkItem.append(linkContainer);
|
|
|
|
headerSection.append(linkItem);
|
|
|
|
// Add timestamp
|
|
const timeItem = $("<div>", {
|
|
class: "block-detail-item"
|
|
});
|
|
timeItem.append($("<div>", {
|
|
class: "block-detail-label",
|
|
text: "Timestamp"
|
|
}));
|
|
timeItem.append($("<div>", {
|
|
class: "block-detail-value",
|
|
text: timestamp
|
|
}));
|
|
headerSection.append(timeItem);
|
|
|
|
// Add merkle root
|
|
const merkleItem = $("<div>", {
|
|
class: "block-detail-item"
|
|
});
|
|
merkleItem.append($("<div>", {
|
|
class: "block-detail-label",
|
|
text: "Merkle Root"
|
|
}));
|
|
merkleItem.append($("<div>", {
|
|
class: "block-hash",
|
|
text: block.merkle_root
|
|
}));
|
|
headerSection.append(merkleItem);
|
|
|
|
// Add previous block hash
|
|
const prevHashItem = $("<div>", {
|
|
class: "block-detail-item"
|
|
});
|
|
prevHashItem.append($("<div>", {
|
|
class: "block-detail-label",
|
|
text: "Previous Block"
|
|
}));
|
|
prevHashItem.append($("<div>", {
|
|
class: "block-hash",
|
|
text: block.previousblockhash
|
|
}));
|
|
headerSection.append(prevHashItem);
|
|
|
|
blockDetails.append(headerSection);
|
|
|
|
// Create the mining section
|
|
const miningSection = $("<div>", {
|
|
class: "block-detail-section"
|
|
});
|
|
|
|
miningSection.append($("<div>", {
|
|
class: "block-detail-title",
|
|
text: "Mining Details"
|
|
}));
|
|
|
|
// Add miner/pool with matching color
|
|
const minerItem = $("<div>", {
|
|
class: "block-detail-item"
|
|
});
|
|
minerItem.append($("<div>", {
|
|
class: "block-detail-label",
|
|
text: "Miner"
|
|
}));
|
|
const poolName = block.extras && block.extras.pool ? block.extras.pool.name : "Unknown";
|
|
const poolColor = getPoolColor(poolName);
|
|
const isOceanPool = poolName.toLowerCase().includes('ocean');
|
|
|
|
// Apply special styling for Ocean pool in the modal
|
|
const minerValue = $("<div>", {
|
|
class: "block-detail-value",
|
|
text: poolName,
|
|
css: {
|
|
color: poolColor,
|
|
textShadow: isOceanPool ? `0 0 8px ${poolColor}` : `0 0 6px ${poolColor}80`,
|
|
fontWeight: isOceanPool ? "bold" : "normal"
|
|
}
|
|
});
|
|
|
|
// Add a special indicator icon for Ocean pool
|
|
if (isOceanPool) {
|
|
minerValue.prepend($("<span>", {
|
|
html: "★ ",
|
|
css: { color: poolColor }
|
|
}));
|
|
|
|
// Add a note for Ocean pool
|
|
minerValue.append($("<div>", {
|
|
text: "(Your Mining Pool)",
|
|
css: {
|
|
fontSize: "0.8em",
|
|
marginTop: "3px",
|
|
color: "#ffffffbb"
|
|
}
|
|
}));
|
|
}
|
|
|
|
minerItem.append(minerValue);
|
|
miningSection.append(minerItem);
|
|
|
|
// Rest of the function remains unchanged
|
|
// Add difficulty
|
|
const difficultyItem = $("<div>", {
|
|
class: "block-detail-item"
|
|
});
|
|
difficultyItem.append($("<div>", {
|
|
class: "block-detail-label",
|
|
text: "Difficulty"
|
|
}));
|
|
difficultyItem.append($("<div>", {
|
|
class: "block-detail-value",
|
|
text: numberWithCommas(Math.round(block.difficulty))
|
|
}));
|
|
miningSection.append(difficultyItem);
|
|
|
|
// Add nonce
|
|
const nonceItem = $("<div>", {
|
|
class: "block-detail-item"
|
|
});
|
|
nonceItem.append($("<div>", {
|
|
class: "block-detail-label",
|
|
text: "Nonce"
|
|
}));
|
|
nonceItem.append($("<div>", {
|
|
class: "block-detail-value",
|
|
text: numberWithCommas(block.nonce)
|
|
}));
|
|
miningSection.append(nonceItem);
|
|
|
|
// Add bits
|
|
const bitsItem = $("<div>", {
|
|
class: "block-detail-item"
|
|
});
|
|
bitsItem.append($("<div>", {
|
|
class: "block-detail-label",
|
|
text: "Bits"
|
|
}));
|
|
bitsItem.append($("<div>", {
|
|
class: "block-detail-value",
|
|
text: block.bits
|
|
}));
|
|
miningSection.append(bitsItem);
|
|
|
|
// Add version
|
|
const versionItem = $("<div>", {
|
|
class: "block-detail-item"
|
|
});
|
|
versionItem.append($("<div>", {
|
|
class: "block-detail-label",
|
|
text: "Version"
|
|
}));
|
|
versionItem.append($("<div>", {
|
|
class: "block-detail-value",
|
|
text: "0x" + block.version.toString(16)
|
|
}));
|
|
miningSection.append(versionItem);
|
|
|
|
blockDetails.append(miningSection);
|
|
|
|
// Create the transaction section
|
|
const txSection = $("<div>", {
|
|
class: "block-detail-section"
|
|
});
|
|
|
|
txSection.append($("<div>", {
|
|
class: "block-detail-title",
|
|
text: "Transaction Details"
|
|
}));
|
|
|
|
// Add transaction count
|
|
const txCountItem = $("<div>", {
|
|
class: "block-detail-item"
|
|
});
|
|
txCountItem.append($("<div>", {
|
|
class: "block-detail-label",
|
|
text: "Transaction Count"
|
|
}));
|
|
txCountItem.append($("<div>", {
|
|
class: "block-detail-value",
|
|
text: numberWithCommas(block.tx_count)
|
|
}));
|
|
txSection.append(txCountItem);
|
|
|
|
// Add size
|
|
const sizeItem = $("<div>", {
|
|
class: "block-detail-item"
|
|
});
|
|
sizeItem.append($("<div>", {
|
|
class: "block-detail-label",
|
|
text: "Size"
|
|
}));
|
|
sizeItem.append($("<div>", {
|
|
class: "block-detail-value",
|
|
text: formatFileSize(block.size)
|
|
}));
|
|
txSection.append(sizeItem);
|
|
|
|
// Add weight
|
|
const weightItem = $("<div>", {
|
|
class: "block-detail-item"
|
|
});
|
|
weightItem.append($("<div>", {
|
|
class: "block-detail-label",
|
|
text: "Weight"
|
|
}));
|
|
weightItem.append($("<div>", {
|
|
class: "block-detail-value",
|
|
text: numberWithCommas(block.weight) + " WU"
|
|
}));
|
|
txSection.append(weightItem);
|
|
|
|
blockDetails.append(txSection);
|
|
|
|
// Create the fee section if available
|
|
if (block.extras) {
|
|
const feeSection = $("<div>", {
|
|
class: "block-detail-section"
|
|
});
|
|
|
|
feeSection.append($("<div>", {
|
|
class: "block-detail-title",
|
|
text: "Fee Details"
|
|
}));
|
|
|
|
// Add total fees
|
|
const totalFeesItem = $("<div>", {
|
|
class: "block-detail-item"
|
|
});
|
|
totalFeesItem.append($("<div>", {
|
|
class: "block-detail-label",
|
|
text: "Total Fees"
|
|
}));
|
|
const totalFees = (block.extras.totalFees / 100000000).toFixed(8);
|
|
totalFeesItem.append($("<div>", {
|
|
class: "block-detail-value",
|
|
text: totalFees + " BTC"
|
|
}));
|
|
feeSection.append(totalFeesItem);
|
|
|
|
// Add reward
|
|
const rewardItem = $("<div>", {
|
|
class: "block-detail-item"
|
|
});
|
|
rewardItem.append($("<div>", {
|
|
class: "block-detail-label",
|
|
text: "Block Reward"
|
|
}));
|
|
const reward = (block.extras.reward / 100000000).toFixed(8);
|
|
rewardItem.append($("<div>", {
|
|
class: "block-detail-value",
|
|
text: reward + " BTC"
|
|
}));
|
|
feeSection.append(rewardItem);
|
|
|
|
// Add median fee
|
|
const medianFeeItem = $("<div>", {
|
|
class: "block-detail-item"
|
|
});
|
|
medianFeeItem.append($("<div>", {
|
|
class: "block-detail-label",
|
|
text: "Median Fee Rate"
|
|
}));
|
|
medianFeeItem.append($("<div>", {
|
|
class: "block-detail-value",
|
|
text: block.extras.medianFee + " sat/vB"
|
|
}));
|
|
feeSection.append(medianFeeItem);
|
|
|
|
// Add average fee
|
|
const avgFeeItem = $("<div>", {
|
|
class: "block-detail-item"
|
|
});
|
|
avgFeeItem.append($("<div>", {
|
|
class: "block-detail-label",
|
|
text: "Average Fee"
|
|
}));
|
|
avgFeeItem.append($("<div>", {
|
|
class: "block-detail-value",
|
|
text: numberWithCommas(block.extras.avgFee) + " sat"
|
|
}));
|
|
feeSection.append(avgFeeItem);
|
|
|
|
// Add average fee rate
|
|
const avgFeeRateItem = $("<div>", {
|
|
class: "block-detail-item"
|
|
});
|
|
avgFeeRateItem.append($("<div>", {
|
|
class: "block-detail-label",
|
|
text: "Average Fee Rate"
|
|
}));
|
|
avgFeeRateItem.append($("<div>", {
|
|
class: "block-detail-value",
|
|
text: block.extras.avgFeeRate + " sat/vB"
|
|
}));
|
|
feeSection.append(avgFeeRateItem);
|
|
|
|
// Add fee range with visual representation
|
|
if (block.extras.feeRange && block.extras.feeRange.length > 0) {
|
|
const feeRangeItem = $("<div>", {
|
|
class: "block-detail-item transaction-data"
|
|
});
|
|
|
|
feeRangeItem.append($("<div>", {
|
|
class: "block-detail-label",
|
|
text: "Fee Rate Percentiles (sat/vB)"
|
|
}));
|
|
|
|
const feeRangeText = $("<div>", {
|
|
class: "block-detail-value",
|
|
text: block.extras.feeRange.join(", ")
|
|
});
|
|
|
|
feeRangeItem.append(feeRangeText);
|
|
|
|
// Add visual fee bar
|
|
const feeBarContainer = $("<div>", {
|
|
class: "fee-bar-container"
|
|
});
|
|
|
|
const feeBar = $("<div>", {
|
|
class: "fee-bar"
|
|
});
|
|
|
|
feeBarContainer.append(feeBar);
|
|
feeRangeItem.append(feeBarContainer);
|
|
|
|
// Animate the fee bar
|
|
setTimeout(() => {
|
|
feeBar.css("width", "100%");
|
|
}, 100);
|
|
|
|
feeSection.append(feeRangeItem);
|
|
}
|
|
|
|
blockDetails.append(feeSection);
|
|
}
|
|
|
|
// Show the modal
|
|
modal.css("display", "block");
|
|
}
|
|
|
|
// Function to close the modal
|
|
function closeModal() {
|
|
$("#block-modal").css("display", "none");
|
|
}
|