config for logs and filters
This commit is contained in:
@@ -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(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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)]
|
||||||
|
|||||||
@@ -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
@@ -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
Reference in New Issue
Block a user