logs_commit4
This commit is contained in:
@@ -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": [
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user