feat(dashboard): add Electrum active servers page and optimize ElectrumX card/perf
This commit is contained in:
@@ -9,6 +9,8 @@ import requests
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import copy
|
||||
import threading
|
||||
from datetime import datetime
|
||||
import psutil
|
||||
import socket
|
||||
@@ -21,6 +23,127 @@ PALLADIUM_RPC_HOST = os.getenv('PALLADIUM_RPC_HOST', 'palladiumd')
|
||||
PALLADIUM_RPC_PORT = int(os.getenv('PALLADIUM_RPC_PORT', '2332'))
|
||||
ELECTRUMX_RPC_HOST = os.getenv('ELECTRUMX_RPC_HOST', 'electrumx')
|
||||
ELECTRUMX_RPC_PORT = int(os.getenv('ELECTRUMX_RPC_PORT', '8000'))
|
||||
ELECTRUMX_STATS_TTL = int(os.getenv('ELECTRUMX_STATS_TTL', '60'))
|
||||
ELECTRUMX_SERVERS_TTL = int(os.getenv('ELECTRUMX_SERVERS_TTL', '120'))
|
||||
ELECTRUMX_EMPTY_SERVERS_TTL = int(os.getenv('ELECTRUMX_EMPTY_SERVERS_TTL', '15'))
|
||||
|
||||
# In-memory caches for fast card stats and heavier server probing stats
|
||||
_electrumx_stats_cache = {'timestamp': 0.0, 'stats': None}
|
||||
_electrumx_servers_cache = {'timestamp': 0.0, 'stats': None}
|
||||
|
||||
|
||||
def warm_electrumx_caches_async():
|
||||
"""Pre-warm caches in background to reduce first-load latency."""
|
||||
def _worker():
|
||||
try:
|
||||
get_electrumx_stats_cached(force_refresh=True, include_addnode_probes=False)
|
||||
get_electrumx_stats_cached(force_refresh=True, include_addnode_probes=True)
|
||||
except Exception as e:
|
||||
print(f"ElectrumX cache warmup error: {e}")
|
||||
|
||||
threading.Thread(target=_worker, daemon=True).start()
|
||||
|
||||
|
||||
def parse_addnode_hosts(conf_path='/palladium-config/palladium.conf'):
|
||||
"""Extract addnode hosts from palladium.conf"""
|
||||
hosts = []
|
||||
try:
|
||||
with open(conf_path, 'r') as f:
|
||||
for raw_line in f:
|
||||
line = raw_line.strip()
|
||||
if not line or line.startswith('#') or not line.startswith('addnode='):
|
||||
continue
|
||||
value = line.split('=', 1)[1].strip()
|
||||
if not value:
|
||||
continue
|
||||
host = value.rsplit(':', 1)[0] if ':' in value else value
|
||||
if host and host not in hosts:
|
||||
hosts.append(host)
|
||||
except Exception as e:
|
||||
print(f"Error parsing addnode hosts: {e}")
|
||||
return hosts
|
||||
|
||||
|
||||
def probe_electrum_server(host, port=50001, timeout=1.2):
|
||||
"""Check if an Electrum server is reachable and speaking protocol on host:port"""
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(timeout)
|
||||
sock.connect((host, port))
|
||||
request = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 100,
|
||||
"method": "server.version",
|
||||
"params": ["palladium-dashboard", "1.4"]
|
||||
}
|
||||
sock.send((json.dumps(request) + '\n').encode())
|
||||
response = sock.recv(4096).decode()
|
||||
sock.close()
|
||||
data = json.loads(response)
|
||||
if 'result' in data:
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
def is_electrumx_reachable(timeout=1.0):
|
||||
"""Fast ElectrumX liveness check used by /api/health"""
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(timeout)
|
||||
sock.connect((ELECTRUMX_RPC_HOST, 50001))
|
||||
request = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 999,
|
||||
"method": "server.version",
|
||||
"params": ["palladium-health", "1.4"]
|
||||
}
|
||||
sock.send((json.dumps(request) + '\n').encode())
|
||||
response = sock.recv(4096).decode()
|
||||
sock.close()
|
||||
data = json.loads(response)
|
||||
return 'result' in data
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def is_electrumx_reachable_retry():
|
||||
"""Retry-aware liveness check to avoid transient false negatives."""
|
||||
if is_electrumx_reachable(timeout=1.2):
|
||||
return True
|
||||
time.sleep(0.15)
|
||||
return is_electrumx_reachable(timeout=2.0)
|
||||
|
||||
|
||||
def get_electrumx_stats_cached(force_refresh=False, include_addnode_probes=False):
|
||||
"""Return cached ElectrumX stats unless cache is stale."""
|
||||
cache = _electrumx_servers_cache if include_addnode_probes else _electrumx_stats_cache
|
||||
ttl = ELECTRUMX_SERVERS_TTL if include_addnode_probes else ELECTRUMX_STATS_TTL
|
||||
now = time.time()
|
||||
cached = cache.get('stats')
|
||||
cached_ts = cache.get('timestamp', 0.0)
|
||||
|
||||
if (
|
||||
include_addnode_probes
|
||||
and cached is not None
|
||||
and (cached.get('active_servers_count', 0) == 0)
|
||||
):
|
||||
ttl = ELECTRUMX_EMPTY_SERVERS_TTL
|
||||
|
||||
if not force_refresh and cached is not None and (now - cached_ts) < ttl:
|
||||
return copy.deepcopy(cached)
|
||||
|
||||
fresh = get_electrumx_stats(include_addnode_probes=include_addnode_probes)
|
||||
if fresh is not None:
|
||||
cache['timestamp'] = now
|
||||
cache['stats'] = fresh
|
||||
return copy.deepcopy(fresh)
|
||||
|
||||
# Fallback to stale cache if fresh fetch fails
|
||||
if cached is not None:
|
||||
return copy.deepcopy(cached)
|
||||
return None
|
||||
|
||||
# Read RPC credentials from palladium.conf
|
||||
def get_rpc_credentials():
|
||||
@@ -78,7 +201,7 @@ def palladium_rpc_call(method, params=None):
|
||||
print(f"RPC call error ({method}): {e}")
|
||||
return None
|
||||
|
||||
def get_electrumx_stats():
|
||||
def get_electrumx_stats(include_addnode_probes=False):
|
||||
"""Get ElectrumX statistics via Electrum protocol and system info"""
|
||||
try:
|
||||
import socket
|
||||
@@ -94,6 +217,10 @@ def get_electrumx_stats():
|
||||
'hash_function': '',
|
||||
'pruning': None,
|
||||
'sessions': 0,
|
||||
'peer_discovery': 'unknown',
|
||||
'peer_announce': 'unknown',
|
||||
'active_servers': [],
|
||||
'active_servers_count': 0,
|
||||
'requests': 0,
|
||||
'subs': 0,
|
||||
'uptime': 0,
|
||||
@@ -106,7 +233,7 @@ def get_electrumx_stats():
|
||||
# Get server IP address
|
||||
try:
|
||||
# Try to get public IP from external service
|
||||
response = requests.get('https://api.ipify.org?format=json', timeout=3)
|
||||
response = requests.get('https://api.ipify.org?format=json', timeout=0.4)
|
||||
if response.status_code == 200:
|
||||
stats['server_ip'] = response.json().get('ip', 'Unknown')
|
||||
else:
|
||||
@@ -152,6 +279,131 @@ def get_electrumx_stats():
|
||||
except Exception as e:
|
||||
print(f"ElectrumX protocol error: {e}")
|
||||
|
||||
# Get peers discovered by ElectrumX
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(5)
|
||||
sock.connect((ELECTRUMX_RPC_HOST, 50001))
|
||||
|
||||
request = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"method": "server.peers.subscribe",
|
||||
"params": []
|
||||
}
|
||||
|
||||
sock.send((json.dumps(request) + '\n').encode())
|
||||
response = sock.recv(65535).decode()
|
||||
sock.close()
|
||||
|
||||
data = json.loads(response)
|
||||
if 'result' in data and isinstance(data['result'], list):
|
||||
peers = []
|
||||
for peer in data['result']:
|
||||
if not isinstance(peer, list) or len(peer) < 3:
|
||||
continue
|
||||
host = peer[1]
|
||||
features = peer[2] if isinstance(peer[2], list) else []
|
||||
tcp_port = None
|
||||
ssl_port = None
|
||||
for feat in features:
|
||||
if isinstance(feat, str) and feat.startswith('t') and feat[1:].isdigit():
|
||||
tcp_port = feat[1:]
|
||||
if isinstance(feat, str) and feat.startswith('s') and feat[1:].isdigit():
|
||||
ssl_port = feat[1:]
|
||||
if host:
|
||||
peers.append({
|
||||
'host': host,
|
||||
'tcp_port': tcp_port,
|
||||
'ssl_port': ssl_port
|
||||
})
|
||||
|
||||
stats['active_servers'] = peers
|
||||
stats['active_servers_count'] = len(peers)
|
||||
except Exception as e:
|
||||
print(f"ElectrumX peers error: {e}")
|
||||
|
||||
# Keep peers list without self for dashboard card count
|
||||
try:
|
||||
merged = []
|
||||
seen = set()
|
||||
self_host = (stats.get('server_ip') or '').strip()
|
||||
for peer in (stats.get('active_servers') or []):
|
||||
host = (peer.get('host') or '').strip()
|
||||
tcp_port = str(peer.get('tcp_port') or '50001')
|
||||
if not host:
|
||||
continue
|
||||
if self_host and host == self_host:
|
||||
continue
|
||||
key = f"{host}:{tcp_port}"
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
merged.append({
|
||||
'host': host,
|
||||
'tcp_port': tcp_port,
|
||||
'ssl_port': peer.get('ssl_port')
|
||||
})
|
||||
stats['active_servers'] = merged
|
||||
stats['active_servers_count'] = len(merged)
|
||||
except Exception as e:
|
||||
print(f"Electrum peers normalization error: {e}")
|
||||
|
||||
# Optional full probing for dedicated servers page
|
||||
if include_addnode_probes:
|
||||
try:
|
||||
addnode_hosts = parse_addnode_hosts()
|
||||
extra_servers = []
|
||||
for host in addnode_hosts:
|
||||
if probe_electrum_server(host, 50001, timeout=0.5):
|
||||
extra_servers.append({
|
||||
'host': host,
|
||||
'tcp_port': '50001',
|
||||
'ssl_port': None
|
||||
})
|
||||
|
||||
merged = []
|
||||
seen = set()
|
||||
self_host = (stats.get('server_ip') or '').strip()
|
||||
for peer in (stats.get('active_servers') or []) + extra_servers:
|
||||
host = (peer.get('host') or '').strip()
|
||||
tcp_port = str(peer.get('tcp_port') or '50001')
|
||||
if not host:
|
||||
continue
|
||||
if self_host and host == self_host:
|
||||
continue
|
||||
key = f"{host}:{tcp_port}"
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
merged.append({
|
||||
'host': host,
|
||||
'tcp_port': tcp_port,
|
||||
'ssl_port': peer.get('ssl_port')
|
||||
})
|
||||
|
||||
stats['active_servers'] = merged
|
||||
stats['active_servers_count'] = len(merged)
|
||||
except Exception as e:
|
||||
print(f"Supplemental Electrum discovery error: {e}")
|
||||
|
||||
# Read peer discovery/announce settings from electrumx container env
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['docker', 'exec', 'electrumx-server', 'sh', '-c',
|
||||
'printf "%s|%s" "${PEER_DISCOVERY:-unknown}" "${PEER_ANNOUNCE:-unknown}"'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=2
|
||||
)
|
||||
if result.returncode == 0:
|
||||
values = (result.stdout or '').strip().split('|', 1)
|
||||
if len(values) == 2:
|
||||
stats['peer_discovery'] = values[0] or 'unknown'
|
||||
stats['peer_announce'] = values[1] or 'unknown'
|
||||
except Exception as e:
|
||||
print(f"Peer discovery env read error: {e}")
|
||||
|
||||
# Try to get container stats via Docker
|
||||
try:
|
||||
# Get container uptime
|
||||
@@ -215,6 +467,11 @@ def peers():
|
||||
"""Serve peers page"""
|
||||
return render_template('peers.html')
|
||||
|
||||
@app.route('/electrum-servers')
|
||||
def electrum_servers():
|
||||
"""Serve Electrum active servers page"""
|
||||
return render_template('electrum_servers.html')
|
||||
|
||||
@app.route('/api/palladium/info')
|
||||
def palladium_info():
|
||||
"""Get Palladium node blockchain info"""
|
||||
@@ -299,8 +556,15 @@ def recent_blocks():
|
||||
def electrumx_stats():
|
||||
"""Get ElectrumX server statistics"""
|
||||
try:
|
||||
stats = get_electrumx_stats()
|
||||
stats = get_electrumx_stats_cached(include_addnode_probes=False)
|
||||
if stats:
|
||||
# If fast path reports no servers, reuse full servers cache if available.
|
||||
if (stats.get('active_servers_count') or 0) == 0:
|
||||
heavy_stats = get_electrumx_stats_cached(include_addnode_probes=True)
|
||||
if heavy_stats and (heavy_stats.get('active_servers_count') or 0) > 0:
|
||||
stats['active_servers'] = heavy_stats.get('active_servers', [])
|
||||
stats['active_servers_count'] = heavy_stats.get('active_servers_count', 0)
|
||||
|
||||
# Get additional info from logs if available
|
||||
try:
|
||||
# Try to get container stats
|
||||
@@ -315,7 +579,6 @@ def electrumx_stats():
|
||||
if result.returncode == 0:
|
||||
# Process is running, add placeholder stats
|
||||
stats['status'] = 'running'
|
||||
stats['sessions'] = 0 # Will show 0 for now
|
||||
stats['requests'] = 0
|
||||
stats['subs'] = 0
|
||||
except:
|
||||
@@ -329,6 +592,28 @@ def electrumx_stats():
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/electrumx/servers')
|
||||
def electrumx_servers():
|
||||
"""Get active Electrum servers discovered by this node"""
|
||||
try:
|
||||
stats = get_electrumx_stats_cached(include_addnode_probes=True)
|
||||
if not stats:
|
||||
return jsonify({'error': 'Cannot connect to ElectrumX'}), 500
|
||||
|
||||
servers = stats.get('active_servers') or []
|
||||
if len(servers) == 0:
|
||||
# Fallback to fast discovery results if full probing is temporarily empty.
|
||||
fast_stats = get_electrumx_stats_cached(include_addnode_probes=False)
|
||||
if fast_stats:
|
||||
servers = fast_stats.get('active_servers') or []
|
||||
return jsonify({
|
||||
'servers': servers,
|
||||
'total': len(servers),
|
||||
'timestamp': datetime.now().isoformat()
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/system/resources')
|
||||
def system_resources():
|
||||
"""Get system resource usage"""
|
||||
@@ -363,7 +648,10 @@ def system_resources():
|
||||
def health():
|
||||
"""Health check endpoint"""
|
||||
palladium_ok = palladium_rpc_call('getblockchaininfo') is not None
|
||||
electrumx_ok = get_electrumx_stats() is not None
|
||||
stats = get_electrumx_stats_cached(include_addnode_probes=False)
|
||||
if not stats or stats.get('server_version') in (None, '', 'Unknown'):
|
||||
stats = get_electrumx_stats_cached(force_refresh=True, include_addnode_probes=False)
|
||||
electrumx_ok = bool(stats and (stats.get('server_version') not in (None, '', 'Unknown')))
|
||||
|
||||
return jsonify({
|
||||
'status': 'healthy' if (palladium_ok and electrumx_ok) else 'degraded',
|
||||
@@ -375,4 +663,5 @@ def health():
|
||||
})
|
||||
|
||||
if __name__ == '__main__':
|
||||
warm_electrumx_caches_async()
|
||||
app.run(host='0.0.0.0', port=8080, debug=False)
|
||||
|
||||
@@ -205,10 +205,6 @@ async function updateElectrumXStats() {
|
||||
}
|
||||
document.getElementById('serverVersion').textContent = serverVersion;
|
||||
|
||||
// Active sessions
|
||||
const sessions = typeof data.stats.sessions === 'number' ? data.stats.sessions : '--';
|
||||
document.getElementById('activeSessions').textContent = sessions;
|
||||
|
||||
// Database size
|
||||
const dbSize = data.stats.db_size > 0 ? formatBytes(data.stats.db_size) : '--';
|
||||
document.getElementById('dbSize').textContent = dbSize;
|
||||
@@ -225,6 +221,10 @@ async function updateElectrumXStats() {
|
||||
|
||||
// SSL Port
|
||||
document.getElementById('sslPort').textContent = data.stats.ssl_port || 50002;
|
||||
|
||||
// Active servers from peer discovery
|
||||
const activeServers = Array.isArray(data.stats.active_servers) ? data.stats.active_servers : [];
|
||||
document.getElementById('activeServersCount').textContent = data.stats.active_servers_count ?? activeServers.length;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
|
||||
57
web-dashboard/static/electrum_servers.js
Normal file
57
web-dashboard/static/electrum_servers.js
Normal file
@@ -0,0 +1,57 @@
|
||||
// Electrum Active Servers Page JavaScript
|
||||
|
||||
function updateLastUpdateTime() {
|
||||
const now = new Date().toLocaleString();
|
||||
document.getElementById('lastUpdate').textContent = now;
|
||||
}
|
||||
|
||||
async function updateElectrumServers() {
|
||||
try {
|
||||
const response = await fetch('/api/electrumx/servers');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
console.error('Electrum servers error:', data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
const servers = Array.isArray(data.servers) ? data.servers : [];
|
||||
const tbody = document.getElementById('electrumServersTable');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (servers.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="3" class="loading">No active servers found</td></tr>';
|
||||
document.getElementById('totalServers').textContent = '0';
|
||||
document.getElementById('tcpReachable').textContent = '0';
|
||||
return;
|
||||
}
|
||||
|
||||
servers.forEach(server => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td class="peer-addr">${server.host || '--'}</td>
|
||||
<td>${server.tcp_port || '--'}</td>
|
||||
<td>${server.ssl_port || '--'}</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
document.getElementById('totalServers').textContent = String(servers.length);
|
||||
const tcpCount = servers.filter(s => !!s.tcp_port).length;
|
||||
document.getElementById('tcpReachable').textContent = String(tcpCount);
|
||||
} catch (error) {
|
||||
console.error('Error fetching Electrum servers:', error);
|
||||
document.getElementById('electrumServersTable').innerHTML =
|
||||
'<tr><td colspan="3" class="loading">Error loading servers</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
async function updateAll() {
|
||||
updateLastUpdateTime();
|
||||
await updateElectrumServers();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
await updateAll();
|
||||
setInterval(updateAll, 10000);
|
||||
});
|
||||
@@ -242,6 +242,12 @@ body {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
#activeServers {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Resource Monitoring */
|
||||
.resource-item {
|
||||
display: grid;
|
||||
|
||||
77
web-dashboard/templates/electrum_servers.html
Normal file
77
web-dashboard/templates/electrum_servers.html
Normal file
@@ -0,0 +1,77 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Electrum Active Servers - Palladium Dashboard</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}?v=11">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<div class="header-content">
|
||||
<h1>
|
||||
<svg class="logo-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
|
||||
</svg>
|
||||
Electrum Active Servers
|
||||
</h1>
|
||||
<a href="/" class="back-button">
|
||||
<span>← Back to Dashboard</span>
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="dashboard-grid">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>Discovery Summary</h2>
|
||||
<span class="card-icon">🌐</span>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="stat-grid">
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">Total Active Servers</div>
|
||||
<div class="stat-value" id="totalServers">--</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">TCP 50001 Reachable</div>
|
||||
<div class="stat-value" id="tcpReachable">--</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card full-width">
|
||||
<div class="card-header">
|
||||
<h2>Other Active Servers</h2>
|
||||
<span class="card-icon">⚡</span>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="table-container">
|
||||
<table class="blocks-table peers-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Host</th>
|
||||
<th>TCP Port</th>
|
||||
<th>SSL Port</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="electrumServersTable">
|
||||
<tr><td colspan="3" class="loading">Loading servers...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<p>Last updated: <span id="lastUpdate">--</span></p>
|
||||
<p>Auto-refresh every 10 seconds</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="{{ url_for('static', filename='electrum_servers.js') }}?v=1"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Palladium & ElectrumX Dashboard</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}?v=10">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}?v=11">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
@@ -105,10 +105,6 @@
|
||||
<div class="stat-label">Server Version</div>
|
||||
<div class="stat-value" id="serverVersion">--</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">Active Sessions</div>
|
||||
<div class="stat-value" id="activeSessions">--</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-label">Database Size</div>
|
||||
<div class="stat-value" id="dbSize">--</div>
|
||||
@@ -117,6 +113,10 @@
|
||||
<div class="stat-label">Uptime</div>
|
||||
<div class="stat-value" id="uptime">--</div>
|
||||
</div>
|
||||
<a href="/electrum-servers" class="stat-item stat-link">
|
||||
<div class="stat-label">Active Servers</div>
|
||||
<div class="stat-value" id="activeServersCount">--</div>
|
||||
</a>
|
||||
<div class="stat-item full-width">
|
||||
<div class="stat-label">Server IP</div>
|
||||
<div class="stat-value" id="serverIP">--</div>
|
||||
@@ -195,6 +195,6 @@
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="{{ url_for('static', filename='dashboard.js') }}?v=10"></script>
|
||||
<script src="{{ url_for('static', filename='dashboard.js') }}?v=11"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user