Implement Add/Edit/Delete curve functionality with group assignment

This commit is contained in:
devmatrix 2026-02-20 20:25:16 +00:00
parent 8d06f60945
commit 61313774e2
4 changed files with 170 additions and 739 deletions

View File

@ -1,190 +0,0 @@
{
"ipmi_host": "192.168.5.191",
"ipmi_username": "root",
"ipmi_password": "calvin",
"ipmi_port": 623,
"http_sensor_enabled": true,
"http_sensor_url": "http://192.168.5.200:8888",
"http_sensor_timeout": 10,
"enabled": true,
"poll_interval": 10,
"fan_update_interval": 10,
"min_speed": 10,
"max_speed": 100,
"panic_temp": 85.0,
"panic_speed": 100,
"panic_on_no_data": true,
"no_data_timeout": 60,
"primary_sensor": "cpu",
"sensor_preference": "auto",
"fans": {},
"fan_groups": {},
"active_curve": "Performance",
"fan_curves": {
"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
}
],
"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
}
],
"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
}
],
"sensor_source": "cpu",
"applies_to": "all"
},
"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"
}
},
"theme": "dark"
}

View File

@ -1 +0,0 @@
{"users": {"admin": "ecd71870d1963316a97e3ac3408c9835ad8cf0f3c1bc703527c30265534f75ae"}}

View File

@ -1,549 +1,8 @@
INFO: Started server process [71515] INFO: Started server process [74745]
INFO: Waiting for application startup. INFO: Waiting for application startup.
2026-02-20 20:06:54,388 - fan_controller - INFO - Loaded config from /home/devmatrix/projects/fan-controller-v2/data/config.json
2026-02-20 20:06:54,388 - __main__ - INFO - Auto-starting fan control (enabled in config)
2026-02-20 20:06:54,546 - fan_controller - INFO - Connected to IPMI at 192.168.5.191
2026-02-20 20:06:54,546 - fan_controller - INFO - HTTP sensor client initialized for http://192.168.5.200:8888
2026-02-20 20:06:54,547 - fan_controller - INFO - IPMI Controller service started
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:61891 - "GET /api/status HTTP/1.1" 401 Unauthorized INFO: 192.168.5.30:54411 - "GET /api/status HTTP/1.1" 401 Unauthorized
2026-02-20 20:06:54,707 - fan_controller - INFO - Manual fan control enabled INFO: 192.168.5.30:54411 - "GET /login HTTP/1.1" 200 OK
INFO: 192.168.5.30:61891 - "GET /login HTTP/1.1" 200 OK INFO: 192.168.5.30:54411 - "GET /icons/favicon.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:61891 - "GET /icons/favicon.svg HTTP/1.1" 304 Not Modified INFO: 127.0.0.1:58102 - "GET / HTTP/1.1" 200 OK
2026-02-20 20:06:59,911 - fan_controller - INFO - Fan 0xff speed set to 27%
2026-02-20 20:06:59,911 - fan_controller - INFO - All fans set to 27% (Temp 36.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:153: 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: 192.168.5.30:52052 - "POST /api/auth/login HTTP/1.1" 200 OK
INFO: 192.168.5.30:52052 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.5.30:52052 - "GET /icons/auto-mode.svg HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:55325 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63188 - "GET /icons/thermometer.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:63188 - "GET /favicon.ico HTTP/1.1" 200 OK
INFO: 192.168.5.30:63188 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:63188 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:63188 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:07:15,453 - fan_controller - INFO - Fan 0xff speed set to 25%
2026-02-20 20:07:15,453 - fan_controller - INFO - All fans set to 25% (Temp 35.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:63188 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63188 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63188 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:63188 - "GET /api/status HTTP/1.1" 200 OK
INFO: 127.0.0.1:50858 - "GET /icons/auto-mode.svg HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:63188 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:07:30,915 - fan_controller - INFO - Fan 0xff speed set to 29%
2026-02-20 20:07:30,915 - fan_controller - INFO - All fans set to 29% (Temp 37.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:63188 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63188 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63188 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:63188 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:63188 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:07:45,964 - fan_controller - INFO - Fan 0xff speed set to 27%
2026-02-20 20:07:45,965 - fan_controller - INFO - All fans set to 27% (Temp 36.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:63188 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63188 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63188 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:63188 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:63188 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63188 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63188 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63188 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:63188 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:63188 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:63188 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:55296 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:55296 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:55296 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:55296 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:55296 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:55296 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:55296 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:55296 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:55296 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:55296 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:55296 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:55296 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:55296 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:55296 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:55296 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:09:03,867 - fan_controller - INFO - Fan 0xff speed set to 29%
2026-02-20 20:09:03,867 - fan_controller - INFO - All fans set to 29% (Temp 37.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:55296 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:55296 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:55296 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:55296 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:55296 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:09:19,798 - fan_controller - INFO - Fan 0xff speed set to 31%
2026-02-20 20:09:19,799 - fan_controller - INFO - All fans set to 31% (Temp 38.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:55296 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:55296 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:57942 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:57942 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:57942 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:09:35,346 - fan_controller - INFO - Fan 0xff speed set to 29%
2026-02-20 20:09:35,347 - fan_controller - INFO - All fans set to 29% (Temp 37.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:57942 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:57942 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:57942 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:57942 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:57942 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:57942 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:09:50,823 - fan_controller - INFO - Fan 0xff speed set to 27%
2026-02-20 20:09:50,823 - fan_controller - INFO - All fans set to 27% (Temp 36.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:57942 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:57942 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:57942 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:57942 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:57942 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:10:06,006 - fan_controller - INFO - Fan 0xff speed set to 31%
2026-02-20 20:10:06,006 - fan_controller - INFO - All fans set to 31% (Temp 38.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:57942 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:57942 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:57942 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:57942 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:57942 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:57942 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:57942 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:57942 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:57942 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:57942 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:57942 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:57942 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:57942 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:57942 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:57942 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:57942 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:10:54,206 - fan_controller - INFO - Fan 0xff speed set to 27%
2026-02-20 20:10:54,206 - fan_controller - INFO - All fans set to 27% (Temp 36.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:64073 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:13:15,599 - fan_controller - INFO - Fan 0xff speed set to 25%
2026-02-20 20:13:15,600 - fan_controller - INFO - All fans set to 25% (Temp 35.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:13:32,024 - fan_controller - INFO - Fan 0xff speed set to 27%
2026-02-20 20:13:32,025 - fan_controller - INFO - All fans set to 27% (Temp 36.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:13:48,155 - fan_controller - INFO - Fan 0xff speed set to 29%
2026-02-20 20:13:48,156 - fan_controller - INFO - All fans set to 29% (Temp 37.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:14:04,773 - fan_controller - INFO - Fan 0xff speed set to 25%
2026-02-20 20:14:04,774 - fan_controller - INFO - All fans set to 25% (Temp 35.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:14:20,306 - fan_controller - INFO - Fan 0xff speed set to 29%
2026-02-20 20:14:20,307 - fan_controller - INFO - All fans set to 29% (Temp 37.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:14:36,209 - fan_controller - INFO - Fan 0xff speed set to 27%
2026-02-20 20:14:36,209 - fan_controller - INFO - All fans set to 27% (Temp 36.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:14:51,615 - fan_controller - INFO - Fan 0xff speed set to 29%
2026-02-20 20:14:51,616 - fan_controller - INFO - All fans set to 29% (Temp 37.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:15:22,579 - fan_controller - INFO - Fan 0xff speed set to 27%
2026-02-20 20:15:22,579 - fan_controller - INFO - All fans set to 27% (Temp 36.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:15:37,470 - fan_controller - INFO - Fan 0xff speed set to 29%
2026-02-20 20:15:37,470 - fan_controller - INFO - All fans set to 29% (Temp 37.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.5.30:50577 - "GET /icons/favicon.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:50577 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:60159 - "GET /icons/lock-closed.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:57390 - "GET /icons/auto-mode.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:59075 - "GET /icons/sun.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:55085 - "GET /icons/server-stack.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:51598 - "GET /icons/arrow-right-on-rectangle.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:50577 - "GET /icons/thermometer.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:57390 - "GET /icons/adjustments-horizontal.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:60159 - "GET /icons/list-bullet.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:51598 - "GET /icons/chart-bar.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:59075 - "GET /icons/document-text.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:59075 - "GET /favicon.ico HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:15:52,508 - fan_controller - INFO - Fan 0xff speed set to 27%
2026-02-20 20:15:52,508 - fan_controller - INFO - All fans set to 27% (Temp 36.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:16:39,127 - fan_controller - INFO - Fan 0xff speed set to 25%
2026-02-20 20:16:39,127 - fan_controller - INFO - All fans set to 25% (Temp 35.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:16:55,091 - fan_controller - INFO - Fan 0xff speed set to 27%
2026-02-20 20:16:55,092 - fan_controller - INFO - All fans set to 27% (Temp 36.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:17:11,182 - fan_controller - INFO - Fan 0xff speed set to 25%
2026-02-20 20:17:11,182 - fan_controller - INFO - All fans set to 25% (Temp 35.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:17:26,052 - fan_controller - INFO - Fan 0xff speed set to 29%
2026-02-20 20:17:26,052 - fan_controller - INFO - All fans set to 29% (Temp 37.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:17:41,039 - fan_controller - INFO - Fan 0xff speed set to 27%
2026-02-20 20:17:41,040 - fan_controller - INFO - All fans set to 27% (Temp 36.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:18:13,111 - fan_controller - INFO - Fan 0xff speed set to 31%
2026-02-20 20:18:13,111 - fan_controller - INFO - All fans set to 31% (Temp 38.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:18:28,096 - fan_controller - INFO - Fan 0xff speed set to 27%
2026-02-20 20:18:28,097 - fan_controller - INFO - All fans set to 27% (Temp 36.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:18:44,743 - fan_controller - INFO - Fan 0xff speed set to 29%
2026-02-20 20:18:44,743 - fan_controller - INFO - All fans set to 29% (Temp 37.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:19:00,200 - fan_controller - INFO - Fan 0xff speed set to 27%
2026-02-20 20:19:00,201 - fan_controller - INFO - All fans set to 27% (Temp 36.0°C)
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
/home/devmatrix/projects/fan-controller-v2/web_server.py:160: 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:59075 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:59075 - "GET /api/status HTTP/1.1" 200 OK

View File

@ -742,6 +742,47 @@ def get_html(theme="dark"):
</div> </div>
</div> </div>
<!-- Add/Edit Curve Modal -->
<div class="modal" id="curve-modal">
<div class="modal-content" style="max-width:500px;">
<h3>Fan Curve Editor</h3>
<div class="form-group">
<label>Curve Name</label>
<input type="text" id="curve-name" placeholder="e.g., Silent, Performance, Balanced">
</div>
<div class="form-group">
<label>Assign to Group (optional)</label>
<select id="curve-group">
<option value="">All Fans</option>
</select>
</div>
<h4 style="margin-top:15px;">Curve Points (Temp Speed)</h4>
<div id="curve-points">
<div class="curve-point" style="display:grid;grid-template-columns:1fr 1fr auto;gap:10px;margin-bottom:8px;">
<input type="number" class="curve-temp" placeholder="Temp °C" value="30">
<input type="number" class="curve-speed" placeholder="Speed %" value="20">
<button class="danger small" onclick="this.parentElement.remove()">×</button>
</div>
<div class="curve-point" style="display:grid;grid-template-columns:1fr 1fr auto;gap:10px;margin-bottom:8px;">
<input type="number" class="curve-temp" placeholder="Temp °C" value="50">
<input type="number" class="curve-speed" placeholder="Speed %" value="50">
<button class="danger small" onclick="this.parentElement.remove()">×</button>
</div>
<div class="curve-point" style="display:grid;grid-template-columns:1fr 1fr auto;gap:10px;margin-bottom:8px;">
<input type="number" class="curve-temp" placeholder="Temp °C" value="70">
<input type="number" class="curve-speed" placeholder="Speed %" value="100">
<button class="danger small" onclick="this.parentElement.remove()">×</button>
</div>
</div>
<button class="secondary small" onclick="addCurvePoint()" style="margin-bottom:15px;">+ Add Point</button>
<div style="display:flex;gap:10px;">
<button class="secondary" style="flex:1;" onclick="hideModal('curve-modal')">Cancel</button>
<button class="primary" style="flex:1;" onclick="saveCurve()">Save Curve</button>
</div>
</div>
</div>
<script> <script>
let currentStatus = {{}}; let currentStatus = {{}};
let currentConfig = {{}}; let currentConfig = {{}};
@ -937,15 +978,18 @@ def get_html(theme="dark"):
const isActive = name === activeCurve; const isActive = name === activeCurve;
const points = curve.points || []; const points = curve.points || [];
const pointsStr = points.map(p => `${{p.temp}}°C${{p.speed}}%`).join(', '); const pointsStr = points.map(p => `${{p.temp}}°C${{p.speed}}%`).join(', ');
const group = curve.group ? `(Group: ${{curve.group}})` : '';
return `<div class="fan-card" style="margin-bottom:10px;${{isActive ? 'border:2px solid var(--accent-success);' : ''}}"> 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 style="display:flex;justify-content:space-between;align-items:center;">
<div> <div>
<div class="fan-name">${{name}} ${{isActive ? '<span style="color:var(--accent-success);">(Active)</span>' : ''}}</div> <div class="fan-name">${{name}} ${{isActive ? '<span style="color:var(--accent-success);">(Active)</span>' : ''}} ${{group}}</div>
<div class="fan-info" style="font-size:0.8rem;margin-top:5px;">${{pointsStr}}</div> <div class="fan-info" style="font-size:0.8rem;margin-top:5px;">${{pointsStr}}</div>
</div> </div>
<div style="display:flex;gap:5px;"> <div style="display:flex;gap:5px;">
${{!isActive ? `<button class="secondary small" onclick="setActiveCurve('${{name}}')">Activate</button>` : ''}} ${{!isActive ? `<button class="secondary small" onclick="setActiveCurve('${{name}}')">Activate</button>` : ''}}
<button class="secondary small" onclick="showCurveModal('${{name}}')">Edit</button>
<button class="danger small" onclick="deleteCurve('${{name}}')">Delete</button>
</div> </div>
</div> </div>
</div>`; </div>`;
@ -1150,6 +1194,124 @@ def get_html(theme="dark"):
}} }}
}} }}
// Curve Management
function showCurveModal(curveName = null) {{
// Populate group dropdown
const groups = currentStatus.config?.fan_groups || {{}};
const select = document.getElementById('curve-group');
select.innerHTML = '<option value="">All Fans</option>' +
Object.keys(groups).map(name => `<option value="${{name}}">${{name}}</option>`).join('');
if (curveName && currentStatus.config?.fan_curves?.[curveName]) {{
const curve = currentStatus.config.fan_curves[curveName];
document.getElementById('curve-name').value = curveName;
document.getElementById('curve-group').value = curve.group || '';
// Populate points
const container = document.getElementById('curve-points');
const points = curve.points || [];
container.innerHTML = points.map(p => `
<div class="curve-point" style="display:grid;grid-template-columns:1fr 1fr auto;gap:10px;margin-bottom:8px;">
<input type="number" class="curve-temp" placeholder="Temp °C" value="${{p.temp}}">
<input type="number" class="curve-speed" placeholder="Speed %" value="${{p.speed}}">
<button class="danger small" onclick="this.parentElement.remove()">×</button>
</div>
`).join('');
}} else {{
// Default new curve
document.getElementById('curve-name').value = '';
document.getElementById('curve-group').value = '';
document.getElementById('curve-points').innerHTML = `
<div class="curve-point" style="display:grid;grid-template-columns:1fr 1fr auto;gap:10px;margin-bottom:8px;">
<input type="number" class="curve-temp" placeholder="Temp °C" value="30">
<input type="number" class="curve-speed" placeholder="Speed %" value="20">
<button class="danger small" onclick="this.parentElement.remove()">×</button>
</div>
<div class="curve-point" style="display:grid;grid-template-columns:1fr 1fr auto;gap:10px;margin-bottom:8px;">
<input type="number" class="curve-temp" placeholder="Temp °C" value="50">
<input type="number" class="curve-speed" placeholder="Speed %" value="50">
<button class="danger small" onclick="this.parentElement.remove()">×</button>
</div>
<div class="curve-point" style="display:grid;grid-template-columns:1fr 1fr auto;gap:10px;margin-bottom:8px;">
<input type="number" class="curve-temp" placeholder="Temp °C" value="70">
<input type="number" class="curve-speed" placeholder="Speed %" value="100">
<button class="danger small" onclick="this.parentElement.remove()">×</button>
</div>
`;
}}
showModal('curve-modal');
}}
function addCurvePoint() {{
const container = document.getElementById('curve-points');
const div = document.createElement('div');
div.className = 'curve-point';
div.style.cssText = 'display:grid;grid-template-columns:1fr 1fr auto;gap:10px;margin-bottom:8px;';
div.innerHTML = `
<input type="number" class="curve-temp" placeholder="Temp °C" value="">
<input type="number" class="curve-speed" placeholder="Speed %" value="">
<button class="danger small" onclick="this.parentElement.remove()">×</button>
`;
container.appendChild(div);
}}
async function saveCurve() {{
const name = document.getElementById('curve-name').value.trim();
if (!name) {{
log('Curve name required', 'error');
return;
}}
const group = document.getElementById('curve-group').value;
// Collect points
const points = [];
document.querySelectorAll('.curve-point').forEach(el => {{
const temp = parseFloat(el.querySelector('.curve-temp').value);
const speed = parseFloat(el.querySelector('.curve-speed').value);
if (!isNaN(temp) && !isNaN(speed)) {{
points.push({{temp, speed}});
}}
}});
if (points.length < 2) {{
log('At least 2 points required', 'error');
return;
}}
// Sort by temperature
points.sort((a, b) => a.temp - b.temp);
const res = await api('/api/curves', {{
method: 'POST',
body: JSON.stringify({{name, points, group}})
}});
const data = await res.json();
if (data.success) {{
log(`Curve "${{name}}" saved`, 'success');
hideModal('curve-modal');
fetchStatus();
}} else {{
log('Failed: ' + data.error, 'error');
}}
}}
async function deleteCurve(name) {{
if (!confirm(`Delete curve "${{name}}"?`)) return;
const res = await api(`/api/curves/${{name}}`, {{method: 'DELETE'}});
const data = await res.json();
if (data.success) {{
log(`Curve "${{name}}" deleted`, 'success');
fetchStatus();
}} else {{
log('Failed: ' + data.error, 'error');
}}
}}
// Save functions // Save functions
async function saveIPMI() {{ async function saveIPMI() {{
const data = {{ const data = {{
@ -1609,7 +1771,8 @@ async def api_create_curve(data: dict, username: str = Depends(get_current_user)
curves[name] = { curves[name] = {
"points": data.get('points', []), "points": data.get('points', []),
"sensor_source": data.get('sensor_source', 'cpu'), "sensor_source": data.get('sensor_source', 'cpu'),
"applies_to": data.get('applies_to', 'all') "applies_to": data.get('applies_to', 'all'),
"group": data.get('group') or None
} }
service.update_config(fan_curves=curves) service.update_config(fan_curves=curves)
return {"success": True} return {"success": True}