config for logs and filters

This commit is contained in:
Mateusz Gruszczyński
2026-05-20 10:31:07 +02:00
parent 0a82211e4c
commit 4cff530b0e
11 changed files with 53 additions and 10 deletions

View File

@@ -22,7 +22,7 @@ from flask import Blueprint, jsonify, request, abort, send_file, redirect, Respo
from ..config import DB_PATH, JOBS_RETENTION_DAYS, SMART_QUEUE_HISTORY_RETENTION_DAYS, LOG_RETENTION_DAYS, WORKERS, PYTORRENT_TMP_DIR from ..config import DB_PATH, JOBS_RETENTION_DAYS, SMART_QUEUE_HISTORY_RETENTION_DAYS, LOG_RETENTION_DAYS, WORKERS, PYTORRENT_TMP_DIR
from ..db import connect, utcnow from ..db import connect, utcnow
from ..services.auth import current_user_id as default_user_id, current_user, list_users, save_user, delete_user, login_user, logout_user, enabled as auth_enabled, require_profile_write from ..services.auth import current_user_id as default_user_id, current_user, list_users, save_user, delete_user, login_user, logout_user, enabled as auth_enabled, require_profile_write
from ..services import preferences, rtorrent, torrent_stats, speed_peaks, tracker_cache, rss as rss_service, ratio_rules, backup as backup_service, download_planner from ..services import preferences, rtorrent, torrent_stats, speed_peaks, tracker_cache, rss as rss_service, ratio_rules, backup as backup_service, download_planner, operation_logs
from ..services.torrent_cache import torrent_cache from ..services.torrent_cache import torrent_cache
from ..services.torrent_summary import cached_summary from ..services.torrent_summary import cached_summary
from ..services.workers import enqueue, list_jobs, cancel_job, retry_job, force_job, clear_jobs, emergency_clear_jobs from ..services.workers import enqueue, list_jobs, cancel_job, retry_job, force_job, clear_jobs, emergency_clear_jobs
@@ -288,6 +288,7 @@ def cleanup_summary() -> dict:
"WHERE profile_id=? OR profile_id IS NULL", "WHERE profile_id=? OR profile_id IS NULL",
(profile_id,), (profile_id,),
) if profile_id else _table_count("operation_logs") ) if profile_id else _table_count("operation_logs")
operation_log_retention = operation_logs.get_settings(profile_id) if profile_id else operation_logs.get_settings(0)
return { return {
"jobs_total": _table_count("jobs"), "jobs_total": _table_count("jobs"),
"jobs_clearable": _table_count("jobs", "WHERE status NOT IN ('pending', 'running')"), "jobs_clearable": _table_count("jobs", "WHERE status NOT IN ('pending', 'running')"),
@@ -299,10 +300,14 @@ def cleanup_summary() -> dict:
"retention_days": { "retention_days": {
"jobs": JOBS_RETENTION_DAYS, "jobs": JOBS_RETENTION_DAYS,
"smart_queue_history": SMART_QUEUE_HISTORY_RETENTION_DAYS, "smart_queue_history": SMART_QUEUE_HISTORY_RETENTION_DAYS,
"operation_logs": LOG_RETENTION_DAYS, "operation_logs": operation_log_retention.get("retention_days", LOG_RETENTION_DAYS),
"automation_history": SMART_QUEUE_HISTORY_RETENTION_DAYS, "automation_history": SMART_QUEUE_HISTORY_RETENTION_DAYS,
"planner_history": SMART_QUEUE_HISTORY_RETENTION_DAYS, "planner_history": SMART_QUEUE_HISTORY_RETENTION_DAYS,
}, },
"operation_log_retention": operation_log_retention,
"retention_labels": {
"operation_logs": operation_logs.retention_label(operation_log_retention),
},
"database": _db_size(), "database": _db_size(),
} }

View File

@@ -23,6 +23,7 @@ def operation_logs_list():
offset=int(request.args.get("offset") or 0), offset=int(request.args.get("offset") or 0),
event_type=str(request.args.get("type") or "").strip(), event_type=str(request.args.get("type") or "").strip(),
q=str(request.args.get("q") or "").strip(), q=str(request.args.get("q") or "").strip(),
hide_jobs=str(request.args.get("hide_jobs") or "").lower() in {"1", "true", "yes", "on"},
) )
data["stats"] = operation_logs.stats(int(profile["id"])) data["stats"] = operation_logs.stats(int(profile["id"]))
data["settings"] = data["stats"].get("settings") data["settings"] = data["stats"].get("settings")

View File

@@ -125,7 +125,7 @@ def record_cache_diff(profile_id: int, added: list[dict], removed: list[str], up
record(profile_id, "torrent_completed", f"Torrent completed: {old.get('name') or h}", source="poller", torrent_hash=h, torrent_name=old.get("name"), details={"ratio": patch.get("ratio", old.get("ratio")), "size": old.get("size"), "path": old.get("path")}) record(profile_id, "torrent_completed", f"Torrent completed: {old.get('name') or h}", source="poller", torrent_hash=h, torrent_name=old.get("name"), details={"ratio": patch.get("ratio", old.get("ratio")), "size": old.get("size"), "path": old.get("path")})
def list_logs(profile_id: int, *, limit: int = 200, offset: int = 0, event_type: str = "", q: str = "") -> dict: def list_logs(profile_id: int, *, limit: int = 200, offset: int = 0, event_type: str = "", q: str = "", hide_jobs: bool = False) -> dict:
limit = max(1, min(int(limit or 200), 1000)) limit = max(1, min(int(limit or 200), 1000))
offset = max(0, int(offset or 0)) offset = max(0, int(offset or 0))
where = ["(profile_id=? OR profile_id IS NULL)"] where = ["(profile_id=? OR profile_id IS NULL)"]
@@ -133,6 +133,9 @@ def list_logs(profile_id: int, *, limit: int = 200, offset: int = 0, event_type:
if event_type: if event_type:
where.append("event_type=?") where.append("event_type=?")
params.append(event_type) params.append(event_type)
if hide_jobs:
# Note: Job-originated rows include torrent_added/torrent_removed events, so source is the reliable filter.
where.append("COALESCE(source, '') <> 'job'")
if q: if q:
where.append("(message LIKE ? OR torrent_name LIKE ? OR torrent_hash LIKE ? OR action LIKE ?)") where.append("(message LIKE ? OR torrent_name LIKE ? OR torrent_hash LIKE ? OR action LIKE ?)")
like = f"%{q}%" like = f"%{q}%"
@@ -155,6 +158,17 @@ def stats(profile_id: int) -> dict:
return {"total": int(total or 0), "by_type": by_type, "by_day": by_day, "by_month": by_month, "top_actions": top_actions, "settings": get_settings(profile_id)} return {"total": int(total or 0), "by_type": by_type, "by_day": by_day, "by_month": by_month, "top_actions": top_actions, "settings": get_settings(profile_id)}
def retention_label(settings: dict) -> str:
mode = settings.get("retention_mode") or "days"
if mode == "manual":
return "manual cleanup only"
if mode == "lines":
return f"retention {settings.get('retention_lines') or DEFAULT_SETTINGS['retention_lines']} lines"
if mode == "both":
return f"retention {settings.get('retention_days') or DEFAULT_SETTINGS['retention_days']} days and {settings.get('retention_lines') or DEFAULT_SETTINGS['retention_lines']} lines"
return f"retention {settings.get('retention_days') or DEFAULT_SETTINGS['retention_days']} days"
def clear(profile_id: int, *, event_type: str = "") -> int: def clear(profile_id: int, *, event_type: str = "") -> int:
where = ["(profile_id=? OR profile_id IS NULL)"] where = ["(profile_id=? OR profile_id IS NULL)"]
params: list[Any] = [int(profile_id or 0)] params: list[Any] = [int(profile_id or 0)]

View File

@@ -38,6 +38,9 @@ RECOMMENDED_TABLE_COLUMNS = {
"created": False, "priority": False, "state": False, "active": False, "complete": False, "created": False, "priority": False, "state": False, "active": False, "complete": False,
"hashing": False, "message": False, "hash": False, "hashing": False, "message": False, "hash": False,
}, },
"mobileSortFilters": {
"seeds:-1": True, "up_rate:-1": True, "down_rate:-1": True, "progress:-1": True,
},
"mobileSmartFiltersEnabled": False, "mobileSmartFiltersEnabled": False,
"widths": { "widths": {
"select": 44, "name": 389, "status": 83, "size": 75, "progress": 177, "select": 44, "name": 389, "status": 83, "size": 75, "progress": 177,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -3338,6 +3338,21 @@ body.mobile-mode .mobile-filter-bar {
grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
gap: 0.55rem; gap: 0.55rem;
} }
.column-config-section {
display: grid;
gap: 0.5rem;
margin-bottom: 1rem;
}
.column-config-section:last-child {
margin-bottom: 0;
}
.column-config-section h6 {
margin: 0;
}
.mobile-progress:empty { .mobile-progress:empty {
display: none; display: none;
} }
@@ -4181,6 +4196,11 @@ body,
max-width: 260px; max-width: 260px;
} }
.operation-log-hide-jobs {
align-items: center;
min-height: 31px;
}
.operation-log-settings-actions { .operation-log-settings-actions {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;

File diff suppressed because one or more lines are too long