Add live log viewer and enable file logging in service
This commit is contained in:
18
app.py
18
app.py
@@ -24,15 +24,31 @@ def api_data():
|
|||||||
summary = miner_api.summary()
|
summary = miner_api.summary()
|
||||||
devs = miner_api.devs()
|
devs = miner_api.devs()
|
||||||
pools = miner_api.pools()
|
pools = miner_api.pools()
|
||||||
|
stats = miner_api.stats()
|
||||||
|
|
||||||
# Process data for easier frontend consumption
|
# Process data for easier frontend consumption
|
||||||
data = {
|
data = {
|
||||||
'summary': summary['SUMMARY'][0] if summary and 'SUMMARY' in summary else {},
|
'summary': summary['SUMMARY'][0] if summary and 'SUMMARY' in summary else {},
|
||||||
'devs': devs['DEVS'] if devs and 'DEVS' in devs 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)
|
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'])
|
@app.route('/save_settings', methods=['POST'])
|
||||||
def save_settings():
|
def save_settings():
|
||||||
# Construct config object from form
|
# Construct config object from form
|
||||||
|
|||||||
@@ -122,12 +122,8 @@ After=network.target necrohash-gui.service
|
|||||||
[Service]
|
[Service]
|
||||||
User=$USER
|
User=$USER
|
||||||
WorkingDirectory=$TARGET_DIR
|
WorkingDirectory=$TARGET_DIR
|
||||||
# ExecStart=/usr/local/bin/cgminer -c $TARGET_DIR/cgminer.conf
|
# Log stdout/stderr to file for the GUI to read
|
||||||
# Using nohup or similar might be needed if it tries to grab stdin/tty,
|
ExecStart=/bin/sh -c 'exec /usr/local/bin/cgminer --gridseed-options freq=850 -c $TARGET_DIR/cgminer.conf >> $TARGET_DIR/cgminer.log 2>&1'
|
||||||
# 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
|
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=10
|
RestartSec=10
|
||||||
|
|
||||||
|
|||||||
@@ -52,27 +52,58 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
// Update Top Stats
|
// Update Top Stats
|
||||||
if (summary) {
|
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;
|
const mhs = summary['MHS 5s'] || summary['MHS av'] || 0;
|
||||||
document.getElementById('stat-mhs').innerText = parseFloat(mhs).toFixed(2);
|
document.getElementById('stat-mhs').innerText = parseFloat(mhs).toFixed(2);
|
||||||
document.getElementById('stat-accepted').innerText = formatNumber(summary['Accepted'] || 0);
|
document.getElementById('stat-accepted').innerText = formatNumber(summary['Accepted'] || 0);
|
||||||
document.getElementById('stat-hw').innerText = formatNumber(summary['Hardware Errors'] || 0);
|
document.getElementById('stat-hw').innerText = formatNumber(summary['Hardware Errors'] || 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update Device Count
|
// Update Device Count
|
||||||
if (devs) {
|
if (devs) {
|
||||||
document.getElementById('stat-devices').innerText = devs.length;
|
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;
|
let maxTemp = 0;
|
||||||
|
const tbody = document.getElementById('devs-table-body');
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
if (devs && devs.length > 0) {
|
if (devs && devs.length > 0) {
|
||||||
devs.forEach(dev => {
|
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 = `
|
||||||
|
<td>${dev['ID']}</td>
|
||||||
|
<td>${dev['Name'] || 'Gridseed'}</td>
|
||||||
|
<td>${dev['Enabled']}</td>
|
||||||
|
<td>${dev['Status']}</td>
|
||||||
|
<td class="${temp > 50 ? 'text-warning' : 'text-success'}">${temp > 0 ? temp.toFixed(1) : '-'}</td>
|
||||||
|
<td>${parseFloat(dev['MHS 5s'] || 0).toFixed(2)}</td>
|
||||||
|
<td>${parseFloat(dev['MHS av'] || 0).toFixed(2)}</td>
|
||||||
|
<td>${dev['Accepted']}</td>
|
||||||
|
<td>${dev['Rejected']}</td>
|
||||||
|
<td>${dev['Hardware Errors']}</td>
|
||||||
|
`;
|
||||||
|
tbody.appendChild(tr);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="10" class="text-center text-muted">Keine Geräte gefunden...</td></tr>';
|
||||||
}
|
}
|
||||||
document.getElementById('stat-temp').innerText = maxTemp.toFixed(1);
|
document.getElementById('stat-temp').innerText = maxTemp.toFixed(1);
|
||||||
|
|
||||||
@@ -140,7 +171,26 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
.catch(err => console.error('Error fetching data:', err));
|
.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
|
// Update every 3 seconds
|
||||||
setInterval(updateDashboard, 3000);
|
setInterval(updateDashboard, 3000);
|
||||||
|
setInterval(updateLog, 5000);
|
||||||
updateDashboard(); // Initial call
|
updateDashboard(); // Initial call
|
||||||
|
updateLog();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -81,7 +81,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Device Details -->
|
<!-- Device Details -->
|
||||||
<div class="row g-3">
|
<div class="row g-3 mb-4">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card bg-dark border-secondary">
|
<div class="card bg-dark border-secondary">
|
||||||
<div class="card-header border-secondary">Gerätestatus</div>
|
<div class="card-header border-secondary">Gerätestatus</div>
|
||||||
@@ -113,5 +113,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Miner Log -->
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card bg-dark border-secondary">
|
||||||
|
<div class="card-header border-secondary d-flex justify-content-between align-items-center">
|
||||||
|
<span>Miner Log (Letzte 50 Zeilen)</span>
|
||||||
|
<small class="text-muted">Aktualisiert automatisch</small>
|
||||||
|
</div>
|
||||||
|
<div class="card-body bg-black font-monospace p-0">
|
||||||
|
<div id="miner-log" class="p-3" style="height: 300px; overflow-y: auto; white-space: pre-wrap; color: #0f0; font-size: 0.85rem;">Lade Log...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user