Standortauswahl und Multi Logo und Action Button
This commit is contained in:
580
app.py
580
app.py
@@ -26,7 +26,7 @@ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
MEDIA_DIR = os.path.join(BASE_DIR, "media")
|
||||
CONFIG_FILE = os.path.join(BASE_DIR, "config.json")
|
||||
|
||||
APP_VERSION = "4.1.1"
|
||||
APP_VERSION = "5.1.0"
|
||||
UPLOAD_EXTENSIONS = {".jpg", ".jpeg", ".png", ".mp4"}
|
||||
|
||||
app = Flask(__name__)
|
||||
@@ -40,7 +40,7 @@ login_manager.login_view = "login"
|
||||
# -------------------------------------------------
|
||||
def load_config():
|
||||
if not os.path.exists(CONFIG_FILE):
|
||||
return {"admin": {}, "screens": {}}
|
||||
return {"admin": {}, "sites": {}}
|
||||
with open(CONFIG_FILE) as f:
|
||||
return json.load(f)
|
||||
|
||||
@@ -48,38 +48,117 @@ def save_config(cfg):
|
||||
with open(CONFIG_FILE, "w") as f:
|
||||
json.dump(cfg, f, indent=2)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
print("✅ config.json gespeichert")
|
||||
|
||||
|
||||
def is_url(value):
|
||||
"""Check if value is a URL (string or dict with 'url' key)"""
|
||||
if isinstance(value, str):
|
||||
return value.lower().startswith(("http://", "https://"))
|
||||
if isinstance(value, dict) and "url" in value:
|
||||
url = value.get("url", "")
|
||||
return isinstance(url, str) and url.lower().startswith(("http://", "https://"))
|
||||
return False
|
||||
def get_site_list(cfg):
|
||||
return sorted(cfg.get("sites", {}).keys())
|
||||
|
||||
def normalize_url(value):
|
||||
"""Convert URL string to dict with zoom factor, or return existing dict"""
|
||||
if isinstance(value, dict) and "url" in value:
|
||||
return value
|
||||
if isinstance(value, str) and value.lower().startswith(("http://", "https://")):
|
||||
return {"url": value, "zoom": 1.0}
|
||||
|
||||
def get_screen_config(cfg, site, screen):
|
||||
return (
|
||||
cfg
|
||||
.setdefault("sites", {})
|
||||
.setdefault(site, {})
|
||||
.setdefault("screens", {})
|
||||
.setdefault(screen, {})
|
||||
)
|
||||
|
||||
|
||||
def is_url(item):
|
||||
if isinstance(item, str):
|
||||
return item.startswith("http://") or item.startswith("https://")
|
||||
return isinstance(item, dict) and "url" in item
|
||||
|
||||
|
||||
def normalize_url(item):
|
||||
if isinstance(item, str):
|
||||
return {"url": item, "zoom": 1.0}
|
||||
if isinstance(item, dict):
|
||||
return {"url": item["url"], "zoom": item.get("zoom", 1.0)}
|
||||
return None
|
||||
|
||||
|
||||
def playlist_item_enabled(item):
|
||||
return not (isinstance(item, dict) and item.get("enabled") is False)
|
||||
|
||||
|
||||
def playlist_item_name(item):
|
||||
if isinstance(item, str):
|
||||
return item
|
||||
if isinstance(item, dict):
|
||||
return item.get("url") or item.get("name")
|
||||
return item
|
||||
return item.get("url") or item.get("name", "")
|
||||
return ""
|
||||
|
||||
|
||||
def playlist_item_enabled(item):
|
||||
if isinstance(item, (str,)):
|
||||
return True
|
||||
if isinstance(item, dict):
|
||||
return item.get("enabled", True)
|
||||
return True
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
# Priority-Helper
|
||||
# -------------------------------------------------
|
||||
def load_priority_files():
|
||||
cfg = load_config()
|
||||
prio_dir = os.path.join(MEDIA_DIR, "priority")
|
||||
os.makedirs(prio_dir, exist_ok=True)
|
||||
|
||||
priority_files = []
|
||||
prio_playlist = cfg.get("priority", {}).get("playlist", [])
|
||||
|
||||
for item in prio_playlist:
|
||||
item_name = playlist_item_name(item)
|
||||
if is_url(item):
|
||||
url_data = normalize_url(item)
|
||||
if url_data:
|
||||
priority_files.append({
|
||||
"name": url_data["url"],
|
||||
"type": "url",
|
||||
"size": "URL",
|
||||
"zoom": url_data.get("zoom", 1.0),
|
||||
"enabled": playlist_item_enabled(item)
|
||||
})
|
||||
continue
|
||||
|
||||
file_path = os.path.join(prio_dir, item_name)
|
||||
if not os.path.exists(file_path):
|
||||
continue
|
||||
|
||||
ext = os.path.splitext(item_name)[1].lower()
|
||||
ftype = "video" if ext == ".mp4" else "image"
|
||||
size = os.path.getsize(file_path) // 1024
|
||||
|
||||
priority_files.append({
|
||||
"name": item_name,
|
||||
"type": ftype,
|
||||
"size": size,
|
||||
"enabled": playlist_item_enabled(item)
|
||||
})
|
||||
|
||||
playlist_names = {playlist_item_name(item) for item in prio_playlist}
|
||||
for f in sorted(os.listdir(prio_dir)):
|
||||
if f in playlist_names or f.startswith("._"):
|
||||
continue
|
||||
|
||||
file_path = os.path.join(prio_dir, f)
|
||||
ext = os.path.splitext(f)[1].lower()
|
||||
ftype = "video" if ext == ".mp4" else "image"
|
||||
size = os.path.getsize(file_path) // 1024
|
||||
|
||||
priority_files.append({
|
||||
"name": f,
|
||||
"type": ftype,
|
||||
"size": size
|
||||
})
|
||||
|
||||
return priority_files
|
||||
|
||||
|
||||
def prio_redirect(site):
|
||||
return f"/admin/{site}/priority"
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
# User & Login
|
||||
# -------------------------------------------------
|
||||
# Auth / Benutzer
|
||||
# -------------------------------------------------
|
||||
@@ -117,34 +196,62 @@ def logout():
|
||||
# -------------------------------------------------
|
||||
# Customer / Willkommensseite
|
||||
# -------------------------------------------------
|
||||
@app.route("/willkommen", methods=["GET", "POST"])
|
||||
@app.route("/customer", methods=["GET", "POST"])
|
||||
# @login_required
|
||||
def add_customer():
|
||||
"""Add new customer with logo search and welcome page"""
|
||||
error = None
|
||||
success = None
|
||||
logo_url = None
|
||||
|
||||
logo_urls = [None, None, None]
|
||||
customer_names = ["", "", ""]
|
||||
site = request.args.get("site") or request.form.get("site") or ""
|
||||
|
||||
# Default to first site if not specified
|
||||
if not site:
|
||||
config = load_config()
|
||||
sites = get_site_list(config)
|
||||
site = sites[0] if sites else ""
|
||||
|
||||
if request.method == "POST":
|
||||
customer_name = request.form.get("customer_name", "").strip()
|
||||
print(f"🔍 APP.PY - Suche nach Kunde: '{customer_name}'")
|
||||
if not customer_name:
|
||||
error = "Kundenname erforderlich"
|
||||
customer_names = [
|
||||
request.form.get("customer_name_1", "").strip(),
|
||||
request.form.get("customer_name_2", "").strip(),
|
||||
request.form.get("customer_name_3", "").strip(),
|
||||
]
|
||||
filled_names = [n for n in customer_names if n]
|
||||
|
||||
print(f"🔍 APP.PY - Suche nach Kunden: {filled_names} (site: {site})")
|
||||
|
||||
if not filled_names:
|
||||
error = "Mindestens ein Kundenname erforderlich"
|
||||
elif not site:
|
||||
error = "Kein Standort ausgewählt"
|
||||
else:
|
||||
try:
|
||||
# Search for logo
|
||||
logo_url = generate_welcome_page.search_customer_logo(customer_name)
|
||||
|
||||
if not logo_url:
|
||||
error = "Logo konnte nicht gefunden werden"
|
||||
found_logos = []
|
||||
for name in filled_names:
|
||||
logo = generate_welcome_page.search_customer_logo(name)
|
||||
found_logos.append(logo)
|
||||
|
||||
valid = [(n, l) for n, l in zip(filled_names, found_logos) if l]
|
||||
|
||||
if not valid:
|
||||
error = "Kein Logo konnte gefunden werden"
|
||||
else:
|
||||
# Generate and save welcome page
|
||||
html_filename = generate_welcome_page.save_welcome_page(customer_name, logo_url)
|
||||
|
||||
valid_names = [n for n, l in valid]
|
||||
valid_urls = [l for n, l in valid]
|
||||
for i, name in enumerate(customer_names):
|
||||
for j, (vn, vu) in enumerate(zip(valid_names, valid_urls)):
|
||||
if vn == name:
|
||||
logo_urls[i] = vu
|
||||
break
|
||||
|
||||
html_filename = generate_welcome_page.save_welcome_page(valid_names, valid_urls, site=site)
|
||||
|
||||
if html_filename:
|
||||
# Add to lobby playlist
|
||||
if generate_welcome_page.add_customer_to_lobby_playlist(html_filename):
|
||||
success = f"✅ Kunde '{customer_name}' erfolgreich hinzugefügt!"
|
||||
if generate_welcome_page.add_customer_to_lobby_playlist(html_filename, site=site):
|
||||
success = f"✅ Willkommensseite mit {len(valid_names)} Kunden erstellt!"
|
||||
else:
|
||||
error = "Fehler beim Hinzufügen zur Playliste"
|
||||
else:
|
||||
@@ -153,8 +260,22 @@ def add_customer():
|
||||
error = f"Konfigurationsfehler: {str(e)}"
|
||||
except Exception as e:
|
||||
error = f"Fehler: {str(e)}"
|
||||
|
||||
return render_template("customer.html", error=error, success=success, logo_url=logo_url)
|
||||
|
||||
config = load_config()
|
||||
site_list = get_site_list(config)
|
||||
return render_template(
|
||||
"customer.html",
|
||||
error=error,
|
||||
success=success,
|
||||
logo_urls=logo_urls,
|
||||
customer_names=customer_names,
|
||||
site=site,
|
||||
site_list=site_list,
|
||||
current_site=site,
|
||||
version=APP_VERSION,
|
||||
year=datetime.now().year,
|
||||
hostname=os.uname().nodename
|
||||
)
|
||||
|
||||
@app.route("/api/customer", methods=["POST"])
|
||||
# @login_required
|
||||
@@ -163,33 +284,41 @@ def api_add_customer():
|
||||
try:
|
||||
data = request.get_json()
|
||||
customer_name = data.get("customer_name", "").strip()
|
||||
|
||||
site = data.get("site", "").strip()
|
||||
|
||||
if not customer_name:
|
||||
return jsonify({"error": "Customer name required"}), 400
|
||||
|
||||
# Search for logo
|
||||
|
||||
# Default to first site
|
||||
if not site:
|
||||
config = load_config()
|
||||
sites = get_site_list(config)
|
||||
site = sites[0] if sites else ""
|
||||
|
||||
if not site:
|
||||
return jsonify({"error": "No site configured"}), 400
|
||||
|
||||
logo_url = generate_welcome_page.search_customer_logo(customer_name)
|
||||
|
||||
|
||||
if not logo_url:
|
||||
return jsonify({"error": "Logo not found"}), 400
|
||||
|
||||
# Generate and save welcome page
|
||||
html_filename = generate_welcome_page.save_welcome_page(customer_name, logo_url)
|
||||
|
||||
|
||||
html_filename = generate_welcome_page.save_welcome_page(customer_name, logo_url, site=site)
|
||||
|
||||
if not html_filename:
|
||||
return jsonify({"error": "Failed to create welcome page"}), 500
|
||||
|
||||
# Add to lobby playlist
|
||||
if generate_welcome_page.add_customer_to_lobby_playlist(html_filename):
|
||||
|
||||
if generate_welcome_page.add_customer_to_lobby_playlist(html_filename, site=site):
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": f"Customer '{customer_name}' added successfully",
|
||||
"welcome_page": html_filename,
|
||||
"logo_url": logo_url
|
||||
"logo_url": logo_url,
|
||||
"site": site
|
||||
}), 201
|
||||
else:
|
||||
return jsonify({"error": "Failed to add to playlist"}), 500
|
||||
|
||||
|
||||
except ValueError as e:
|
||||
return jsonify({"error": str(e)}), 400
|
||||
except Exception as e:
|
||||
@@ -198,25 +327,36 @@ def api_add_customer():
|
||||
# -------------------------------------------------
|
||||
# Medien ausliefern
|
||||
# -------------------------------------------------
|
||||
@app.route("/media/<screen>/<path:filename>")
|
||||
def media(screen, filename):
|
||||
path = os.path.join(MEDIA_DIR, screen)
|
||||
@app.route("/media/priority/<path:filename>")
|
||||
def media_priority(filename):
|
||||
path = os.path.join(MEDIA_DIR, "priority")
|
||||
return send_from_directory(path, filename)
|
||||
|
||||
@app.route("/media/<site>/<screen>/<path:filename>")
|
||||
def media(site, screen, filename):
|
||||
path = os.path.join(MEDIA_DIR, site, screen)
|
||||
return send_from_directory(path, filename)
|
||||
|
||||
# -------------------------------------------------
|
||||
# Player
|
||||
# -------------------------------------------------
|
||||
|
||||
@app.route("/player/<screen>")
|
||||
def player(screen):
|
||||
@app.route("/player/<site>/<screen>")
|
||||
def player(site, screen):
|
||||
config = load_config()
|
||||
screens_cfg = config.setdefault("screens", {})
|
||||
sites_cfg = config.setdefault("sites", {})
|
||||
site_cfg = sites_cfg.get(site)
|
||||
|
||||
if not site_cfg:
|
||||
abort(404)
|
||||
|
||||
screens_cfg = site_cfg.setdefault("screens", {})
|
||||
screen_cfg = screens_cfg.get(screen)
|
||||
|
||||
if not screen_cfg:
|
||||
abort(404)
|
||||
|
||||
folder = os.path.join(MEDIA_DIR, screen)
|
||||
folder = os.path.join(MEDIA_DIR, site, screen)
|
||||
playlist = screen_cfg.get("playlist", [])
|
||||
|
||||
show_images = screen_cfg.get("show_images", True)
|
||||
@@ -267,7 +407,6 @@ def player(screen):
|
||||
|
||||
# ------------------------------
|
||||
# Priority-Playlist (global)
|
||||
# WICHTIG: IMMER definieren!
|
||||
# ------------------------------
|
||||
priority_cfg = config.get("priority", {})
|
||||
|
||||
@@ -291,26 +430,30 @@ def player(screen):
|
||||
|
||||
return render_template(
|
||||
"player.html",
|
||||
site=site,
|
||||
screen=screen,
|
||||
normal_files=[item for item in normal_files if item.get("enabled", True)],
|
||||
prio_files=[item for item in prio_files if item.get("enabled", True)],
|
||||
interval=screen_cfg.get("interval", 10),
|
||||
newsticker_text=screen_cfg.get("newsticker_text", ""),
|
||||
newsticker_enabled=screen_cfg.get("newsticker_enabled", False)
|
||||
newsticker_enabled=screen_cfg.get("newsticker_enabled", False),
|
||||
custom_url=screen_cfg.get("custom_url", ""),
|
||||
custom_url_label=screen_cfg.get("custom_url_label", ""),
|
||||
custom_url_enabled=screen_cfg.get("custom_url_enabled", False),
|
||||
custom_url_target=screen_cfg.get("custom_url_target", "overlay")
|
||||
)
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
# Hash für automatisches Player-Reload
|
||||
# -------------------------------------------------
|
||||
@app.route("/playlist/<screen>/hash")
|
||||
def playlist_hash(screen):
|
||||
@app.route("/playlist/<site>/<screen>/hash")
|
||||
def playlist_hash(site, screen):
|
||||
config = load_config()
|
||||
|
||||
# ✅ Globaler Hash über ALLE relevanten Daten
|
||||
relevant = {
|
||||
"screens": {
|
||||
screen: config["screens"].get(screen, {})
|
||||
"sites": {
|
||||
site: config.get("sites", {}).get(site, {})
|
||||
},
|
||||
"priority": config.get("priority", {})
|
||||
}
|
||||
@@ -325,33 +468,66 @@ def playlist_hash(screen):
|
||||
|
||||
@app.route("/admin")
|
||||
@login_required
|
||||
def admin():
|
||||
def admin_redirect():
|
||||
"""Redirect to first available site"""
|
||||
cfg = load_config()
|
||||
sites = get_site_list(cfg)
|
||||
if sites:
|
||||
return redirect(f"/admin/{sites[0]}")
|
||||
return "<h1>Kein Standort konfiguriert</h1><p>Bitte lege einen Standort im media-Verzeichnis an.</p>", 200
|
||||
|
||||
|
||||
@app.route("/admin/<site>")
|
||||
@login_required
|
||||
def admin(site):
|
||||
cfg = load_config()
|
||||
|
||||
site_list = get_site_list(cfg)
|
||||
|
||||
# Validate site exists
|
||||
if site not in site_list:
|
||||
# Create default site config if filesystem dir exists but no config
|
||||
if os.path.isdir(os.path.join(MEDIA_DIR, site)):
|
||||
cfg.setdefault("sites", {})[site] = {"screens": {}}
|
||||
save_config(cfg)
|
||||
site_list = get_site_list(cfg)
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
current_site = site
|
||||
screens = {}
|
||||
media_files = {}
|
||||
screen_status = {}
|
||||
|
||||
for screen in os.listdir(MEDIA_DIR):
|
||||
if screen == "priority":
|
||||
continue # ✅ kein echter Screen
|
||||
site_media_base = os.path.join(MEDIA_DIR, current_site)
|
||||
|
||||
path = os.path.join(MEDIA_DIR, screen)
|
||||
if not os.path.isdir(path):
|
||||
continue
|
||||
# Discover screens: from config + filesystem
|
||||
known_screens = set()
|
||||
site_cfg = cfg.setdefault("sites", {}).setdefault(current_site, {"screens": {}})
|
||||
config_screens = site_cfg.setdefault("screens", {})
|
||||
|
||||
# Merge with filesystem directories
|
||||
if os.path.isdir(site_media_base):
|
||||
for entry in os.listdir(site_media_base):
|
||||
entry_path = os.path.join(site_media_base, entry)
|
||||
if os.path.isdir(entry_path) and not entry.startswith("._"):
|
||||
known_screens.add(entry)
|
||||
|
||||
# Add screens from config that might not have a directory yet
|
||||
for s in config_screens:
|
||||
known_screens.add(s)
|
||||
|
||||
for screen_name in sorted(known_screens):
|
||||
path = os.path.join(site_media_base, screen_name)
|
||||
os.makedirs(path, exist_ok=True)
|
||||
|
||||
files = []
|
||||
screen_cfg = cfg.setdefault("screens", {}).setdefault(screen, {
|
||||
"interval": 10,
|
||||
"show_images": True,
|
||||
"show_videos": True,
|
||||
"playlist": []
|
||||
})
|
||||
screen_cfg = get_screen_config(cfg, current_site, screen_name)
|
||||
playlist = screen_cfg.get("playlist", [])
|
||||
screen_cfg["newsticker_text"] = screen_cfg.get("newsticker_text", "")
|
||||
screen_cfg["newsticker_enabled"] = screen_cfg.get("newsticker_enabled", False)
|
||||
|
||||
# 1️⃣ Playlist-Reihenfolge
|
||||
# 1 Playlist-Reihenfolge
|
||||
for item in playlist:
|
||||
item_name = playlist_item_name(item)
|
||||
|
||||
@@ -374,10 +550,10 @@ def admin():
|
||||
ext = os.path.splitext(item_name)[1].lower()
|
||||
if ext == ".mp4":
|
||||
ftype = "video"
|
||||
if ext in (".jpg", ".jpeg", ".png"):
|
||||
if ext in (".jpg", ".jpeg", ".png"):
|
||||
ftype = "image"
|
||||
if ext == ".html":
|
||||
ftype = "html"
|
||||
ftype = "html"
|
||||
|
||||
size = os.path.getsize(file_path) // 1024
|
||||
|
||||
@@ -388,7 +564,7 @@ def admin():
|
||||
"enabled": playlist_item_enabled(item)
|
||||
})
|
||||
|
||||
# 2️⃣ Neue Dateien anhängen (nicht in Playlist)
|
||||
# 2 Neue Dateien anhängen
|
||||
playlist_names = {playlist_item_name(item) for item in playlist}
|
||||
for f in sorted(os.listdir(path)):
|
||||
if f in playlist_names or f.startswith("._"):
|
||||
@@ -405,72 +581,40 @@ def admin():
|
||||
"size": size
|
||||
})
|
||||
|
||||
screens[screen] = screen_cfg
|
||||
media_files[screen] = files
|
||||
screen_status[screen] = "active" if files else "empty"
|
||||
screens[screen_name] = screen_cfg
|
||||
media_files[screen_name] = files
|
||||
screen_status[screen_name] = "active" if files else "empty"
|
||||
|
||||
|
||||
# --- PRIORITY EXTENSION: Admin ---
|
||||
prio_dir = os.path.join(MEDIA_DIR, "priority")
|
||||
os.makedirs(prio_dir, exist_ok=True)
|
||||
|
||||
priority_files = []
|
||||
prio_playlist = cfg["priority"].get("playlist", [])
|
||||
|
||||
# 1️⃣ Playlist-Reihenfolge
|
||||
for item in prio_playlist:
|
||||
item_name = playlist_item_name(item)
|
||||
if is_url(item):
|
||||
url_data = normalize_url(item)
|
||||
if url_data:
|
||||
priority_files.append({
|
||||
"name": url_data["url"],
|
||||
"type": "url",
|
||||
"size": "URL",
|
||||
"zoom": url_data.get("zoom", 1.0),
|
||||
"enabled": playlist_item_enabled(item)
|
||||
})
|
||||
continue
|
||||
|
||||
file_path = os.path.join(prio_dir, item_name)
|
||||
if not os.path.exists(file_path):
|
||||
continue
|
||||
|
||||
ext = os.path.splitext(item_name)[1].lower()
|
||||
ftype = "video" if ext == ".mp4" else "image"
|
||||
size = os.path.getsize(file_path) // 1024
|
||||
|
||||
priority_files.append({
|
||||
"name": item_name,
|
||||
"type": ftype,
|
||||
"size": size,
|
||||
"enabled": playlist_item_enabled(item)
|
||||
})
|
||||
|
||||
# 2️⃣ Neue Dateien anhängen (nicht in Playlist)
|
||||
for f in sorted(os.listdir(prio_dir)):
|
||||
if f in prio_playlist or f.startswith("._"):
|
||||
continue
|
||||
|
||||
file_path = os.path.join(prio_dir, f)
|
||||
ext = os.path.splitext(f)[1].lower()
|
||||
ftype = "video" if ext == ".mp4" else "image"
|
||||
size = os.path.getsize(file_path) // 1024
|
||||
|
||||
priority_files.append({
|
||||
"name": f,
|
||||
"type": ftype,
|
||||
"size": size
|
||||
})
|
||||
|
||||
|
||||
return render_template(
|
||||
"admin.html",
|
||||
site_list=site_list,
|
||||
current_site=current_site,
|
||||
screens=screens,
|
||||
media_files=media_files,
|
||||
screen_status=screen_status,
|
||||
version=APP_VERSION,
|
||||
year=datetime.now().year,
|
||||
hostname=os.uname().nodename
|
||||
)
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
# Admin: Priority-Playlist (separate Seite)
|
||||
# -------------------------------------------------
|
||||
@app.route("/admin/<site>/priority")
|
||||
@login_required
|
||||
def admin_priority(site):
|
||||
cfg = load_config()
|
||||
site_list = sorted(cfg.get("sites", {}).keys())
|
||||
|
||||
priority_files = load_priority_files()
|
||||
|
||||
return render_template(
|
||||
"priority.html",
|
||||
site_list=site_list,
|
||||
current_site=site,
|
||||
version=APP_VERSION,
|
||||
year=datetime.now().year,
|
||||
hostname=os.uname().nodename,
|
||||
priority_files=priority_files
|
||||
)
|
||||
@@ -479,58 +623,106 @@ def admin():
|
||||
# -------------------------------------------------
|
||||
# Admin: Screen-Einstellungen speichern
|
||||
# -------------------------------------------------
|
||||
@app.route("/admin/update/<screen>", methods=["POST"])
|
||||
@app.route("/admin/<site>/update/<screen>", methods=["POST"])
|
||||
@login_required
|
||||
def update_screen(screen):
|
||||
def update_screen(site, screen):
|
||||
config = load_config()
|
||||
cfg = config.setdefault("screens", {}).setdefault(screen, {
|
||||
"interval": 10,
|
||||
"show_images": True,
|
||||
"show_videos": True,
|
||||
"playlist": []
|
||||
})
|
||||
cfg = get_screen_config(config, site, screen)
|
||||
|
||||
cfg["interval"] = int(request.form.get("interval", 10))
|
||||
cfg["interval"] = int(request.form.get("interval") or 10)
|
||||
cfg["show_images"] = "show_images" in request.form
|
||||
cfg["show_videos"] = "show_videos" in request.form
|
||||
cfg["newsticker_text"] = request.form.get("newsticker_text", "")
|
||||
cfg["newsticker_enabled"] = "newsticker_enabled" in request.form
|
||||
cfg["custom_url"] = request.form.get("custom_url", "").strip()
|
||||
cfg["custom_url_label"] = request.form.get("custom_url_label", "").strip()
|
||||
cfg["custom_url_enabled"] = "custom_url_enabled" in request.form
|
||||
cfg["custom_url_target"] = request.form.get("custom_url_target", "overlay")
|
||||
|
||||
save_config(config)
|
||||
return redirect("/admin")
|
||||
return redirect(f"/admin/{site}")
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
# Admin: Screen löschen
|
||||
# -------------------------------------------------
|
||||
@app.route("/admin/<site>/delete-screen/<screen>", methods=["POST"])
|
||||
@login_required
|
||||
def delete_screen(site, screen):
|
||||
config = load_config()
|
||||
|
||||
# Aus config.json entfernen
|
||||
screens_cfg = config.get("sites", {}).get(site, {}).get("screens", {})
|
||||
screens_cfg.pop(screen, None)
|
||||
save_config(config)
|
||||
|
||||
# Medien-Verzeichnis löschen
|
||||
screen_dir = os.path.join(MEDIA_DIR, site, screen)
|
||||
if os.path.isdir(screen_dir):
|
||||
import shutil
|
||||
shutil.rmtree(screen_dir)
|
||||
|
||||
return redirect(f"/admin/{site}")
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
# Admin: Neuen Screen anlegen
|
||||
# -------------------------------------------------
|
||||
@app.route("/admin/<site>/add-screen", methods=["GET"])
|
||||
@login_required
|
||||
def add_screen(site):
|
||||
screen_name = request.args.get("name", "").strip().lower()
|
||||
if not screen_name:
|
||||
return redirect(f"/admin/{site}")
|
||||
|
||||
config = load_config()
|
||||
|
||||
os.makedirs(os.path.join(MEDIA_DIR, site, screen_name), exist_ok=True)
|
||||
|
||||
screens_cfg = config.setdefault("sites", {}).setdefault(site, {}).setdefault("screens", {})
|
||||
if screen_name not in screens_cfg:
|
||||
screens_cfg[screen_name] = {"playlist": [], "interval": 10, "show_images": True, "show_videos": True}
|
||||
|
||||
save_config(config)
|
||||
return redirect(f"/admin/{site}#screen-{screen_name}")
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
# Admin: Upload
|
||||
# -------------------------------------------------
|
||||
@app.route("/admin/upload/<screen>", methods=["POST"])
|
||||
@app.route("/admin/<site>/upload/<screen>", methods=["POST"])
|
||||
@login_required
|
||||
def upload(screen):
|
||||
def upload(site, screen):
|
||||
file = request.files.get("file")
|
||||
if not file:
|
||||
return redirect("/admin")
|
||||
return redirect(prio_redirect(site) if screen == "priority" else f"/admin/{site}")
|
||||
|
||||
ext = os.path.splitext(file.filename)[1].lower()
|
||||
if ext not in UPLOAD_EXTENSIONS:
|
||||
return "Ungültiger Dateityp", 400
|
||||
|
||||
filename = secure_filename(file.filename)
|
||||
file.save(os.path.join(MEDIA_DIR, screen, filename))
|
||||
|
||||
return redirect("/admin")
|
||||
if screen == "priority":
|
||||
file.save(os.path.join(MEDIA_DIR, "priority", filename))
|
||||
else:
|
||||
file.save(os.path.join(MEDIA_DIR, site, screen, filename))
|
||||
|
||||
return redirect(prio_redirect(site) if screen == "priority" else f"/admin/{site}")
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
# Admin: URL hinzufügen
|
||||
# -------------------------------------------------
|
||||
@app.route("/admin/add-url/<screen>", methods=["POST"])
|
||||
@app.route("/admin/<site>/add-url/<screen>", methods=["POST"])
|
||||
@login_required
|
||||
def add_url(screen):
|
||||
def add_url(site, screen):
|
||||
url = request.form.get("url", "").strip()
|
||||
zoom = request.form.get("zoom", "1.0").strip()
|
||||
|
||||
|
||||
if not is_url(url):
|
||||
return redirect("/admin")
|
||||
|
||||
return redirect(prio_redirect(site) if screen == "priority" else f"/admin/{site}")
|
||||
|
||||
try:
|
||||
zoom_factor = float(zoom)
|
||||
if zoom_factor <= 0:
|
||||
@@ -538,83 +730,73 @@ def add_url(screen):
|
||||
except (ValueError, TypeError):
|
||||
zoom_factor = 1.0
|
||||
|
||||
# Store as object with zoom factor
|
||||
url_data = {"url": url, "zoom": zoom_factor}
|
||||
|
||||
|
||||
config = load_config()
|
||||
if screen == "priority":
|
||||
config.setdefault("priority", {}).setdefault("playlist", []).append(url_data)
|
||||
else:
|
||||
screen_cfg = config.setdefault("screens", {}).setdefault(screen, {
|
||||
"interval": 10,
|
||||
"show_images": True,
|
||||
"show_videos": True,
|
||||
"playlist": []
|
||||
})
|
||||
screen_cfg.setdefault("playlist", []).append(url_data)
|
||||
cfg = get_screen_config(config, site, screen)
|
||||
cfg.setdefault("playlist", []).append(url_data)
|
||||
|
||||
save_config(config)
|
||||
return redirect("/admin")
|
||||
return redirect(prio_redirect(site) if screen == "priority" else f"/admin/{site}")
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
# Admin: Datei löschen
|
||||
# -------------------------------------------------
|
||||
|
||||
@app.route("/admin/delete/<screen>/<path:filename>", methods=["POST"])
|
||||
@app.route("/admin/<site>/delete/<screen>/<path:filename>", methods=["POST"])
|
||||
@login_required
|
||||
def delete_file(screen, filename):
|
||||
def delete_file(site, screen, filename):
|
||||
cfg = load_config()
|
||||
|
||||
# Datei löschen
|
||||
path = os.path.join(MEDIA_DIR, screen, filename)
|
||||
if os.path.exists(path):
|
||||
os.remove(path)
|
||||
|
||||
# --- Playlist bereinigen ---
|
||||
if screen == "priority":
|
||||
path = os.path.join(MEDIA_DIR, "priority", filename)
|
||||
playlist = cfg.get("priority", {}).get("playlist", [])
|
||||
# Remove both string URLs and dict-based URLs
|
||||
playlist[:] = [item for item in playlist if not (
|
||||
(isinstance(item, str) and item == filename) or
|
||||
(isinstance(item, dict) and item.get("url") == filename)
|
||||
)]
|
||||
else:
|
||||
playlist = cfg["screens"].get(screen, {}).get("playlist", [])
|
||||
# Remove both string URLs and dict-based URLs
|
||||
path = os.path.join(MEDIA_DIR, site, screen, filename)
|
||||
screens_cfg = cfg.get("sites", {}).get(site, {}).get("screens", {})
|
||||
playlist = screens_cfg.get(screen, {}).get("playlist", [])
|
||||
playlist[:] = [item for item in playlist if not (
|
||||
(isinstance(item, str) and item == filename) or
|
||||
(isinstance(item, dict) and item.get("url") == filename)
|
||||
)]
|
||||
|
||||
if os.path.exists(path):
|
||||
os.remove(path)
|
||||
|
||||
save_config(cfg)
|
||||
return redirect("/admin")
|
||||
return redirect(prio_redirect(site) if screen == "priority" else f"/admin/{site}")
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
# Admin: Playlist speichern
|
||||
# -------------------------------------------------
|
||||
|
||||
@app.route("/admin/playlist/<screen>", methods=["POST"])
|
||||
@app.route("/admin/<site>/playlist/<screen>", methods=["POST"])
|
||||
@login_required
|
||||
def save_playlist(screen):
|
||||
def save_playlist(site, screen):
|
||||
cfg = load_config()
|
||||
data = request.get_json()
|
||||
new_order = data["playlist"] # List of filenames/URLs as strings
|
||||
new_order = data["playlist"]
|
||||
enabled_map = data.get("enabled", {})
|
||||
|
||||
# Get the current playlist with all data (including zoom factors)
|
||||
|
||||
if screen == "priority":
|
||||
old_playlist = cfg.get("priority", {}).get("playlist", [])
|
||||
else:
|
||||
old_playlist = cfg.get("screens", {}).get(screen, {}).get("playlist", [])
|
||||
|
||||
# Create a mapping: filename/url -> full item (preserves zoom factor)
|
||||
screens_cfg = cfg.get("sites", {}).get(site, {}).get("screens", {})
|
||||
old_playlist = screens_cfg.get(screen, {}).get("playlist", [])
|
||||
|
||||
item_map = {}
|
||||
for item in old_playlist:
|
||||
item_map[playlist_item_name(item)] = item
|
||||
|
||||
# Build new playlist: use the mapping to preserve zoom factors
|
||||
|
||||
new_playlist = []
|
||||
for key in new_order:
|
||||
if key in item_map:
|
||||
@@ -623,11 +805,11 @@ def save_playlist(screen):
|
||||
new_playlist.append(item)
|
||||
else:
|
||||
new_playlist.append({"url": key, "zoom": 1.0, "enabled": enabled_map.get(key, True)} if is_url(key) else {"name": key, "enabled": enabled_map.get(key, True)})
|
||||
|
||||
|
||||
if screen == "priority":
|
||||
cfg.setdefault("priority", {})["playlist"] = new_playlist
|
||||
else:
|
||||
cfg.setdefault("screens", {}).setdefault(screen, {})["playlist"] = new_playlist
|
||||
cfg.setdefault("sites", {}).setdefault(site, {}).setdefault("screens", {}).setdefault(screen, {})["playlist"] = new_playlist
|
||||
|
||||
save_config(cfg)
|
||||
return "", 204
|
||||
|
||||
Reference in New Issue
Block a user