logs_commit4

This commit is contained in:
Mateusz Gruszczyński
2026-05-20 08:55:20 +02:00
parent 44ebb6afb0
commit 0a82211e4c
6 changed files with 490 additions and 10 deletions

View File

@@ -370,6 +370,9 @@
"jobs_total": {
"type": "integer"
},
"operation_logs_total": {
"type": "integer"
},
"planner_history_total": {
"type": "integer"
},
@@ -381,6 +384,9 @@
"jobs": {
"type": "integer"
},
"operation_logs": {
"type": "integer"
},
"planner_history": {
"type": "integer"
},
@@ -401,10 +407,26 @@
"retention_days",
"database",
"cache",
"planner_history_total"
"planner_history_total",
"operation_logs_total"
],
"type": "object"
},
"DeletedResponse": {
"allOf": [
{
"$ref": "#/components/schemas/ApiOk"
},
{
"properties": {
"deleted": {
"type": "integer"
}
},
"type": "object"
}
]
},
"FilePriorityRequest": {
"properties": {
"files": {
@@ -528,6 +550,262 @@
"additionalProperties": true,
"type": "object"
},
"OperationLogClearRequest": {
"properties": {
"event_type": {
"description": "Optional event type filter. Empty clears all active-profile operation logs.",
"type": "string"
}
},
"type": "object"
},
"OperationLogEntry": {
"properties": {
"action": {
"nullable": true,
"type": "string"
},
"created_at": {
"type": "string"
},
"details": {
"additionalProperties": true,
"type": "object"
},
"details_h": {
"type": "string"
},
"details_json": {
"type": "string"
},
"event_type": {
"type": "string"
},
"id": {
"type": "integer"
},
"message": {
"type": "string"
},
"profile_id": {
"nullable": true,
"type": "integer"
},
"severity": {
"type": "string"
},
"source": {
"type": "string"
},
"torrent_hash": {
"nullable": true,
"type": "string"
},
"torrent_name": {
"nullable": true,
"type": "string"
},
"user_id": {
"type": "integer"
}
},
"type": "object"
},
"OperationLogRetentionResult": {
"properties": {
"deleted": {
"type": "integer"
},
"deleted_days": {
"type": "integer"
},
"deleted_lines": {
"type": "integer"
},
"settings": {
"$ref": "#/components/schemas/OperationLogSettings"
}
},
"type": "object"
},
"OperationLogSettings": {
"properties": {
"created_at": {
"type": "string"
},
"profile_id": {
"type": "integer"
},
"retention_days": {
"maximum": 3650,
"minimum": 1,
"type": "integer"
},
"retention_lines": {
"maximum": 1000000,
"minimum": 100,
"type": "integer"
},
"retention_mode": {
"enum": [
"days",
"lines",
"both",
"manual"
],
"type": "string"
},
"updated_at": {
"type": "string"
},
"user_id": {
"type": "integer"
}
},
"type": "object"
},
"OperationLogSettingsRequest": {
"properties": {
"retention_days": {
"maximum": 3650,
"minimum": 1,
"type": "integer"
},
"retention_lines": {
"maximum": 1000000,
"minimum": 100,
"type": "integer"
},
"retention_mode": {
"enum": [
"days",
"lines",
"both",
"manual"
],
"type": "string"
}
},
"type": "object"
},
"OperationLogSettingsResponse": {
"allOf": [
{
"$ref": "#/components/schemas/ApiOk"
},
{
"properties": {
"retention": {
"$ref": "#/components/schemas/OperationLogRetentionResult"
},
"settings": {
"$ref": "#/components/schemas/OperationLogSettings"
}
},
"type": "object"
}
]
},
"OperationLogStats": {
"properties": {
"by_day": {
"items": {
"properties": {
"bucket": {
"type": "string"
},
"n": {
"type": "integer"
}
},
"type": "object"
},
"type": "array"
},
"by_month": {
"items": {
"properties": {
"bucket": {
"type": "string"
},
"n": {
"type": "integer"
}
},
"type": "object"
},
"type": "array"
},
"by_type": {
"items": {
"properties": {
"event_type": {
"type": "string"
},
"n": {
"type": "integer"
}
},
"type": "object"
},
"type": "array"
},
"settings": {
"$ref": "#/components/schemas/OperationLogSettings"
},
"top_actions": {
"items": {
"properties": {
"action": {
"type": "string"
},
"n": {
"type": "integer"
}
},
"type": "object"
},
"type": "array"
},
"total": {
"type": "integer"
}
},
"type": "object"
},
"OperationLogsResponse": {
"allOf": [
{
"$ref": "#/components/schemas/ApiOk"
},
{
"properties": {
"limit": {
"type": "integer"
},
"logs": {
"items": {
"$ref": "#/components/schemas/OperationLogEntry"
},
"type": "array"
},
"offset": {
"type": "integer"
},
"settings": {
"$ref": "#/components/schemas/OperationLogSettings"
},
"stats": {
"$ref": "#/components/schemas/OperationLogStats"
},
"total": {
"type": "integer"
}
},
"type": "object"
}
]
},
"PathBrowseResponse": {
"allOf": [
{
@@ -2636,7 +2914,7 @@
},
"/api/cleanup/all": {
"post": {
"description": "Clears finished job logs, Smart Queue history, Planner action history and automation history. Pending/running jobs, saved rules, settings and torrents are preserved.",
"description": "Clears finished job logs, Smart Queue history, active-profile operation logs, Planner action history and automation history. Pending/running jobs, saved rules, settings and torrents are preserved.",
"responses": {
"200": {
"content": {
@@ -2734,6 +3012,29 @@
"summary": "Clear finished job history"
}
},
"/api/cleanup/operation-logs": {
"post": {
"description": "Clears active-profile operation logs. Torrents, jobs, settings and rules are preserved.",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CleanupResponse"
}
}
},
"description": "Cleanup result"
}
},
"security": [
{
"sessionCookie": []
}
],
"summary": "Clear operation logs"
}
},
"/api/cleanup/planner": {
"post": {
"description": "Deletes Download Planner action history for the active profile. Saved Planner settings are preserved.",
@@ -3482,6 +3783,162 @@
"summary": "OpenAPI schema"
}
},
"/api/operation-logs": {
"get": {
"description": "Lists active-profile operation logs with filters, retention settings and statistics.",
"parameters": [
{
"in": "query",
"name": "limit",
"required": false,
"schema": {
"maximum": 1000,
"minimum": 1,
"type": "integer"
}
},
{
"in": "query",
"name": "offset",
"required": false,
"schema": {
"minimum": 0,
"type": "integer"
}
},
{
"in": "query",
"name": "type",
"required": false,
"schema": {
"type": "string"
}
},
{
"in": "query",
"name": "q",
"required": false,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OperationLogsResponse"
}
}
},
"description": "Operation logs"
}
},
"security": [
{
"sessionCookie": []
}
],
"summary": "List operation logs"
}
},
"/api/operation-logs/apply-retention": {
"post": {
"description": "Runs retention cleanup for active-profile operation logs.",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/ApiOk"
},
{
"$ref": "#/components/schemas/OperationLogRetentionResult"
}
]
}
}
},
"description": "Retention result"
}
},
"security": [
{
"sessionCookie": []
}
],
"summary": "Apply operation log retention"
}
},
"/api/operation-logs/clear": {
"post": {
"description": "Clears active-profile operation logs, optionally limited to one event type.",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OperationLogClearRequest"
}
}
},
"required": false
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DeletedResponse"
}
}
},
"description": "Deleted count"
}
},
"security": [
{
"sessionCookie": []
}
],
"summary": "Clear operation logs"
}
},
"/api/operation-logs/settings": {
"post": {
"description": "Saves active-profile operation log retention and applies it immediately.",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OperationLogSettingsRequest"
}
}
},
"required": false
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/OperationLogSettingsResponse"
}
}
},
"description": "Saved settings"
}
},
"security": [
{
"sessionCookie": []
}
],
"summary": "Save operation log retention settings"
}
},
"/api/path/browse": {
"get": {
"parameters": [

View File

@@ -19,7 +19,7 @@ import threading
from pathlib import Path
from urllib.parse import quote
from flask import Blueprint, jsonify, request, abort, send_file, redirect, Response, stream_with_context
from ..config import DB_PATH, JOBS_RETENTION_DAYS, SMART_QUEUE_HISTORY_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 ..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
@@ -281,16 +281,25 @@ def _active_profile_cache_summary(profile_id: int | None = None) -> dict:
def cleanup_summary() -> dict:
active_profile = preferences.active_profile()
profile_id = int((active_profile or {}).get("id") or 0)
operation_logs_total = _table_count(
"operation_logs",
"WHERE profile_id=? OR profile_id IS NULL",
(profile_id,),
) if profile_id else _table_count("operation_logs")
return {
"jobs_total": _table_count("jobs"),
"jobs_clearable": _table_count("jobs", "WHERE status NOT IN ('pending', 'running')"),
"smart_queue_history_total": _table_count("smart_queue_history"),
"operation_logs_total": operation_logs_total,
"automation_history_total": _table_count("automation_history"),
"planner_history_total": download_planner.history_count(int((preferences.active_profile() or {}).get("id") or 0)) if preferences.active_profile() else 0,
"cache": _active_profile_cache_summary(),
"planner_history_total": download_planner.history_count(profile_id) if profile_id else 0,
"cache": _active_profile_cache_summary(profile_id if profile_id else None),
"retention_days": {
"jobs": JOBS_RETENTION_DAYS,
"smart_queue_history": SMART_QUEUE_HISTORY_RETENTION_DAYS,
"operation_logs": LOG_RETENTION_DAYS,
"automation_history": SMART_QUEUE_HISTORY_RETENTION_DAYS,
"planner_history": SMART_QUEUE_HISTORY_RETENTION_DAYS,
},

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
from ._shared import *
from ..services import operation_logs
@bp.get("/system/disk")
def system_disk():
@@ -208,6 +209,17 @@ def cleanup_smart_queue():
@bp.post("/cleanup/operation-logs")
def cleanup_operation_logs():
profile = preferences.active_profile()
if not profile:
return jsonify({"ok": False, "error": "No profile"}), 400
# Note: Operation log cleanup removes only profile-scoped log entries; torrents, jobs and settings stay intact.
deleted = operation_logs.clear(int(profile["id"]))
return ok({"deleted": deleted, "cleanup": cleanup_summary()})
@bp.post("/cleanup/planner")
def cleanup_planner():
profile = preferences.active_profile()
@@ -236,7 +248,9 @@ def cleanup_automations():
def cleanup_all():
deleted_jobs = clear_jobs()
active_profile = preferences.active_profile()
deleted_planner = download_planner.clear_history(int(active_profile["id"])) if active_profile else 0
active_profile_id = int(active_profile["id"]) if active_profile else 0
deleted_logs = operation_logs.clear(active_profile_id) if active_profile_id else 0
deleted_planner = download_planner.clear_history(active_profile_id) if active_profile_id else 0
with connect() as conn:
exists = conn.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='smart_queue_history'").fetchone()
if not exists:
@@ -250,7 +264,7 @@ def cleanup_all():
else:
cur = conn.execute("DELETE FROM automation_history")
deleted_auto = int(cur.rowcount or 0)
return ok({"deleted": {"jobs": deleted_jobs, "smart_queue_history": deleted_smart, "planner_history": deleted_planner, "automation_history": deleted_auto}, "cleanup": cleanup_summary()})
return ok({"deleted": {"jobs": deleted_jobs, "smart_queue_history": deleted_smart, "operation_logs": deleted_logs, "planner_history": deleted_planner, "automation_history": deleted_auto}, "cleanup": cleanup_summary()})

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