Skip to content
38 changes: 19 additions & 19 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -603,11 +603,11 @@ def change_password():
(generate_password_hash(new), current_user.id))
conn.commit()
log_event(current_user.username, "Changed password")
flash("Password updated successfully.")
flash("Password updated successfully.", "success")
return redirect(url_for('guide'))
else:
log_event(current_user.username, "Failed password change attempt (invalid old password)")
flash("Old password incorrect.")
flash("Old password incorrect.", "error")
return render_template("change_password.html", current_tuner=get_current_tuner())

@app.route('/add_user', methods=['GET','POST'])
Expand All @@ -627,11 +627,11 @@ def add_user_route():
(new_username, generate_password_hash(new_password)))
conn.commit()
log_event(current_user.username, f"Added user {new_username}")
flash(f"User {new_username} added successfully.")
flash(f"User {new_username} added successfully.", "success")
return redirect(url_for('guide'))
except sqlite3.IntegrityError:
log_event(current_user.username, f"Failed to add user {new_username} (duplicate)")
flash("Username already exists.")
flash("Username already exists.", "warning")
return render_template("add_user.html", current_tuner=get_current_tuner())

@app.route('/delete_user', methods=['GET','POST'])
Expand All @@ -645,15 +645,15 @@ def delete_user():
del_username = request.form['username']
if del_username == 'admin':
log_event(current_user.username, "Attempted to delete admin user (blocked)")
flash("You cannot delete the admin account.")
flash("You cannot delete the admin account.", "warning")
return redirect(url_for('delete_user'))

with sqlite3.connect(DATABASE, timeout=10) as conn:
c = conn.cursor()
c.execute('DELETE FROM users WHERE username=?', (del_username,))
conn.commit()
log_event(current_user.username, f"Deleted user {del_username}")
flash(f"User {del_username} deleted (if they existed).")
flash(f"User {del_username} deleted (if they existed).", "success")
return redirect(url_for('guide'))

with sqlite3.connect(DATABASE, timeout=10) as conn:
Expand All @@ -675,7 +675,7 @@ def manage_users():
if current_user.username != 'admin' or is_tv:
# Log unauthorized or TV-based attempt
log_event(current_user.username, f"Unauthorized attempt to access /manage_users from UA: {ua}")
flash("Unauthorized access.")
flash("Unauthorized access.", "warning")
return redirect(url_for('guide'))

# ---- Normal admin logic below ----
Expand All @@ -691,7 +691,7 @@ def manage_users():

if action == 'add':
if not username or not password:
flash("Please provide both username and password.")
flash("Please provide both username and password.", "warning")
else:
try:
with sqlite3.connect(DATABASE, timeout=10) as conn:
Expand All @@ -700,25 +700,25 @@ def manage_users():
(username, generate_password_hash(password)))
conn.commit()
log_event(current_user.username, f"Added user {username}")
flash(f"✅ User '{username}' added successfully.")
flash(f"✅ User '{username}' added successfully.", "success")
except sqlite3.IntegrityError:
flash("⚠️ Username already exists.")
flash("⚠️ Username already exists.", "warning")

elif action == 'delete':
if username == 'admin':
flash("❌ Cannot delete the admin account.")
flash("❌ Cannot delete the admin account.", "error")
else:
with sqlite3.connect(DATABASE, timeout=10) as conn:
c = conn.cursor()
c.execute('DELETE FROM users WHERE username=?', (username,))
conn.commit()
log_event(current_user.username, f"Deleted user {username}")
flash(f"🗑 Deleted user '{username}'.")
flash(f"🗑 Deleted user '{username}'.", "success")

elif action == 'signout':
revoke_user_sessions(username)
log_event(current_user.username, f"Revoked sessions for {username}")
flash(f"🚪 Signed out all active logins for '{username}'.")
flash(f"🚪 Signed out all active logins for '{username}'.", "success")

return redirect(url_for('manage_users'))

Expand Down Expand Up @@ -811,7 +811,7 @@ def change_tuner():
new_tuner = request.form["tuner"]
set_current_tuner(new_tuner)
log_event(current_user.username, f"Switched active tuner to {new_tuner}")
flash(f"Active tuner switched to {new_tuner}")
flash(f"Active tuner switched to {new_tuner}", "success")

# ✅ Refresh cached guide data immediately
global cached_channels, cached_epg
Expand All @@ -832,7 +832,7 @@ def change_tuner():
try:
update_tuner_urls(tuner, xml_url, m3u_url)
log_event(current_user.username, f"Updated URLs for tuner {tuner}")
flash(f"Updated URLs for tuner {tuner}")
flash(f"Updated URLs for tuner {tuner}", "success")

# ✅ Validate inputs (DNS/reachability check)
if xml_url:
Expand All @@ -851,7 +851,7 @@ def change_tuner():
else:
delete_tuner(tuner)
log_event(current_user.username, f"Deleted tuner {tuner}")
flash(f"Tuner {tuner} deleted.")
flash(f"Tuner {tuner} deleted.", "success")

elif action == "rename_tuner":
old_name = request.form["tuner"] # matches HTML <select name="tuner">
Expand All @@ -861,7 +861,7 @@ def change_tuner():
else:
rename_tuner(old_name, new_name)
log_event(current_user.username, f"Renamed tuner {old_name} → {new_name}")
flash(f"Tuner {old_name} renamed to {new_name}")
flash(f"Tuner {old_name} renamed to {new_name}", "success")

elif action == "add_tuner":
name = request.form["tuner_name"].strip()
Expand Down Expand Up @@ -1574,12 +1574,12 @@ def view_logs():
@login_required
def clear_logs():
if current_user.username != 'admin':
flash("Unauthorized access.")
flash("Unauthorized access.", "warning")
return redirect(url_for('view_logs'))

open(LOG_PATH, "w").close() # clear the file
log_event("admin", "Cleared log file")
flash("🧹 Logs cleared successfully.")
flash("🧹 Logs cleared successfully.", "success")
return redirect(url_for('view_logs'))


Expand Down
189 changes: 189 additions & 0 deletions static/css/change_tuner.css
Original file line number Diff line number Diff line change
Expand Up @@ -400,3 +400,192 @@ body.light .flash-message.flash-info {
background: rgba(59, 130, 246, 0.1);
color: rgb(37, 99, 235);
}

/* Dark theme adjustments - High contrast on dark background */
body.dark .flash-message.flash-success,
body.dark .flash-message.flash-default {
background: rgba(34, 197, 94, 0.2);
border: 1px solid rgba(34, 197, 94, 0.4);
color: rgb(74, 222, 128);
}

body.dark .flash-message.flash-warning {
background: rgba(251, 191, 36, 0.2);
border: 1px solid rgba(251, 191, 36, 0.4);
color: rgb(253, 224, 71);
}

body.dark .flash-message.flash-error,
body.dark .flash-message.flash-danger {
background: rgba(239, 68, 68, 0.2);
border: 1px solid rgba(239, 68, 68, 0.4);
color: rgb(252, 165, 165);
}

body.dark .flash-message.flash-info {
background: rgba(59, 130, 246, 0.2);
border: 1px solid rgba(59, 130, 246, 0.4);
color: rgb(147, 197, 253);
}

/* RetroIPTV theme adjustments - Match warm retro palette */
body.retroiptv .flash-message.flash-success,
body.retroiptv .flash-message.flash-default {
background: rgba(34, 197, 94, 0.12);
border: 2px solid rgba(34, 197, 94, 0.5);
color: #48494a;
}

body.retroiptv .flash-message.flash-warning {
background: rgba(251, 191, 36, 0.12);
border: 2px solid rgba(251, 191, 36, 0.5);
color: #48494a;
}

body.retroiptv .flash-message.flash-error,
body.retroiptv .flash-message.flash-danger {
background: rgba(239, 68, 68, 0.12);
border: 2px solid rgba(239, 68, 68, 0.5);
color: #48494a;
}

body.retroiptv .flash-message.flash-info {
background: rgba(63, 183, 176, 0.12);
border: 2px solid rgba(63, 183, 176, 0.5);
color: #48494a;
}

/* Retro Magazine theme adjustments - Black text on white background theme */
body.retro-magazine .flash-message.flash-success,
body.retro-magazine .flash-message.flash-default {
background: rgba(34, 197, 94, 0.15);
border: 2px solid rgba(34, 197, 94, 0.6);
color: #48494a;
}

body.retro-magazine .flash-message.flash-warning {
background: rgba(251, 191, 36, 0.15);
border: 2px solid rgba(251, 191, 36, 0.6);
color: #48494a;
}

body.retro-magazine .flash-message.flash-error,
body.retro-magazine .flash-message.flash-danger {
background: rgba(239, 68, 68, 0.15);
border: 2px solid rgba(239, 68, 68, 0.6);
color: #48494a;
}

body.retro-magazine .flash-message.flash-info {
background: rgba(59, 130, 246, 0.15);
border: 2px solid rgba(59, 130, 246, 0.6);
color: #48494a;
}

/* DirecTV theme adjustments - Match blue corporate palette */
body.directv .flash-message.flash-success,
body.directv .flash-message.flash-default {
background: rgba(34, 197, 94, 0.2);
border: 2px solid rgba(34, 197, 94, 0.5);
color: rgb(74, 222, 128);
}

body.directv .flash-message.flash-warning {
background: rgba(255, 204, 0, 0.2);
border: 2px solid rgba(255, 204, 0, 0.5);
color: rgb(255, 215, 0);
}

body.directv .flash-message.flash-error,
body.directv .flash-message.flash-danger {
background: rgba(239, 68, 68, 0.2);
border: 2px solid rgba(239, 68, 68, 0.5);
color: rgb(252, 165, 165);
}

body.directv .flash-message.flash-info {
background: rgba(176, 220, 255, 0.2);
border: 2px solid rgba(176, 220, 255, 0.5);
color: rgb(216, 235, 255);
}

/* Comcast theme adjustments - Match blue/navy palette */
body.comcast .flash-message.flash-success,
body.comcast .flash-message.flash-default {
background: rgba(34, 197, 94, 0.2);
border: 2px solid rgba(34, 197, 94, 0.5);
color: rgb(74, 222, 128);
}

body.comcast .flash-message.flash-warning {
background: rgba(255, 204, 0, 0.2);
border: 2px solid rgba(255, 204, 0, 0.5);
color: rgb(255, 224, 102);
}

body.comcast .flash-message.flash-error,
body.comcast .flash-message.flash-danger {
background: rgba(239, 68, 68, 0.2);
border: 2px solid rgba(239, 68, 68, 0.5);
color: rgb(252, 165, 165);
}

body.comcast .flash-message.flash-info {
background: rgba(188, 216, 255, 0.2);
border: 2px solid rgba(188, 216, 255, 0.5);
color: rgb(188, 216, 255);
}

/* Retro AOL theme adjustments - Match teal/cyan and yellow palette */
body.retro-aol .flash-message.flash-success,
body.retro-aol .flash-message.flash-default {
background: rgba(51, 204, 204, 0.2);
border: 2px solid rgba(51, 204, 204, 0.5);
color: rgb(102, 255, 255);
}

body.retro-aol .flash-message.flash-warning {
background: rgba(255, 204, 1, 0.2);
border: 2px solid rgba(255, 204, 1, 0.5);
color: rgb(255, 221, 85);
}

body.retro-aol .flash-message.flash-error,
body.retro-aol .flash-message.flash-danger {
background: rgba(239, 68, 68, 0.2);
border: 2px solid rgba(239, 68, 68, 0.5);
color: rgb(252, 165, 165);
}

body.retro-aol .flash-message.flash-info {
background: rgba(102, 204, 255, 0.2);
border: 2px solid rgba(102, 204, 255, 0.5);
color: rgb(153, 238, 255);
}

/* TV Guide 1990 theme adjustments - Black/white newspaper style */
body.tvguide1990 .flash-message.flash-success,
body.tvguide1990 .flash-message.flash-default {
background: rgba(34, 197, 94, 0.15);
border: 2px solid rgba(34, 197, 94, 0.6);
color: rgb(21, 128, 61);
}

body.tvguide1990 .flash-message.flash-warning {
background: rgba(251, 191, 36, 0.15);
border: 2px solid rgba(251, 191, 36, 0.6);
color: rgb(146, 64, 14);
}

body.tvguide1990 .flash-message.flash-error,
body.tvguide1990 .flash-message.flash-danger {
background: rgba(239, 68, 68, 0.15);
border: 2px solid rgba(239, 68, 68, 0.6);
color: rgb(153, 27, 27);
}

body.tvguide1990 .flash-message.flash-info {
background: rgba(59, 130, 246, 0.15);
border: 2px solid rgba(59, 130, 246, 0.6);
color: rgb(29, 78, 216);
}