Add fan curves display in web UI with activate button
This commit is contained in:
parent
3dfdcd1865
commit
0c20af8023
183
data/config.json
183
data/config.json
|
|
@ -11,7 +11,7 @@
|
|||
"fan_update_interval": 10,
|
||||
"min_speed": 10,
|
||||
"max_speed": 100,
|
||||
"panic_temp": 85,
|
||||
"panic_temp": 85.0,
|
||||
"panic_speed": 100,
|
||||
"panic_on_no_data": true,
|
||||
"no_data_timeout": 60,
|
||||
|
|
@ -20,54 +20,171 @@
|
|||
"fans": {},
|
||||
"fan_groups": {},
|
||||
"fan_curves": {
|
||||
"Default": {
|
||||
"points": [
|
||||
{
|
||||
"temp": 30,
|
||||
"speed": 15
|
||||
},
|
||||
{
|
||||
"temp": 40,
|
||||
"speed": 25
|
||||
},
|
||||
{
|
||||
"temp": 50,
|
||||
"speed": 40
|
||||
},
|
||||
{
|
||||
"temp": 60,
|
||||
"speed": 60
|
||||
},
|
||||
{
|
||||
"temp": 70,
|
||||
"speed": 80
|
||||
},
|
||||
{
|
||||
"temp": 80,
|
||||
"speed": 100
|
||||
}
|
||||
],
|
||||
"sensor_source": "cpu",
|
||||
"applies_to": "all"
|
||||
},
|
||||
"Balanced": {
|
||||
"points": [
|
||||
{"temp": 30, "speed": 10},
|
||||
{"temp": 35, "speed": 12},
|
||||
{"temp": 40, "speed": 15},
|
||||
{"temp": 45, "speed": 20},
|
||||
{"temp": 50, "speed": 30},
|
||||
{"temp": 55, "speed": 40},
|
||||
{"temp": 60, "speed": 55},
|
||||
{"temp": 65, "speed": 70},
|
||||
{"temp": 70, "speed": 85},
|
||||
{"temp": 75, "speed": 95},
|
||||
{"temp": 80, "speed": 100}
|
||||
{
|
||||
"temp": 30,
|
||||
"speed": 10
|
||||
},
|
||||
{
|
||||
"temp": 35,
|
||||
"speed": 12
|
||||
},
|
||||
{
|
||||
"temp": 40,
|
||||
"speed": 15
|
||||
},
|
||||
{
|
||||
"temp": 45,
|
||||
"speed": 20
|
||||
},
|
||||
{
|
||||
"temp": 50,
|
||||
"speed": 30
|
||||
},
|
||||
{
|
||||
"temp": 55,
|
||||
"speed": 40
|
||||
},
|
||||
{
|
||||
"temp": 60,
|
||||
"speed": 55
|
||||
},
|
||||
{
|
||||
"temp": 65,
|
||||
"speed": 70
|
||||
},
|
||||
{
|
||||
"temp": 70,
|
||||
"speed": 85
|
||||
},
|
||||
{
|
||||
"temp": 75,
|
||||
"speed": 95
|
||||
},
|
||||
{
|
||||
"temp": 80,
|
||||
"speed": 100
|
||||
}
|
||||
],
|
||||
"sensor_source": "cpu",
|
||||
"applies_to": "all"
|
||||
},
|
||||
"Silent": {
|
||||
"points": [
|
||||
{"temp": 30, "speed": 5},
|
||||
{"temp": 40, "speed": 10},
|
||||
{"temp": 50, "speed": 15},
|
||||
{"temp": 55, "speed": 25},
|
||||
{"temp": 60, "speed": 35},
|
||||
{"temp": 65, "speed": 50},
|
||||
{"temp": 70, "speed": 70},
|
||||
{"temp": 75, "speed": 85},
|
||||
{"temp": 80, "speed": 100}
|
||||
{
|
||||
"temp": 30,
|
||||
"speed": 5
|
||||
},
|
||||
{
|
||||
"temp": 40,
|
||||
"speed": 10
|
||||
},
|
||||
{
|
||||
"temp": 50,
|
||||
"speed": 15
|
||||
},
|
||||
{
|
||||
"temp": 55,
|
||||
"speed": 25
|
||||
},
|
||||
{
|
||||
"temp": 60,
|
||||
"speed": 35
|
||||
},
|
||||
{
|
||||
"temp": 65,
|
||||
"speed": 50
|
||||
},
|
||||
{
|
||||
"temp": 70,
|
||||
"speed": 70
|
||||
},
|
||||
{
|
||||
"temp": 75,
|
||||
"speed": 85
|
||||
},
|
||||
{
|
||||
"temp": 80,
|
||||
"speed": 100
|
||||
}
|
||||
],
|
||||
"sensor_source": "cpu",
|
||||
"applies_to": "all"
|
||||
},
|
||||
"Performance": {
|
||||
"points": [
|
||||
{"temp": 30, "speed": 20},
|
||||
{"temp": 35, "speed": 25},
|
||||
{"temp": 40, "speed": 35},
|
||||
{"temp": 45, "speed": 45},
|
||||
{"temp": 50, "speed": 55},
|
||||
{"temp": 55, "speed": 70},
|
||||
{"temp": 60, "speed": 85},
|
||||
{"temp": 65, "speed": 95},
|
||||
{"temp": 70, "speed": 100}
|
||||
{
|
||||
"temp": 30,
|
||||
"speed": 20
|
||||
},
|
||||
{
|
||||
"temp": 35,
|
||||
"speed": 25
|
||||
},
|
||||
{
|
||||
"temp": 40,
|
||||
"speed": 35
|
||||
},
|
||||
{
|
||||
"temp": 45,
|
||||
"speed": 45
|
||||
},
|
||||
{
|
||||
"temp": 50,
|
||||
"speed": 55
|
||||
},
|
||||
{
|
||||
"temp": 55,
|
||||
"speed": 70
|
||||
},
|
||||
{
|
||||
"temp": 60,
|
||||
"speed": 85
|
||||
},
|
||||
{
|
||||
"temp": 65,
|
||||
"speed": 95
|
||||
},
|
||||
{
|
||||
"temp": 70,
|
||||
"speed": 100
|
||||
}
|
||||
],
|
||||
"sensor_source": "cpu",
|
||||
"applies_to": "all"
|
||||
}
|
||||
},
|
||||
"active_curve": "Balanced",
|
||||
"theme": "dark"
|
||||
}
|
||||
"theme": "dark",
|
||||
"active_curve": "Balanced"
|
||||
}
|
||||
27
server.log
27
server.log
|
|
@ -1,14 +1,27 @@
|
|||
INFO: Started server process [20120]
|
||||
INFO: Started server process [25918]
|
||||
INFO: Waiting for application startup.
|
||||
INFO: Application startup complete.
|
||||
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
|
||||
INFO: 192.168.5.30:60395 - "GET /api/status HTTP/1.1" 401 Unauthorized
|
||||
INFO: 192.168.5.30:60395 - "GET /login HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:40728 - "GET /api/curves HTTP/1.1" 401 Unauthorized
|
||||
INFO: 192.168.5.30:64198 - "GET / HTTP/1.1" 200 OK
|
||||
INFO: 192.168.5.30:64198 - "GET /api/status HTTP/1.1" 401 Unauthorized
|
||||
INFO: 192.168.5.30:64198 - "GET /login HTTP/1.1" 200 OK
|
||||
/home/devmatrix/projects/fan-controller-v2/web_server.py:149: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
|
||||
self._sessions[token] = (username, datetime.utcnow() + timedelta(days=7))
|
||||
INFO: 127.0.0.1:40744 - "POST /api/auth/login HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:33886 - "POST /api/auth/login HTTP/1.1" 200 OK
|
||||
INFO: 192.168.5.30:61387 - "POST /api/auth/login HTTP/1.1" 200 OK
|
||||
INFO: 192.168.5.30:61387 - "GET / HTTP/1.1" 200 OK
|
||||
/home/devmatrix/projects/fan-controller-v2/web_server.py:156: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
|
||||
if datetime.utcnow() > expiry:
|
||||
2026-02-20 16:41:42,900 - fan_controller - INFO - Loaded config from /home/devmatrix/projects/fan-controller-v2/data/config.json
|
||||
INFO: 127.0.0.1:40756 - "GET /api/curves HTTP/1.1" 200 OK
|
||||
2026-02-20 17:07:43,397 - fan_controller - INFO - Loaded config from /home/devmatrix/projects/fan-controller-v2/data/config.json
|
||||
INFO: 192.168.5.30:61387 - "GET /api/status HTTP/1.1" 200 OK
|
||||
INFO: 192.168.5.30:61387 - "GET /favicon.ico HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:44038 - "GET /api/curves HTTP/1.1" 200 OK
|
||||
INFO: 192.168.5.30:61387 - "GET /api/status HTTP/1.1" 200 OK
|
||||
2026-02-20 17:07:48,925 - fan_controller - INFO - Saved config to /home/devmatrix/projects/fan-controller-v2/data/config.json
|
||||
2026-02-20 17:07:49,084 - fan_controller - INFO - Connected to IPMI at 192.168.5.191
|
||||
INFO: 192.168.5.30:61387 - "POST /api/config/ipmi HTTP/1.1" 200 OK
|
||||
/home/devmatrix/projects/fan-controller-v2/web_server.py:156: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
|
||||
if datetime.utcnow() > expiry:
|
||||
INFO: 192.168.5.30:61387 - "GET /api/status HTTP/1.1" 200 OK
|
||||
INFO: 192.168.5.30:61387 - "GET /favicon.ico HTTP/1.1" 200 OK
|
||||
INFO: 192.168.5.30:61387 - "GET /api/status HTTP/1.1" 200 OK
|
||||
|
|
|
|||
|
|
@ -792,6 +792,7 @@ def get_html(theme="dark"):
|
|||
if (currentStatus.config) {{
|
||||
currentConfig = currentStatus.config;
|
||||
updateConfigFields();
|
||||
updateCurvesList();
|
||||
}}
|
||||
}}
|
||||
|
||||
|
|
@ -818,6 +819,47 @@ def get_html(theme="dark"):
|
|||
document.getElementById('cfg-panic-no-data').checked = cfg.panic_on_no_data !== false;
|
||||
}}
|
||||
|
||||
function updateCurvesList() {{
|
||||
const cfg = currentConfig;
|
||||
const curves = cfg.fan_curves || {{}};
|
||||
const activeCurve = cfg.active_curve || 'Balanced';
|
||||
const container = document.getElementById('curves-list');
|
||||
|
||||
if (Object.keys(curves).length === 0) {{
|
||||
container.innerHTML = '<p style="color:var(--text-secondary);">No curves defined</p>';
|
||||
return;
|
||||
}}
|
||||
|
||||
container.innerHTML = Object.entries(curves).map(([name, curve]) => {{
|
||||
const isActive = name === activeCurve;
|
||||
const points = curve.points || [];
|
||||
const pointsStr = points.map(p => `${{p.temp}}°C→${{p.speed}}%`).join(', ');
|
||||
|
||||
return `<div class="fan-card" style="margin-bottom:10px;${{isActive ? 'border:2px solid var(--accent-success);' : ''}}">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;">
|
||||
<div>
|
||||
<div class="fan-name">${{name}} ${{isActive ? '<span style="color:var(--accent-success);">(Active)</span>' : ''}}</div>
|
||||
<div class="fan-info" style="font-size:0.8rem;margin-top:5px;">${{pointsStr}}</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:5px;">
|
||||
${{!isActive ? `<button class="secondary small" onclick="setActiveCurve('${{name}}')">Activate</button>` : ''}}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}}).join('');
|
||||
}}
|
||||
|
||||
async function setActiveCurve(name) {{
|
||||
const res = await api('/api/curves/active', {{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({{name}})
|
||||
}});
|
||||
if (!res) return;
|
||||
const data = await res.json();
|
||||
log(data.success ? `Curve "${{name}}" activated` : 'Failed: ' + data.error, data.success ? 'success' : 'error');
|
||||
fetchStatus();
|
||||
}}
|
||||
|
||||
// Tabs
|
||||
function showTab(tab) {{
|
||||
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
||||
|
|
|
|||
Loading…
Reference in New Issue