From 507c4a10ea8781f29cce7e347570ed45666f4f17 Mon Sep 17 00:00:00 2001 From: Gemini Bot Date: Tue, 20 Jan 2026 13:19:06 +0000 Subject: [PATCH] Add live log viewer and enable file logging in service --- app.py | 18 ++++++++++- install.sh | 8 ++--- static/js/dashboard.js | 64 +++++++++++++++++++++++++++++++++++----- templates/dashboard.html | 17 ++++++++++- 4 files changed, 92 insertions(+), 15 deletions(-) diff --git a/app.py b/app.py index 6512ed2..80bf4af 100644 --- a/app.py +++ b/app.py @@ -24,15 +24,31 @@ def api_data(): summary = miner_api.summary() devs = miner_api.devs() pools = miner_api.pools() + stats = miner_api.stats() # Process data for easier frontend consumption data = { 'summary': summary['SUMMARY'][0] if summary and 'SUMMARY' in summary else {}, 'devs': devs['DEVS'] if devs and 'DEVS' in devs else [], - 'pools': pools['POOLS'] if pools and 'POOLS' in pools else [] + 'pools': pools['POOLS'] if pools and 'POOLS' in pools else [], + 'stats': stats if stats else {} } return jsonify(data) +@app.route('/api/log') +def api_log(): + log_path = 'cgminer.log' + if not os.path.exists(log_path): + return jsonify({'log': 'Kein Log gefunden.'}) + + try: + # Read last 50 lines + with open(log_path, 'r') as f: + lines = f.readlines() + return jsonify({'log': ''.join(lines[-50:])}) + except Exception as e: + return jsonify({'log': f'Fehler beim Lesen des Logs: {str(e)}'}) + @app.route('/save_settings', methods=['POST']) def save_settings(): # Construct config object from form diff --git a/install.sh b/install.sh index bca8eed..19fc9c3 100755 --- a/install.sh +++ b/install.sh @@ -122,12 +122,8 @@ After=network.target necrohash-gui.service [Service] User=$USER WorkingDirectory=$TARGET_DIR -# ExecStart=/usr/local/bin/cgminer -c $TARGET_DIR/cgminer.conf -# Using nohup or similar might be needed if it tries to grab stdin/tty, -# but usually systemd handles it. -# Explicitly disabling ncurses/text interaction via --text-only or --real-quiet if available is good. -# For now, standard call: -ExecStart=/usr/local/bin/cgminer --gridseed-options freq=850 -c $TARGET_DIR/cgminer.conf +# Log stdout/stderr to file for the GUI to read +ExecStart=/bin/sh -c 'exec /usr/local/bin/cgminer --gridseed-options freq=850 -c $TARGET_DIR/cgminer.conf >> $TARGET_DIR/cgminer.log 2>&1' Restart=always RestartSec=10 diff --git a/static/js/dashboard.js b/static/js/dashboard.js index d8000fe..e25b91b 100644 --- a/static/js/dashboard.js +++ b/static/js/dashboard.js @@ -52,27 +52,58 @@ document.addEventListener('DOMContentLoaded', function() { // Update Top Stats if (summary) { - // Cgminer returns MHS in different fields depending on version, usually "MHS 5s" or similar - // The API returns dict keys. - // Let's inspect typical cgminer summary keys: "MHS av", "MHS 5s", "Accepted", "Hardware Errors", "Utility" - const mhs = summary['MHS 5s'] || summary['MHS av'] || 0; document.getElementById('stat-mhs').innerText = parseFloat(mhs).toFixed(2); document.getElementById('stat-accepted').innerText = formatNumber(summary['Accepted'] || 0); document.getElementById('stat-hw').innerText = formatNumber(summary['Hardware Errors'] || 0); } - + // Update Device Count if (devs) { document.getElementById('stat-devices').innerText = devs.length; } - // Update Max Temp + // Helper to find temp in stats if missing in devs + // Cgminer 'stats' command returns specific driver info. + // Structure: stats ->STATS -> list of dicts. usually one dict per device with ID keys like 'MM ID 0', etc. + // Or simplified list. + function getTempFallback(devId, statsData) { + // Try to find matching ID in stats + // This is heuristic as structure varies wildy between forks + return 0; + } + + // Update Devs Table & Max Temp let maxTemp = 0; + const tbody = document.getElementById('devs-table-body'); + tbody.innerHTML = ''; + if (devs && devs.length > 0) { devs.forEach(dev => { - if (dev['Temperature'] > maxTemp) maxTemp = dev['Temperature']; + let temp = dev['Temperature'] || 0; + + // Fallback logic could go here if we understood the STATS structure for this specific fork + // For now we rely on standard API. + + if (temp > maxTemp) maxTemp = temp; + + const tr = document.createElement('tr'); + tr.innerHTML = ` + ${dev['ID']} + ${dev['Name'] || 'Gridseed'} + ${dev['Enabled']} + ${dev['Status']} + ${temp > 0 ? temp.toFixed(1) : '-'} + ${parseFloat(dev['MHS 5s'] || 0).toFixed(2)} + ${parseFloat(dev['MHS av'] || 0).toFixed(2)} + ${dev['Accepted']} + ${dev['Rejected']} + ${dev['Hardware Errors']} + `; + tbody.appendChild(tr); }); + } else { + tbody.innerHTML = 'Keine Geräte gefunden...'; } document.getElementById('stat-temp').innerText = maxTemp.toFixed(1); @@ -140,7 +171,26 @@ document.addEventListener('DOMContentLoaded', function() { .catch(err => console.error('Error fetching data:', err)); } + // Update Log + function updateLog() { + fetch('/api/log') + .then(response => response.json()) + .then(data => { + const logDiv = document.getElementById('miner-log'); + if (data.log) { + const isScrolledToBottom = logDiv.scrollHeight - logDiv.clientHeight <= logDiv.scrollTop + 1; + logDiv.innerText = data.log; + if (isScrolledToBottom) { + logDiv.scrollTop = logDiv.scrollHeight; + } + } + }) + .catch(err => console.error('Error fetching log:', err)); + } + // Update every 3 seconds setInterval(updateDashboard, 3000); + setInterval(updateLog, 5000); updateDashboard(); // Initial call + updateLog(); }); diff --git a/templates/dashboard.html b/templates/dashboard.html index 9d485b0..f6f5cd4 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -81,7 +81,7 @@ -
+
Gerätestatus
@@ -113,5 +113,20 @@
+ + +
+
+
+
+ Miner Log (Letzte 50 Zeilen) + Aktualisiert automatisch +
+
+
Lade Log...
+
+
+
+
{% endblock %}