Bug: Enhance schedule handling with improved request processing and validation
Sometimes page loads, sometimes doesn't trying to implement something to figure out why the page isn't loading. In python everything loads in certain order so if something hangs, it could prevent something else from running. (Like web page from loading :()
This commit is contained in:
parent
b8336f82c8
commit
9fda192f0b
@ -8,6 +8,7 @@ class TempWebServer:
|
||||
self.port = port
|
||||
self.socket = None
|
||||
self.sensors = {}
|
||||
self.last_page_render = 0 # Track last successful HTML generation
|
||||
|
||||
def start(self):
|
||||
"""Start the web server (non-blocking)."""
|
||||
@ -20,34 +21,47 @@ class TempWebServer:
|
||||
print("Web server started on port {}".format(self.port))
|
||||
except Exception as e:
|
||||
print("Failed to start web server: {}".format(e))
|
||||
|
||||
|
||||
def check_requests(self, sensors, ac_monitor=None, heater_monitor=None, schedule_monitor=None):
|
||||
"""Check for incoming requests (call in main loop)."""
|
||||
if not self.socket:
|
||||
return
|
||||
|
||||
try:
|
||||
conn, addr = self.socket.accept()
|
||||
conn.settimeout(3.0)
|
||||
request = conn.recv(1024).decode('utf-8')
|
||||
|
||||
# Check if this is a POST request (form submission)
|
||||
|
||||
if 'POST /update' in request:
|
||||
response = self._handle_update(request, sensors, ac_monitor, heater_monitor, schedule_monitor)
|
||||
|
||||
elif 'GET /schedule' in request:
|
||||
response = self._get_schedule_editor_page(sensors, ac_monitor, heater_monitor)
|
||||
conn.send('HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\n\r\n')
|
||||
conn.sendall(response.encode('utf-8'))
|
||||
conn.close()
|
||||
return
|
||||
|
||||
elif 'POST /schedule' in request:
|
||||
response = self._handle_schedule_update(request, sensors, ac_monitor, heater_monitor, schedule_monitor)
|
||||
# If handler returns a redirect response, send it raw and exit
|
||||
if isinstance(response, str) and response.startswith('HTTP/1.1 303'):
|
||||
conn.sendall(response.encode('utf-8'))
|
||||
conn.close()
|
||||
return
|
||||
elif 'GET /ping' in request:
|
||||
# Quick health check endpoint (no processing)
|
||||
conn.send('HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n')
|
||||
conn.sendall(b'OK')
|
||||
conn.close()
|
||||
return
|
||||
|
||||
else:
|
||||
# Regular GET request
|
||||
response = self._get_status_page(sensors, ac_monitor, heater_monitor)
|
||||
|
||||
# Make sure we have a valid response
|
||||
|
||||
if response is None:
|
||||
print("Error: response is None, generating default page")
|
||||
response = self._get_status_page(sensors, ac_monitor, heater_monitor)
|
||||
|
||||
conn.send('HTTP/1.1 200 OK\r\n')
|
||||
conn.send('Content-Type: text/html; charset=utf-8\r\n')
|
||||
conn.send('Connection: close\r\n\r\n')
|
||||
|
||||
conn.send('HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\n\r\n')
|
||||
conn.sendall(response.encode('utf-8'))
|
||||
conn.close()
|
||||
except OSError:
|
||||
@ -56,12 +70,24 @@ class TempWebServer:
|
||||
print("Web server error: {}".format(e))
|
||||
import sys
|
||||
sys.print_exception(e)
|
||||
|
||||
|
||||
def _save_config_to_file(self, config):
|
||||
"""Save configuration to config.json file."""
|
||||
"""Save configuration to config.json file (atomic write)."""
|
||||
try:
|
||||
with open('config.json', 'w') as f:
|
||||
import os
|
||||
# Write to temp file first
|
||||
with open('config.tmp', 'w') as f:
|
||||
json.dump(config, f)
|
||||
|
||||
# Remove old config if exists
|
||||
try:
|
||||
os.remove('config.json')
|
||||
except:
|
||||
pass
|
||||
|
||||
# Rename temp to config (atomic on most filesystems)
|
||||
os.rename('config.tmp', 'config.json')
|
||||
|
||||
print("Settings saved to config.json")
|
||||
return True
|
||||
except Exception as e:
|
||||
@ -86,6 +112,7 @@ class TempWebServer:
|
||||
|
||||
def _handle_schedule_update(self, request, sensors, ac_monitor, heater_monitor, schedule_monitor):
|
||||
"""Handle schedule form submission."""
|
||||
|
||||
try:
|
||||
body = request.split('\r\n\r\n')[1] if '\r\n\r\n' in request else ''
|
||||
params = {}
|
||||
@ -160,7 +187,6 @@ class TempWebServer:
|
||||
|
||||
# Redirect back to homepage
|
||||
return 'HTTP/1.1 303 See Other\r\nLocation: /\r\n\r\n'
|
||||
# ===== END: Handle mode actions =====
|
||||
|
||||
elif mode_action == 'save_schedules':
|
||||
# Just fall through to schedule parsing below
|
||||
@ -177,6 +203,20 @@ class TempWebServer:
|
||||
heater_key = 'schedule_{}_heater'.format(i)
|
||||
|
||||
if time_key in params and params[time_key]:
|
||||
# Validate time format (HH:MM)
|
||||
time_val = params[time_key]
|
||||
if ':' not in time_val or len(time_val.split(':')) != 2:
|
||||
print("Invalid time format: {}".format(time_val))
|
||||
return 'HTTP/1.1 303 See Other\r\nLocation: /schedule\r\n\r\n'
|
||||
|
||||
try:
|
||||
hours, mins = time_val.split(':')
|
||||
if not (0 <= int(hours) <= 23 and 0 <= int(mins) <= 59):
|
||||
raise ValueError
|
||||
except:
|
||||
print("Invalid time value: {}".format(time_val))
|
||||
return 'HTTP/1.1 303 See Other\r\nLocation: /schedule\r\n\r\n'
|
||||
|
||||
schedule = {
|
||||
'time': params[time_key],
|
||||
'name': params.get(name_key, 'Schedule {}'.format(i+1)),
|
||||
@ -223,13 +263,16 @@ class TempWebServer:
|
||||
except:
|
||||
pass
|
||||
# ===== END: Handle schedule configuration save =====
|
||||
|
||||
|
||||
# Redirect back to schedule page
|
||||
return 'HTTP/1.1 303 See Other\r\nLocation: /schedule\r\n\r\n'
|
||||
|
||||
except Exception as e:
|
||||
print("Error updating schedule: {}".format(e))
|
||||
import sys
|
||||
sys.print_exception(e)
|
||||
|
||||
return self._get_status_page(sensors, ac_monitor, heater_monitor, show_success=True)
|
||||
# Safety: avoid rendering an error page here; just redirect
|
||||
return 'HTTP/1.1 303 See Other\r\nLocation: /schedule\r\n\r\n'
|
||||
|
||||
def _handle_update(self, request, sensors, ac_monitor, heater_monitor, schedule_monitor):
|
||||
"""Handle form submission and update settings."""
|
||||
@ -342,12 +385,18 @@ class TempWebServer:
|
||||
"""Generate HTML status page."""
|
||||
print("DEBUG: Generating status page...")
|
||||
try:
|
||||
# Get current temperatures
|
||||
inside_temps = sensors['inside'].read_all_temps(unit='F')
|
||||
outside_temps = sensors['outside'].read_all_temps(unit='F')
|
||||
# Get current temperatures (use cached values to avoid blocking)
|
||||
inside_temp = getattr(sensors.get('inside'), 'last_temp', None)
|
||||
outside_temp = getattr(sensors.get('outside'), 'last_temp', None)
|
||||
|
||||
inside_temp = list(inside_temps.values())[0] if inside_temps else "N/A"
|
||||
outside_temp = list(outside_temps.values())[0] if outside_temps else "N/A"
|
||||
# Fallback to sensor read if no cached value (first load only)
|
||||
if inside_temp is None:
|
||||
inside_temps = sensors['inside'].read_all_temps(unit='F')
|
||||
inside_temp = list(inside_temps.values())[0] if inside_temps else "N/A"
|
||||
|
||||
if outside_temp is None:
|
||||
outside_temps = sensors['outside'].read_all_temps(unit='F')
|
||||
outside_temp = list(outside_temps.values())[0] if outside_temps else "N/A"
|
||||
|
||||
# Get AC/Heater status
|
||||
ac_status = "ON" if ac_monitor and ac_monitor.ac.get_state() else "OFF"
|
||||
@ -386,6 +435,9 @@ class TempWebServer:
|
||||
|
||||
# Build schedule cards
|
||||
schedule_cards = ""
|
||||
|
||||
# Build mode buttons for dashboard
|
||||
mode_buttons = self._build_mode_buttons(config)
|
||||
if config.get('schedules'):
|
||||
for schedule in config.get('schedules', []):
|
||||
# ===== START: Decode URL-encoded values =====
|
||||
@ -416,9 +468,6 @@ class TempWebServer:
|
||||
</div>
|
||||
"""
|
||||
|
||||
# Build schedule form
|
||||
schedule_form = self._build_schedule_form(config)
|
||||
|
||||
# Success message
|
||||
success_html = """
|
||||
<div class="success-message">
|
||||
@ -732,7 +781,13 @@ class TempWebServer:
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 20px;">
|
||||
{schedule_cards}
|
||||
</div>
|
||||
{schedule_form}
|
||||
{mode_buttons}
|
||||
|
||||
<div style="margin-top: 20px; text-align: center;">
|
||||
<a href="/schedule" class="btn" style="text-decoration: none; display: inline-block;">
|
||||
⚙️ Edit Schedules
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
@ -759,8 +814,9 @@ class TempWebServer:
|
||||
schedule_color=schedule_color,
|
||||
schedule_icon=schedule_icon,
|
||||
schedule_cards=schedule_cards,
|
||||
schedule_form=schedule_form
|
||||
mode_buttons=mode_buttons
|
||||
)
|
||||
self.last_page_render = time.time() # Track successful render
|
||||
return html
|
||||
|
||||
except Exception as e:
|
||||
@ -771,13 +827,11 @@ class TempWebServer:
|
||||
|
||||
def _get_error_page(self, error_title, error_message, sensors, ac_monitor, heater_monitor):
|
||||
"""Generate error page with message."""
|
||||
# Get current temps
|
||||
inside_temps = sensors['inside'].read_all_temps(unit='F')
|
||||
outside_temps = sensors['outside'].read_all_temps(unit='F')
|
||||
|
||||
inside_temp = list(inside_temps.values())[0] if inside_temps else "N/A"
|
||||
outside_temp = list(outside_temps.values())[0] if outside_temps else "N/A"
|
||||
# Get current temps (cached, fast - no blocking sensor reads)
|
||||
inside_temp = getattr(sensors.get('inside'), 'last_temp', None) or "N/A"
|
||||
outside_temp = getattr(sensors.get('outside'), 'last_temp', None) or "N/A"
|
||||
|
||||
# Format temperature values
|
||||
inside_temp_str = "{:.1f}".format(inside_temp) if isinstance(inside_temp, float) else str(inside_temp)
|
||||
outside_temp_str = "{:.1f}".format(outside_temp) if isinstance(outside_temp, float) else str(outside_temp)
|
||||
|
||||
@ -896,137 +950,197 @@ class TempWebServer:
|
||||
)
|
||||
|
||||
return html
|
||||
|
||||
def _build_schedule_form(self, config):
|
||||
"""Build the schedule editing form."""
|
||||
|
||||
def _get_schedule_editor_page(self, sensors, ac_monitor, heater_monitor):
|
||||
"""Generate schedule editor page (no auto-refresh, schedules only)."""
|
||||
# Get current temps (use cached to avoid blocking)
|
||||
inside_temp = getattr(sensors.get('inside'), 'last_temp', None) or "N/A"
|
||||
outside_temp = getattr(sensors.get('outside'), 'last_temp', None) or "N/A"
|
||||
|
||||
# Format temperature values
|
||||
inside_temp_str = "{:.1f}".format(inside_temp) if isinstance(inside_temp, float) else str(inside_temp)
|
||||
outside_temp_str = "{:.1f}".format(outside_temp) if isinstance(outside_temp, float) else str(outside_temp)
|
||||
|
||||
# Load config
|
||||
config = self._load_config()
|
||||
schedules = config.get('schedules', [])
|
||||
|
||||
# Pad with empty schedules up to 4
|
||||
while len(schedules) < 4:
|
||||
schedules.append({'time': '', 'name': '', 'ac_target': 77.0, 'heater_target': 80.0})
|
||||
|
||||
# ===== START: Determine current mode =====
|
||||
# Check if schedules exist
|
||||
has_schedules = len([s for s in schedules if s.get('time')]) > 0
|
||||
|
||||
# Determine mode based on config
|
||||
if not has_schedules:
|
||||
current_mode = "no_schedules" # No schedules configured yet
|
||||
elif config.get('schedule_enabled'):
|
||||
current_mode = "automatic" # Schedules are running
|
||||
elif config.get('permanent_hold', False):
|
||||
current_mode = "permanent_hold" # User disabled schedules permanently
|
||||
else:
|
||||
current_mode = "temporary_hold" # Manual override (HOLD mode)
|
||||
# ===== END: Determine current mode =====
|
||||
|
||||
# ===== START: Build mode control buttons =====
|
||||
if current_mode == "no_schedules":
|
||||
# No mode buttons if no schedules configured
|
||||
mode_buttons = """
|
||||
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; text-align: center; color: #7f8c8d; margin-bottom: 20px;">
|
||||
ℹ️ Configure schedules below, then choose a mode
|
||||
</div>
|
||||
"""
|
||||
elif current_mode == "automatic":
|
||||
# Automatic mode active
|
||||
mode_buttons = """
|
||||
<div style="background: linear-gradient(135deg, #2ecc71, #27ae60); color: white; padding: 15px; border-radius: 10px; margin-bottom: 20px; box-shadow: 0 4px 8px rgba(0,0,0,0.2);">
|
||||
<div style="font-weight: bold; font-size: 18px; margin-bottom: 10px;">
|
||||
✅ Automatic Mode Active
|
||||
</div>
|
||||
<div style="font-size: 14px; margin-bottom: 15px;">
|
||||
Temperatures automatically adjust based on schedule
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px; justify-content: center;">
|
||||
<button type="submit" name="mode_action" value="temporary_hold" style="padding: 8px 16px; background: #f39c12; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;">
|
||||
⏸️ Temporary Hold
|
||||
</button>
|
||||
<button type="submit" name="mode_action" value="permanent_hold" style="padding: 8px 16px; background: #e74c3c; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;">
|
||||
🛑 Permanent Hold
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
elif current_mode == "temporary_hold":
|
||||
# Temporary hold (manual override)
|
||||
mode_buttons = """
|
||||
<div style="background: linear-gradient(135deg, #f39c12, #e67e22); color: white; padding: 15px; border-radius: 10px; margin-bottom: 20px; box-shadow: 0 4px 8px rgba(0,0,0,0.2);">
|
||||
<div style="font-weight: bold; font-size: 18px; margin-bottom: 10px;">
|
||||
⏸️ Temporary Hold Active
|
||||
</div>
|
||||
<div style="font-size: 14px; margin-bottom: 15px;">
|
||||
Manual settings in use - Schedule paused
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px; justify-content: center;">
|
||||
<button type="submit" name="mode_action" value="resume" style="padding: 8px 16px; background: #2ecc71; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;">
|
||||
▶️ Resume Schedule
|
||||
</button>
|
||||
<button type="submit" name="mode_action" value="permanent_hold" style="padding: 8px 16px; background: #e74c3c; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;">
|
||||
🛑 Make Permanent
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
else: # permanent_hold
|
||||
# Permanent hold (schedules disabled by user)
|
||||
mode_buttons = """
|
||||
<div style="background: linear-gradient(135deg, #e74c3c, #c0392b); color: white; padding: 15px; border-radius: 10px; margin-bottom: 20px; box-shadow: 0 4px 8px rgba(0,0,0,0.2);">
|
||||
<div style="font-weight: bold; font-size: 18px; margin-bottom: 10px;">
|
||||
🛑 Permanent Hold Active
|
||||
</div>
|
||||
<div style="font-size: 14px; margin-bottom: 15px;">
|
||||
Schedules disabled - Manual control only
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px; justify-content: center;">
|
||||
<button type="submit" name="mode_action" value="resume" style="padding: 8px 16px; background: #2ecc71; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;">
|
||||
▶️ Enable Schedules
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
# ===== END: Build mode control buttons =====
|
||||
|
||||
form = """
|
||||
<form method="POST" action="/schedule" class="controls" style="margin-top: 20px;">
|
||||
<h3 style="color: #34495e; margin-bottom: 15px;">⚙️ Schedule Configuration</h3>
|
||||
|
||||
{mode_buttons}
|
||||
""".format(mode_buttons=mode_buttons)
|
||||
schedules.append({'time': '', 'name': '', 'ac_target': 75.0, 'heater_target': 72.0})
|
||||
|
||||
# Build schedule inputs
|
||||
schedule_inputs = ""
|
||||
for i, schedule in enumerate(schedules[:4]):
|
||||
form += """
|
||||
<div class="schedule-row">
|
||||
<div class="control-group" style="margin: 0;">
|
||||
<label class="control-label" style="font-size: 14px;">Time</label>
|
||||
<input type="time" name="schedule_{i}_time" value="{time}">
|
||||
schedule_inputs += """
|
||||
<div style="display: grid; grid-template-columns: 1fr 2fr 1fr 1fr; gap: 10px; margin-bottom: 10px; padding: 15px; background: #f8f9fa; border-radius: 8px;">
|
||||
<div>
|
||||
<label style="font-size: 14px; font-weight: bold; color: #34495e; display: block; margin-bottom: 5px;">Time</label>
|
||||
<input type="time" name="schedule_{i}_time" value="{time}" style="width: 100%; padding: 10px; border: 2px solid #ddd; border-radius: 5px;">
|
||||
</div>
|
||||
<div class="control-group" style="margin: 0;">
|
||||
<label class="control-label" style="font-size: 14px;">Name</label>
|
||||
<input type="text" name="schedule_{i}_name" value="{name}" placeholder="e.g. Morning">
|
||||
<div>
|
||||
<label style="font-size: 14px; font-weight: bold; color: #34495e; display: block; margin-bottom: 5px;">Name</label>
|
||||
<input type="text" name="schedule_{i}_name" value="{name}" placeholder="e.g. Morning" style="width: 100%; padding: 10px; border: 2px solid #ddd; border-radius: 5px;">
|
||||
</div>
|
||||
<div class="control-group" style="margin: 0;">
|
||||
<label class="control-label" style="font-size: 14px;">Heater (°F)</label>
|
||||
<input type="number" name="schedule_{i}_heater" value="{heater}" step="0.5" min="60" max="85">
|
||||
<div>
|
||||
<label style="font-size: 14px; font-weight: bold; color: #34495e; display: block; margin-bottom: 5px;">🔥 Heater (°F)</label>
|
||||
<input type="number" name="schedule_{i}_heater" value="{heater}" step="0.5" min="60" max="85" style="width: 100%; padding: 10px; border: 2px solid #ddd; border-radius: 5px;">
|
||||
</div>
|
||||
<div class="control-group" style="margin: 0;">
|
||||
<label class="control-label" style="font-size: 14px;">AC (°F)</label>
|
||||
<input type="number" name="schedule_{i}_ac" value="{ac}" step="0.5" min="60" max="85">
|
||||
<div>
|
||||
<label style="font-size: 14px; font-weight: bold; color: #34495e; display: block; margin-bottom: 5px;">❄️ AC (°F)</label>
|
||||
<input type="number" name="schedule_{i}_ac" value="{ac}" step="0.5" min="60" max="85" style="width: 100%; padding: 10px; border: 2px solid #ddd; border-radius: 5px;">
|
||||
</div>
|
||||
</div>
|
||||
""".format(
|
||||
i=i,
|
||||
time=schedule.get('time', ''),
|
||||
name=schedule.get('name', ''),
|
||||
ac=schedule.get('ac_target', 77.0),
|
||||
heater=schedule.get('heater_target', 80.0)
|
||||
heater=schedule.get('heater_target', 72.0),
|
||||
ac=schedule.get('ac_target', 75.0)
|
||||
)
|
||||
|
||||
form += """
|
||||
<div class="control-group" style="margin-top: 20px;">
|
||||
<button type="submit" name="mode_action" value="save_schedules" class="btn">💾 Save Schedule Changes</button>
|
||||
html = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Schedule Editor - Climate Control</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {{
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
}}
|
||||
.container {{
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
|
||||
}}
|
||||
h1 {{
|
||||
color: #2c3e50;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}}
|
||||
.header-info {{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 30px;
|
||||
margin-bottom: 30px;
|
||||
padding: 15px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
}}
|
||||
.btn {{
|
||||
padding: 12px 24px;
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}}
|
||||
.btn:hover {{ transform: translateY(-2px); }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>📅 Schedule Configuration</h1>
|
||||
|
||||
<div class="header-info">
|
||||
<div>🏠 Inside: <strong>{inside_temp}°F</strong></div>
|
||||
<div>🌡️ Outside: <strong>{outside_temp}°F</strong></div>
|
||||
</div>
|
||||
</form>
|
||||
"""
|
||||
|
||||
<form method="POST" action="/schedule">
|
||||
<h3 style="color: #34495e; margin-bottom: 15px;">⏰ Configure Schedule Times & Temperatures</h3>
|
||||
<p style="color: #7f8c8d; margin-bottom: 20px;">
|
||||
Set up to 4 time-based schedules. Leave time blank to disable a schedule.
|
||||
</p>
|
||||
|
||||
{schedule_inputs}
|
||||
|
||||
<div style="margin-top: 20px;">
|
||||
<button type="submit" name="mode_action" value="save_schedules" class="btn" style="width: 100%;">
|
||||
💾 Save Schedule Configuration
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div style="text-align: center; margin-top: 20px;">
|
||||
<a href="/" class="btn" style="background: linear-gradient(135deg, #95a5a6, #7f8c8d);">
|
||||
⬅️ Back to Dashboard
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; color: #7f8c8d; margin-top: 20px; padding-top: 20px; border-top: 2px solid #ecf0f1;">
|
||||
💡 This page does not auto-refresh<br>
|
||||
To change modes (Automatic/Hold), return to the dashboard
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
""".format(
|
||||
inside_temp=inside_temp_str,
|
||||
outside_temp=outside_temp_str,
|
||||
schedule_inputs=schedule_inputs
|
||||
)
|
||||
|
||||
return form
|
||||
return html
|
||||
|
||||
def _build_mode_buttons(self, config):
|
||||
"""Build mode control buttons for dashboard only."""
|
||||
schedules = config.get('schedules', [])
|
||||
has_schedules = len([s for s in schedules if s.get('time')]) > 0
|
||||
|
||||
if not has_schedules:
|
||||
return """
|
||||
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; text-align: center; color: #7f8c8d; margin: 20px 0;">
|
||||
ℹ️ No schedules configured - <a href="/schedule" style="color: #667eea; font-weight: bold;">Configure schedules</a>
|
||||
</div>
|
||||
"""
|
||||
|
||||
# Build mode buttons based on current state
|
||||
if config.get('schedule_enabled'):
|
||||
return """
|
||||
<form method="POST" action="/schedule" style="margin: 20px 0;">
|
||||
<div style="background: linear-gradient(135deg, #2ecc71, #27ae60); color: white; padding: 15px; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.2);">
|
||||
<div style="font-weight: bold; font-size: 18px; margin-bottom: 10px;">✅ Automatic Mode</div>
|
||||
<div style="font-size: 14px; margin-bottom: 15px;">Temperatures adjust based on schedule</div>
|
||||
<div style="display: flex; gap: 10px; justify-content: center;">
|
||||
<button type="submit" name="mode_action" value="temporary_hold" style="padding: 10px 20px; background: #f39c12; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;">⏸️ Pause</button>
|
||||
<button type="submit" name="mode_action" value="permanent_hold" style="padding: 10px 20px; background: #e74c3c; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;">🛑 Disable</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
"""
|
||||
elif config.get('permanent_hold', False):
|
||||
return """
|
||||
<form method="POST" action="/schedule" style="margin: 20px 0;">
|
||||
<div style="background: linear-gradient(135deg, #e74c3c, #c0392b); color: white; padding: 15px; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.2);">
|
||||
<div style="font-weight: bold; font-size: 18px; margin-bottom: 10px;">🛑 Permanent Hold</div>
|
||||
<div style="font-size: 14px; margin-bottom: 15px;">Manual control only - Schedules disabled</div>
|
||||
<button type="submit" name="mode_action" value="resume" style="padding: 10px 20px; background: #2ecc71; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;">▶️ Enable Schedules</button>
|
||||
</div>
|
||||
</form>
|
||||
"""
|
||||
else:
|
||||
return """
|
||||
<form method="POST" action="/schedule" style="margin: 20px 0;">
|
||||
<div style="background: linear-gradient(135deg, #f39c12, #e67e22); color: white; padding: 15px; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.2);">
|
||||
<div style="font-weight: bold; font-size: 18px; margin-bottom: 10px;">⏸️ Temporary Hold</div>
|
||||
<div style="font-size: 14px; margin-bottom: 15px;">Manual override active</div>
|
||||
<div style="display: flex; gap: 10px; justify-content: center;">
|
||||
<button type="submit" name="mode_action" value="resume" style="padding: 10px 20px; background: #2ecc71; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;">▶️ Resume</button>
|
||||
<button type="submit" name="mode_action" value="permanent_hold" style="padding: 10px 20px; background: #e74c3c; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;">🛑 Disable</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
"""
|
||||
Loading…
x
Reference in New Issue
Block a user