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,
|
"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"
|
||||||
}
|
}
|
||||||
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: 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
|
||||||
|
|
|
||||||
|
|
@ -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'));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue