diff --git a/server.py b/server.py index f0b75c7..c4fb1af 100644 --- a/server.py +++ b/server.py @@ -2,7 +2,6 @@ from flask import ( Flask, make_response, request, - jsonify, render_template, send_from_directory, send_file, @@ -105,32 +104,6 @@ def catch_all(path: str): # endregion -# region API routes - -api_requests = 0 - - -@app.route("/api/v1/data", methods=["GET"]) -def api_data(): - """ - Example API endpoint that returns some data. - You can modify this to return whatever data you need. - """ - - global api_requests - api_requests += 1 - - data = { - "header": "Sample API Response", - "content": f"Hello, this is a sample API response! You have called this endpoint {api_requests} times.", - "timestamp": datetime.now().isoformat(), - } - return jsonify(data) - - -# endregion - - # region Error Catching # 404 catch all @app.errorhandler(404) @@ -140,4 +113,4 @@ def not_found(e): # endregion if __name__ == "__main__": - app.run(debug=True, port=5000, host="0.0.0.0") + app.run(debug=True, port=5000, host="127.0.0.1") diff --git a/templates/assets/css/index.css b/templates/assets/css/index.css index 3ef53b7..96c002a 100644 --- a/templates/assets/css/index.css +++ b/templates/assets/css/index.css @@ -1,41 +1,362 @@ -body { - background-color: #000000; - color: #ffffff; -} -h1 { - font-size: 50px; +* { margin: 0; padding: 0; + box-sizing: border-box; } -.centre { - margin-top: 10%; + +body { + background: linear-gradient(135deg, #0a0a0a 0%, #1a1a1a 100%); + color: #e0e0e0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + line-height: 1.6; + min-height: 100vh; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 20px; +} + +/* Header */ +header { + background: linear-gradient(135deg, #8b2f0a 0%, #6b3d0a 100%); + padding: 2rem 0; text-align: center; + box-shadow: 0 4px 20px rgba(255, 107, 53, 0.3); + margin: 0 0 2rem 0; } -a { + +header h1 { + font-size: 3rem; + margin: 0; color: #ffffff; - text-decoration: none; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); } + +.subtitle { + color: rgba(255, 255, 255, 0.9); + font-size: 1.2rem; + margin-top: 0.5rem; +} + +/* Main Content */ +main { + padding: 2rem 0; +} + +/* Cards */ +.card { + background: rgba(30, 30, 30, 0.8); + border-radius: 12px; + padding: 2rem; + margin-bottom: 2rem; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + border: 1px solid rgba(255, 107, 53, 0.2); +} + +.card h2 { + color: #ff6b35; + margin-bottom: 1.5rem; + font-size: 1.8rem; +} + +.card h3 { + color: #f7931e; + margin-bottom: 1rem; + font-size: 1.3rem; +} + +/* Status Section */ +.status-section { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 1.5rem; + margin-bottom: 2rem; +} + +.status-card { + padding: 1.5rem; +} + +.status-content { + color: #b0b0b0; + font-size: 0.9rem; +} + +/* Info Grid */ +.info-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 0.75rem; +} + +.info-item { + padding: 0.5rem; + background: rgba(20, 20, 20, 0.5); + border-radius: 6px; + border: 1px solid rgba(255, 107, 53, 0.15); +} + +.info-item.no-border { + background: transparent; + border: none; + padding: 0; +} + +.info-item.full-width { + grid-column: 1 / -1; +} + +.info-item strong { + color: #ff6b35; + display: inline-block; + min-width: 100px; +} + +.mono { + font-family: 'Courier New', monospace; + font-size: 0.85rem; + word-break: break-all; +} + +.view-all-btn { + display: inline-block; + padding: 0.5rem 1rem; + background: linear-gradient(135deg, #ff6b35 0%, #f7931e 100%); + color: #ffffff !important; + text-decoration: none !important; + border-radius: 6px; + font-weight: 600; + transition: all 0.3s ease; +} + +.view-all-btn:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(255, 107, 53, 0.4); +} + +/* Tabs */ +.search-tabs { + display: flex; + gap: 0.5rem; + margin-bottom: 1.5rem; + flex-wrap: wrap; +} + +.tab-btn { + background: rgba(50, 50, 50, 0.5); + color: #e0e0e0; + border: 1px solid rgba(255, 107, 53, 0.3); + padding: 0.75rem 1.5rem; + border-radius: 8px; + cursor: pointer; + font-size: 1rem; + transition: all 0.3s ease; +} + +.tab-btn:hover { + background: rgba(255, 107, 53, 0.2); + border-color: #ff6b35; +} + +.tab-btn.active { + background: linear-gradient(135deg, #ff6b35 0%, #f7931e 100%); + color: #ffffff; + border-color: #ff6b35; +} + +.tab-content { + display: none; +} + +.tab-content.active { + display: block; +} + +/* Search Box */ +.search-box { + display: flex; + gap: 0.75rem; + margin-bottom: 1.5rem; + flex-wrap: wrap; +} + +.search-box input { + flex: 1; + min-width: 200px; + padding: 0.75rem 1rem; + background: rgba(20, 20, 20, 0.8); + border: 1px solid rgba(255, 107, 53, 0.3); + border-radius: 8px; + color: #e0e0e0; + font-size: 1rem; +} + +.search-box input:focus { + outline: none; + border-color: #ff6b35; + box-shadow: 0 0 0 2px rgba(255, 107, 53, 0.2); +} + +.search-box input::placeholder { + color: rgba(224, 224, 224, 0.5); +} + +.search-box button { + padding: 0.75rem 1.5rem; + background: linear-gradient(135deg, #ff6b35 0%, #f7931e 100%); + color: #ffffff; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 1rem; + font-weight: 600; + transition: all 0.3s ease; +} + +.search-box button:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(255, 107, 53, 0.4); +} + +.search-box button:active { + transform: translateY(0); +} + +.secondary-btn { + padding: 0.75rem 1.5rem; + background: rgba(50, 50, 50, 0.8); + color: #ff6b35; + border: 1px solid #ff6b35; + border-radius: 8px; + cursor: pointer; + font-size: 1rem; + font-weight: 600; + transition: all 0.3s ease; +} + +.secondary-btn:hover { + background: rgba(255, 107, 53, 0.2); +} + +/* Result Box */ +.result-box { + background: rgba(10, 10, 10, 0.8); + border: 1px solid rgba(255, 107, 53, 0.2); + border-radius: 8px; + padding: 1rem; + min-height: 100px; + max-height: 600px; + overflow-y: auto; +} + +.result-box:empty { + display: none; +} + +.result-box pre { + color: #b0b0b0; + font-family: 'Courier New', monospace; + font-size: 0.9rem; + white-space: pre-wrap; + word-wrap: break-word; +} + +.result-box .error { + color: #ff4444; + font-weight: 600; +} + +/* Scrollbar */ +.result-box::-webkit-scrollbar { + width: 8px; +} + +.result-box::-webkit-scrollbar-track { + background: rgba(20, 20, 20, 0.5); + border-radius: 4px; +} + +.result-box::-webkit-scrollbar-thumb { + background: rgba(255, 107, 53, 0.5); + border-radius: 4px; +} + +.result-box::-webkit-scrollbar-thumb:hover { + background: rgba(255, 107, 53, 0.7); +} + +/* Footer */ +footer { + background: rgba(20, 20, 20, 0.8); + padding: 2rem 0; + text-align: center; + margin-top: 3rem; + border-top: 1px solid rgba(255, 107, 53, 0.2); +} + +footer p { + color: rgba(224, 224, 224, 0.7); + margin: 0.5rem 0; +} + +.timestamp { + font-size: 0.9rem; + color: rgba(224, 224, 224, 0.5); +} + +/* Links */ +a { + color: #ff6b35; + text-decoration: none; + transition: color 0.3s ease; +} + a:hover { + color: #f7931e; text-decoration: underline; } -/* Mike section styling */ -.mike-section { - margin-top: 30px; - max-width: 600px; - margin-left: auto; - margin-right: auto; - padding: 20px; - background-color: rgba(50, 50, 50, 0.3); - border-radius: 8px; +/* Responsive Design */ +@media (max-width: 768px) { + header h1 { + font-size: 2rem; + } + + .subtitle { + font-size: 1rem; + } + + .card { + padding: 1.5rem; + } + + .search-box { + flex-direction: column; + } + + .search-box input, + .search-box button { + width: 100%; + } + + .status-section { + grid-template-columns: 1fr; + } } -.mike-section h2 { - color: #f0f0f0; - margin-top: 0; +/* Loading Animation */ +@keyframes pulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } } -.mike-section p { - line-height: 1.6; - margin-bottom: 15px; -} \ No newline at end of file +.status-content:empty::after { + content: 'Loading...'; + animation: pulse 1.5s ease-in-out infinite; +} diff --git a/templates/assets/img/favicon.png b/templates/assets/img/favicon.png index b325464..03772ad 100644 Binary files a/templates/assets/img/favicon.png and b/templates/assets/img/favicon.png differ diff --git a/templates/index.html b/templates/index.html index f3aac2c..fa0c5e2 100644 --- a/templates/index.html +++ b/templates/index.html @@ -4,57 +4,331 @@ - Nathan.Woodburn/ + Fire Explorer - Handshake Blockchain Explorer -
-
-

Nathan.Woodburn/

- The current date and time is {{datetime}} -
+
+
+

Fire Icon Fire Explorer

+

Handshake Blockchain Explorer

+
+
-
-
-

Pulling data

- This is a test content area that will be updated with data from the server. -
-
- Timestamp: Waiting to pull data -
+
+ +
+
+

Node Status

+
Loading...
+
+
+

Chain Info

+
Loading...
+
+
+

Mempool

+
Loading...
+
+
+ + +
+
+

Search Blockchain

+
+ + + + +
+ + +
+ +
+
+ + +
+ +
+
+ + +
+ +
+
+ + +
+ + +
+
+
+
+ + +
+
+

Coin Lookup

+ +
+
+
+
+ + + // Format chain data nicely + function formatChainData(chain) { + return ` +
+
Height: ${chain.height.toLocaleString()}
+
Progress: ${(chain.progress * 100).toFixed(2)}%
+
TX Count: ${chain.state.tx.toLocaleString()}
+
Coins: ${chain.state.coin.toLocaleString()}
+
Value: ${(chain.state.value / 1e6).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})} HNS
+
Burned: ${(chain.state.burned / 1e6).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2})} HNS
+
Tip: ${chain.tip}
+
Tree Root: ${chain.treeRoot}
+
+ `; + } + // Format mempool data nicely + function formatMempoolData(mempool) { + // Check if mempool is an array of transaction IDs + if (Array.isArray(mempool)) { + return ` +
+
Transactions: ${mempool.length.toLocaleString()}
+
+ View All Transactions +
+
+ `; + } + // Fallback for object format + return ` +
+
Transactions: ${mempool.tx || 0}
+
Size: ${mempool.size ? (mempool.size / 1024).toFixed(2) + ' KB' : '0 KB'}
+
Orphans: ${mempool.orphans || 0}
+
Claims: ${mempool.claims || 0}
+
Airdrops: ${mempool.airdrops || 0}
+
+ `; + } + + // Display helper + function displayResult(elementId, data, key = null) { + const element = document.getElementById(elementId); + if (data.error) { + element.innerHTML = `
Error: ${data.error}
`; + } else { + if (key && data[key] !== undefined) { + // If the value is a simple string, display it without JSON formatting + if (typeof data[key] === 'string') { + element.innerHTML = `
${data[key]}
`; + } else { + element.innerHTML = `
${JSON.stringify(data[key], null, 2)}
`; + } + return; + } + element.innerHTML = `
${JSON.stringify(data, null, 2)}
`; + } + } + + // Load status on page load + async function loadStatus() { + const nodeStatus = await apiCall('status'); + displayResult('node-status', nodeStatus, 'status'); + + const chainStatus = await apiCall('chain'); + if (chainStatus.chain) { + document.getElementById('chain-status').innerHTML = formatChainData(chainStatus.chain); + } else { + displayResult('chain-status', chainStatus); + } + + const mempoolStatus = await apiCall('mempool'); + if (mempoolStatus && !mempoolStatus.error) { + document.getElementById('mempool-status').innerHTML = formatMempoolData(mempoolStatus); + } else { + displayResult('mempool-status', mempoolStatus); + } + } + + // Search functions + async function searchBlock() { + const blockId = document.getElementById('block-input').value.trim(); + if (!blockId) { + alert('Please enter a block height or hash'); + return; + } + const data = await apiCall(`block/${blockId}`); + displayResult('block-result', data); + } + + async function searchHeader() { + const blockId = document.getElementById('block-input').value.trim(); + if (!blockId) { + alert('Please enter a block height or hash'); + return; + } + const data = await apiCall(`header/${blockId}`); + displayResult('block-result', data); + } + + async function searchTx() { + const txId = document.getElementById('tx-input').value.trim(); + if (!txId) { + alert('Please enter a transaction ID'); + return; + } + const data = await apiCall(`tx/${txId}`); + displayResult('tx-result', data); + } + + async function searchAddressTx() { + const address = document.getElementById('address-input').value.trim(); + if (!address) { + alert('Please enter an address'); + return; + } + const data = await apiCall(`tx/address/${address}`); + displayResult('address-result', data); + } + + async function searchAddressCoins() { + const address = document.getElementById('address-input').value.trim(); + if (!address) { + alert('Please enter an address'); + return; + } + const data = await apiCall(`coin/address/${address}`); + displayResult('address-result', data); + } + + async function searchName() { + const name = document.getElementById('name-input').value.trim(); + if (!name) { + alert('Please enter a name'); + return; + } + const data = await apiCall(`name/${name}`); + displayResult('name-result', data); + } + + async function searchNameResource() { + const name = document.getElementById('name-input').value.trim(); + if (!name) { + alert('Please enter a name'); + return; + } + const data = await apiCall(`nameresource/${name}`); + displayResult('name-result', data); + } + + async function searchNameSummary() { + const name = document.getElementById('name-input').value.trim(); + if (!name) { + alert('Please enter a name'); + return; + } + const data = await apiCall(`namesummary/${name}`); + displayResult('name-result', data); + } + + async function searchNameHash() { + const nameHash = document.getElementById('namehash-input').value.trim(); + if (!nameHash) { + alert('Please enter a name hash'); + return; + } + const data = await apiCall(`namehash/${nameHash}`); + displayResult('name-result', data); + } + + async function searchCoin() { + const coinHash = document.getElementById('coin-hash').value.trim(); + const coinIndex = document.getElementById('coin-index').value.trim(); + if (!coinHash || !coinIndex) { + alert('Please enter both coin hash and index'); + return; + } + const data = await apiCall(`coin/${coinHash}/${coinIndex}`); + displayResult('coin-result', data); + } + + // Load status when page loads + window.addEventListener('load', loadStatus); + + // Refresh status every 30 seconds + setInterval(loadStatus, 30000); + \ No newline at end of file