Initial commit: self-hosted personal radio station
Flask + React web UI with audio player, podcast queue, feed management, episode browser, music library, schedule viewer, and log tail. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
"""GET /api/logs — tail log files."""
|
||||
|
||||
import os
|
||||
|
||||
from flask import Blueprint, jsonify, request
|
||||
|
||||
from ..config import LOG_DIR
|
||||
|
||||
bp = Blueprint("logs", __name__)
|
||||
|
||||
ALLOWED_LOGS = {"poller", "liquidsoap", "icecast_access", "icecast_error"}
|
||||
|
||||
|
||||
@bp.route("/logs")
|
||||
def get_logs():
|
||||
log_name = request.args.get("file", "poller")
|
||||
lines_count = request.args.get("lines", 50, type=int)
|
||||
lines_count = min(lines_count, 500)
|
||||
|
||||
if log_name not in ALLOWED_LOGS:
|
||||
return jsonify({"error": f"Unknown log file. Allowed: {ALLOWED_LOGS}"}), 400
|
||||
|
||||
log_path = LOG_DIR / f"{log_name}.log"
|
||||
|
||||
if not log_path.exists():
|
||||
return jsonify({"lines": [], "file": log_name, "exists": False})
|
||||
|
||||
# Read last N lines efficiently
|
||||
try:
|
||||
with open(log_path, "rb") as f:
|
||||
f.seek(0, os.SEEK_END)
|
||||
size = f.tell()
|
||||
# Read up to 1MB from the end
|
||||
read_size = min(size, 1024 * 1024)
|
||||
f.seek(max(0, size - read_size))
|
||||
content = f.read().decode("utf-8", errors="replace")
|
||||
|
||||
all_lines = content.splitlines()
|
||||
tail = all_lines[-lines_count:]
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
return jsonify({
|
||||
"lines": tail,
|
||||
"file": log_name,
|
||||
"exists": True,
|
||||
"total_lines": len(all_lines),
|
||||
})
|
||||
Reference in New Issue
Block a user