Implement fan groups, per-fan controls, theme toggle, footer links

This commit is contained in:
devmatrix 2026-02-20 20:19:18 +00:00
parent 1b93f71a19
commit 8d06f60945
2 changed files with 785 additions and 75 deletions

View File

@ -1,98 +1,549 @@
INFO: Started server process [70935]
INFO: Started server process [71515]
INFO: Waiting for application startup.
2026-02-20 20:04:15,011 - fan_controller - INFO - Loaded config from /home/devmatrix/projects/fan-controller-v2/data/config.json
2026-02-20 20:04:15,011 - __main__ - INFO - Auto-starting fan control (enabled in config)
2026-02-20 20:04:15,162 - fan_controller - INFO - Connected to IPMI at 192.168.5.191
2026-02-20 20:04:15,162 - fan_controller - INFO - HTTP sensor client initialized for http://192.168.5.200:8888
2026-02-20 20:04:15,163 - fan_controller - INFO - IPMI Controller service started
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: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
2026-02-20 20:04:15,314 - fan_controller - INFO - Manual fan control enabled
INFO: 192.168.5.30:62669 - "GET /api/status HTTP/1.1" 401 Unauthorized
INFO: 192.168.5.30:62669 - "GET /login HTTP/1.1" 200 OK
INFO: 192.168.5.30:62669 - "GET /icons/favicon.svg HTTP/1.1" 304 Not Modified
2026-02-20 20:04:20,942 - fan_controller - INFO - Fan 0xff speed set to 27%
2026-02-20 20:04:20,942 - fan_controller - INFO - All fans set to 27% (Temp 36.0°C)
2026-02-20 20:04:36,853 - fan_controller - INFO - Fan 0xff speed set to 31%
2026-02-20 20:04:36,853 - fan_controller - INFO - All fans set to 31% (Temp 38.0°C)
INFO: 127.0.0.1:34870 - "GET /api/status HTTP/1.1" 401 Unauthorized
2026-02-20 20:04:51,781 - fan_controller - INFO - Fan 0xff speed set to 27%
2026-02-20 20:04:51,781 - fan_controller - INFO - All fans set to 27% (Temp 36.0°C)
2026-02-20 20:05:07,261 - fan_controller - INFO - Fan 0xff speed set to 25%
2026-02-20 20:05:07,261 - fan_controller - INFO - All fans set to 25% (Temp 35.0°C)
INFO: 192.168.5.30:65404 - "GET /login HTTP/1.1" 200 OK
INFO: 192.168.5.30:65404 - "GET /favicon.ico HTTP/1.1" 200 OK
INFO: 192.168.5.30:61891 - "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:61891 - "GET /login HTTP/1.1" 200 OK
INFO: 192.168.5.30:61891 - "GET /icons/favicon.svg HTTP/1.1" 304 Not Modified
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:65404 - "POST /api/auth/login HTTP/1.1" 200 OK
INFO: 192.168.5.30:65404 - "GET / HTTP/1.1" 200 OK
INFO: 192.168.5.30:65404 - "GET /icons/sun.svg HTTP/1.1" 304 Not Modified
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:58005 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:64268 - "GET /icons/lock-closed.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:63723 - "GET /icons/auto-mode.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:65404 - "GET /icons/thermometer.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:64382 - "GET /icons/server-stack.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:50712 - "GET /icons/arrow-right-on-rectangle.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:58005 - "GET /icons/list-bullet.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:63723 - "GET /icons/chart-bar.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:64268 - "GET /icons/adjustments-horizontal.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:65404 - "GET /icons/document-text.svg HTTP/1.1" 304 Not Modified
INFO: 192.168.5.30:65404 - "GET /favicon.ico HTTP/1.1" 200 OK
2026-02-20 20:05:22,464 - fan_controller - INFO - Fan 0xff speed set to 27%
2026-02-20 20:05:22,464 - fan_controller - INFO - All fans set to 27% (Temp 36.0°C)
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:65404 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:65404 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:65404 - "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:65404 - "GET /api/status HTTP/1.1" 200 OK
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:65404 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:65404 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:65404 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:65404 - "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:65404 - "GET /api/status HTTP/1.1" 200 OK
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:65404 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:05:53,251 - fan_controller - INFO - Fan 0xff speed set to 29%
2026-02-20 20:05:53,251 - fan_controller - INFO - All fans set to 29% (Temp 37.0°C)
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:65404 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:65404 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:65404 - "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:65404 - "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:65404 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:06:08,235 - fan_controller - INFO - Fan 0xff speed set to 25%
2026-02-20 20:06:08,235 - fan_controller - INFO - All fans set to 25% (Temp 35.0°C)
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:65404 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:65404 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:65404 - "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:65404 - "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:65404 - "GET /api/status HTTP/1.1" 200 OK
2026-02-20 20:06:23,620 - fan_controller - INFO - Fan 0xff speed set to 29%
2026-02-20 20:06:23,620 - fan_controller - INFO - All fans set to 29% (Temp 37.0°C)
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:65404 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:65404 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:65404 - "GET /api/status HTTP/1.1" 200 OK
INFO: 192.168.5.30:65404 - "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: 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

@ -102,7 +102,8 @@ class FanGroupCreate(BaseModel):
class ManualSpeedRequest(BaseModel):
speed: int = Field(..., ge=0, le=100)
fan_id: str = "0xff"
fan_id: Optional[str] = "0xff"
group: Optional[str] = None
class IdentifyRequest(BaseModel):
fan_id: str
@ -554,7 +555,7 @@ def get_html(theme="dark"):
<button class="tab active" onclick="showTab('ipmi')"><span class="icon-svg"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="3" width="20" height="6" rx="2"/><rect x="2" y="11" width="20" height="6" rx="2"/><rect x="2" y="19" width="20" height="3" rx="1.5"/></svg></span>IPMI</button>
<button class="tab" onclick="showTab('http')"><span class="icon-svg"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg></span>HTTP</button>
<button class="tab" onclick="showTab('control')"><span class="icon-svg"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></span>Control</button>
<button class="tab" onclick="showTab('fans')"><img src="/icons/favicon.svg" class="icon-svg" alt=""> Fans</button>
<button class="tab" onclick="showTab('fans')"><img src="/icons/favicon.svg" class="icon-svg" alt=""> Fan Groups</button>
<button class="tab" onclick="showTab('curves')"><img src="/icons/chart-bar.svg" class="icon-svg" alt=""> Curves</button>
<button class="tab" onclick="showTab('logs')"><img src="/icons/document-text.svg" class="icon-svg" alt=""> Logs</button>
</div>
@ -646,15 +647,21 @@ def get_html(theme="dark"):
</div>
<div id="tab-fans" class="tab-content">
<h3>Fan Configuration</h3>
<h3>Fan Groups Management</h3>
<div id="fan-groups-list" style="margin-bottom:20px;">
<p style="color:var(--text-secondary);">Loading fan groups...</p>
</div>
<h4>Individual Fan Configuration</h4>
<div id="fan-config-list">
<p style="color:var(--text-secondary);">Connect to IPMI to see fans</p>
</div>
<h4 style="margin-top:20px;">Fan Groups</h4>
<div id="fan-groups-list">
<p style="color:var(--text-secondary);">No groups defined</p>
<h4 style="margin-top:20px;">Quick Actions</h4>
<div style="display:flex;gap:10px;flex-wrap:wrap;">
<button class="secondary" onclick="setAllFans(50)">Set All 50%</button>
<button class="secondary" onclick="setAllFans(75)">Set All 75%</button>
<button class="secondary" onclick="setAllFans(100)">Set All 100%</button>
</div>
<button class="secondary" onclick="showGroupModal()">+ Add Group</button>
</div>
<div id="tab-curves" class="tab-content">
@ -697,6 +704,44 @@ def get_html(theme="dark"):
</div>
</div>
<!-- Fan Group Modal -->
<div class="modal" id="group-modal">
<div class="modal-content">
<h3>Manage Fan Group</h3>
<div class="form-group">
<label>Group Name</label>
<input type="text" id="group-name" placeholder="e.g., CPU Fans, Rear Fans">
</div>
<div class="form-group">
<label>Select Fans for Group</label>
<div id="group-fan-selection" style="max-height:200px;overflow-y:auto;">
<p style="color:var(--text-secondary);">Connect to IPMI to see fans</p>
</div>
</div>
<div style="display:flex;gap:10px;margin-top:15px;">
<button class="secondary" style="flex:1;" onclick="hideModal('group-modal')">Cancel</button>
<button class="primary" style="flex:1;" onclick="saveGroup()">Save Group</button>
</div>
</div>
</div>
<!-- Per-Fan Speed Modal -->
<div class="modal" id="fan-speed-modal">
<div class="modal-content">
<h3>Set Fan Speed</h3>
<div id="fan-speed-target" style="margin-bottom:15px;font-weight:bold;"></div>
<div class="form-group">
<label>Speed: <span id="fan-speed-val">50%</span></label>
<input type="range" id="fan-speed-slider" min="0" max="100" value="50"
oninput="document.getElementById('fan-speed-val').textContent = this.value + '%'">
</div>
<div style="display:flex;gap:10px;margin-top:15px;">
<button class="secondary" style="flex:1;" onclick="hideModal('fan-speed-modal')">Cancel</button>
<button class="primary" style="flex:1;" onclick="applyFanSpeed()">Apply</button>
</div>
</div>
</div>
<script>
let currentStatus = {{}};
let currentConfig = {{}};
@ -706,7 +751,7 @@ def get_html(theme="dark"):
const current = localStorage.getItem('theme') || 'dark';
const next = current === 'dark' ? 'light' : 'dark';
localStorage.setItem('theme', next);
location.reload();
window.location.href = window.location.pathname + '?theme=' + next;
}}
// Load saved theme
@ -782,6 +827,57 @@ def get_html(theme="dark"):
// Fan grid
const fans = currentStatus.fans || [];
const fanConfigs = currentStatus.config?.fans || {{}};
const fanGroups = currentStatus.config?.fan_groups || {{}};
// Update Fan Groups tab
const fanGroupsList = document.getElementById('fan-groups-list');
if (Object.keys(fanGroups).length === 0) {{
fanGroupsList.innerHTML = '<p style="color:var(--text-secondary);">No groups defined. Groups let you control multiple fans together.</p>' +
'<button class="secondary" onclick="showGroupModal()">+ Create First Group</button>';
}} else {{
fanGroupsList.innerHTML = Object.entries(fanGroups).map(([name, group]) => {{
const fanCount = group.fan_ids?.length || 0;
return `<div class="fan-card" style="margin-bottom:10px;">
<div style="display:flex;justify-content:space-between;align-items:center;">
<div>
<div class="fan-name">${{name}}</div>
<div class="fan-info">${{fanCount}} fan${{fanCount !== 1 ? 's' : ''}}</div>
</div>
<div style="display:flex;gap:5px;">
<button class="secondary small" onclick="showFanSpeedModal('${{name}}', true)">Set Speed</button>
<button class="secondary small" onclick="showGroupModal('${{name}}')">Edit</button>
<button class="danger small" onclick="deleteGroup('${{name}}')">Delete</button>
</div>
</div>
</div>`;
}}).join('') + '<button class="secondary" onclick="showGroupModal()" style="margin-top:10px;">+ Add Group</button>';
}}
// Update Individual Fan Config
const fanConfigList = document.getElementById('fan-config-list');
if (fans.length === 0) {{
fanConfigList.innerHTML = '<p style="color:var(--text-secondary);">Connect to IPMI to see fans</p>';
}} else {{
fanConfigList.innerHTML = fans.map(f => {{
const cfg = fanConfigs[f.fan_id] || {{}};
const name = cfg.name || `Fan ${{f.fan_number}}`;
const group = Object.entries(fanGroups).find(([n, g]) => g.fan_ids?.includes(f.fan_id));
const groupName = group ? group[0] : 'No group';
return `<div class="fan-card" style="margin-bottom:10px;">
<div style="display:flex;justify-content:space-between;align-items:center;">
<div>
<div class="fan-name">${{name}}</div>
<div class="fan-info">${{f.speed_rpm || 0}} RPM ${{f.speed_percent || 0}}% Group: ${{groupName}}</div>
</div>
<div style="display:flex;gap:5px;">
<button class="secondary small" onclick="showFanSpeedModal('${{f.fan_id}}', false)">Set Speed</button>
<button class="secondary small" onclick="identifyFan('${{f.fan_id}}')">Identify</button>
</div>
</div>
</div>`;
}}).join('');
}}
const fanGrid = document.getElementById('fan-grid');
if (fans.length > 0) {{
fanGrid.innerHTML = fans.map(f => {{
@ -940,6 +1036,118 @@ def get_html(theme="dark"):
const res = await api('/api/fans/stop-identify', {{method: 'POST'}});
log('Identify mode stopped');
hideModal('identify-modal');
fetchStatus();
}}
// Fan Group Management
let currentFanSpeedTarget = null;
function showGroupModal(groupName = null) {{
const fans = currentStatus.fans || [];
const container = document.getElementById('group-fan-selection');
if (fans.length === 0) {{
container.innerHTML = '<p style="color:var(--text-secondary);">No fans available. Connect to IPMI first.</p>';
}} else {{
const fanConfigs = currentStatus.config?.fans || {{}};
container.innerHTML = fans.map(f => {{
const cfg = fanConfigs[f.fan_id] || {{}};
const name = cfg.name || `Fan ${{f.fan_number}}`;
return `<label style="display:block;margin:5px 0;cursor:pointer;">
<input type="checkbox" value="${{f.fan_id}}" class="group-fan-checkbox"> ${{name}} (${{f.fan_id}})
</label>`;
}}).join('');
}}
document.getElementById('group-name').value = groupName || '';
showModal('group-modal');
}}
async function saveGroup() {{
const name = document.getElementById('group-name').value.trim();
if (!name) {{
log('Group name required', 'error');
return;
}}
const fanIds = Array.from(document.querySelectorAll('.group-fan-checkbox:checked')).map(cb => cb.value);
if (fanIds.length === 0) {{
log('Select at least one fan', 'error');
return;
}}
const res = await api('/api/fans/groups', {{
method: 'POST',
body: JSON.stringify({{name, fan_ids: fanIds}})
}});
const data = await res.json();
if (data.success) {{
log(`Group "${{name}}" saved`, 'success');
hideModal('group-modal');
fetchStatus();
}} else {{
log('Failed: ' + data.error, 'error');
}}
}}
function showFanSpeedModal(target, isGroup = false) {{
currentFanSpeedTarget = {{target, isGroup}};
document.getElementById('fan-speed-target').textContent = `Setting speed for: ${{target}}`;
document.getElementById('fan-speed-slider').value = 50;
document.getElementById('fan-speed-val').textContent = '50%';
showModal('fan-speed-modal');
}}
async function applyFanSpeed() {{
const speed = parseInt(document.getElementById('fan-speed-slider').value);
const {{target, isGroup}} = currentFanSpeedTarget;
const res = await api('/api/control/manual', {{
method: 'POST',
body: JSON.stringify({{speed, fan_id: isGroup ? null : target, group: isGroup ? target : null}})
}});
const data = await res.json();
if (data.success) {{
log(`${{isGroup ? 'Group' : 'Fan'}} "${{target}}" set to ${{speed}}%`, 'success');
hideModal('fan-speed-modal');
fetchStatus();
}} else {{
log('Failed: ' + data.error, 'error');
}}
}}
async function setAllFans(speed) {{
const res = await api('/api/control/manual', {{
method: 'POST',
body: JSON.stringify({{speed, fan_id: '0xff'}})
}});
const data = await res.json();
if (data.success) {{
log(`All fans set to ${{speed}}%`, 'success');
fetchStatus();
}} else {{
log('Failed: ' + data.error, 'error');
}}
}}
async function deleteGroup(name) {{
if (!confirm(`Delete group "${{name}}"?`)) return;
const res = await api('/api/fans/groups', {{
method: 'DELETE',
body: JSON.stringify({{name}})
}});
const data = await res.json();
if (data.success) {{
log(`Group "${{name}}" deleted`, 'success');
fetchStatus();
}} else {{
log('Failed: ' + data.error, 'error');
}}
}}
// Save functions
@ -1037,6 +1245,15 @@ def get_html(theme="dark"):
setInterval(fetchStatus, 3000);
fetchStatus();
</script>
<footer style="text-align:center;padding:20px;color:var(--text-secondary);font-size:0.85rem;border-top:1px solid var(--border);margin-top:20px;">
<div style="margin-bottom:10px;">
<a href="https://github.com/ImpulsiveFPS/IPMI-Controller/issues" target="_blank" style="color:var(--accent-primary);text-decoration:none;margin:0 10px;">🐛 Report Bug</a>
<a href="https://github.com/ImpulsiveFPS/IPMI-Controller" target="_blank" style="color:var(--accent-primary);text-decoration:none;margin:0 10px;">📁 GitHub Repo</a>
<a href="https://ko-fi.com/impulsivefps" target="_blank" style="color:var(--accent-primary);text-decoration:none;margin:0 10px;"> Support on Ko-fi</a>
</div>
<div>IPMI Controller v3.0.0 - Built by ImpulsiveFPS</div>
</footer>
</body>
</html>'''
@ -1320,6 +1537,20 @@ async def api_control_manual(req: ManualSpeedRequest, username: str = Depends(ge
if not service._init_controller():
return {"success": False, "error": "Not connected"}
# Handle group speed setting
if req.group:
groups = service.config.get('fan_groups', {})
if req.group not in groups:
return {"success": False, "error": f"Group '{req.group}' not found"}
fan_ids = groups[req.group].get('fan_ids', [])
success = True
for fan_id in fan_ids:
if not service.set_manual_speed(req.speed, fan_id):
success = False
return {"success": success}
# Handle individual fan or all fans
if service.set_manual_speed(req.speed, req.fan_id):
return {"success": True}
return {"success": False, "error": "Failed"}
@ -1419,6 +1650,34 @@ async def api_stop_identify(username: str = Depends(get_current_user)):
service.stop_identify()
return {"success": True}
# Fan Groups API
@app.post("/api/fans/groups")
async def api_save_group(data: dict, username: str = Depends(get_current_user)):
service = get_service(str(CONFIG_FILE))
name = data.get('name')
fan_ids = data.get('fan_ids', [])
if not name:
return {"success": False, "error": "Group name required"}
if 'fan_groups' not in service.config:
service.config['fan_groups'] = {}
service.config['fan_groups'][name] = {'fan_ids': fan_ids}
service._save_config()
return {"success": True}
@app.delete("/api/fans/groups")
async def api_delete_group(data: dict, username: str = Depends(get_current_user)):
service = get_service(str(CONFIG_FILE))
name = data.get('name')
if 'fan_groups' in service.config and name in service.config['fan_groups']:
del service.config['fan_groups'][name]
service._save_config()
return {"success": True}
return {"success": False, "error": "Group not found"}
# Public API (no auth required - for external integrations)
@app.get("/api/public/status")
async def api_public_status():