Add fan curves display in web UI with activate button

This commit is contained in:
devmatrix 2026-02-20 17:07:53 +00:00
parent 3dfdcd1865
commit 0c20af8023
3 changed files with 212 additions and 40 deletions

View File

@ -11,7 +11,7 @@
"fan_update_interval": 10, "fan_update_interval": 10,
"min_speed": 10, "min_speed": 10,
"max_speed": 100, "max_speed": 100,
"panic_temp": 85, "panic_temp": 85.0,
"panic_speed": 100, "panic_speed": 100,
"panic_on_no_data": true, "panic_on_no_data": true,
"no_data_timeout": 60, "no_data_timeout": 60,
@ -20,54 +20,171 @@
"fans": {}, "fans": {},
"fan_groups": {}, "fan_groups": {},
"fan_curves": { "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": { "Balanced": {
"points": [ "points": [
{"temp": 30, "speed": 10}, {
{"temp": 35, "speed": 12}, "temp": 30,
{"temp": 40, "speed": 15}, "speed": 10
{"temp": 45, "speed": 20}, },
{"temp": 50, "speed": 30}, {
{"temp": 55, "speed": 40}, "temp": 35,
{"temp": 60, "speed": 55}, "speed": 12
{"temp": 65, "speed": 70}, },
{"temp": 70, "speed": 85}, {
{"temp": 75, "speed": 95}, "temp": 40,
{"temp": 80, "speed": 100} "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", "sensor_source": "cpu",
"applies_to": "all" "applies_to": "all"
}, },
"Silent": { "Silent": {
"points": [ "points": [
{"temp": 30, "speed": 5}, {
{"temp": 40, "speed": 10}, "temp": 30,
{"temp": 50, "speed": 15}, "speed": 5
{"temp": 55, "speed": 25}, },
{"temp": 60, "speed": 35}, {
{"temp": 65, "speed": 50}, "temp": 40,
{"temp": 70, "speed": 70}, "speed": 10
{"temp": 75, "speed": 85}, },
{"temp": 80, "speed": 100} {
"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", "sensor_source": "cpu",
"applies_to": "all" "applies_to": "all"
}, },
"Performance": { "Performance": {
"points": [ "points": [
{"temp": 30, "speed": 20}, {
{"temp": 35, "speed": 25}, "temp": 30,
{"temp": 40, "speed": 35}, "speed": 20
{"temp": 45, "speed": 45}, },
{"temp": 50, "speed": 55}, {
{"temp": 55, "speed": 70}, "temp": 35,
{"temp": 60, "speed": 85}, "speed": 25
{"temp": 65, "speed": 95}, },
{"temp": 70, "speed": 100} {
"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", "sensor_source": "cpu",
"applies_to": "all" "applies_to": "all"
} }
}, },
"active_curve": "Balanced", "theme": "dark",
"theme": "dark" "active_curve": "Balanced"
} }

View File

@ -1,14 +1,27 @@
INFO: Started server process [20120] INFO: Started server process [25918]
INFO: Waiting for application startup. INFO: Waiting for application startup.
INFO: Application startup complete. INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) 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:64198 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.5.30:60395 - "GET /login HTTP/1.1" 200 OK INFO: 192.168.5.30:64198 - "GET /api/status HTTP/1.1" 401 Unauthorized
INFO: 127.0.0.1:40728 - "GET /api/curves 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). /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)) 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). /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: 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 2026-02-20 17:07:43,397 - 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 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

View File

@ -792,6 +792,7 @@ def get_html(theme="dark"):
if (currentStatus.config) {{ if (currentStatus.config) {{
currentConfig = currentStatus.config; currentConfig = currentStatus.config;
updateConfigFields(); updateConfigFields();
updateCurvesList();
}} }}
}} }}
@ -818,6 +819,47 @@ def get_html(theme="dark"):
document.getElementById('cfg-panic-no-data').checked = cfg.panic_on_no_data !== false; 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 // Tabs
function showTab(tab) {{ function showTab(tab) {{
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));