From 337091d8d1b98f5f8b47d5494048c67e25150124 Mon Sep 17 00:00:00 2001 From: Marsway Date: Tue, 13 Jan 2026 03:05:32 +0800 Subject: [PATCH] =?UTF-8?q?update=EF=BC=9A=E7=AD=9B=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/filters.py | 49 ++++++++++++++++++++++++++++++++++++++++++++ app/admin/views.py | 31 ++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 app/admin/filters.py diff --git a/app/admin/filters.py b/app/admin/filters.py new file mode 100644 index 0000000..0948725 --- /dev/null +++ b/app/admin/filters.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +from datetime import datetime, timedelta +from typing import Any + + +class RecentDateTimeFilter: + """ + 最近时间筛选(避免依赖 DateTime 的 OperationColumnFilter 支持情况): + - all: 全部 + - 1h/24h/7d/30d: 最近 N + """ + + def __init__(self, column: Any, *, title: str, parameter_name: str) -> None: + self.column = column + self.title = title + self.parameter_name = parameter_name + + def lookups(self, request, model) -> list[tuple[str, str]]: # noqa: ARG002 (framework signature) + return [ + ("all", "全部"), + ("1h", "最近 1 小时"), + ("24h", "最近 24 小时"), + ("7d", "最近 7 天"), + ("30d", "最近 30 天"), + ] + + def get_filtered_query(self, query, value: str): + if not value or value == "all": + return query + + now = datetime.utcnow() + if value == "1h": + threshold = now - timedelta(hours=1) + elif value == "24h": + threshold = now - timedelta(hours=24) + elif value == "7d": + threshold = now - timedelta(days=7) + elif value == "30d": + threshold = now - timedelta(days=30) + else: + return query + + cond = self.column >= threshold + # SQLAlchemy Select 在不同版本下可能是 where/filter;这里兼容两者 + if hasattr(query, "where"): + return query.where(cond) + return query.filter(cond) + diff --git a/app/admin/views.py b/app/admin/views.py index 9cc6158..17b40a1 100644 --- a/app/admin/views.py +++ b/app/admin/views.py @@ -8,9 +8,11 @@ from zoneinfo import ZoneInfo from croniter import croniter from markupsafe import Markup from sqladmin import ModelView, action +from sqladmin.filters import BooleanFilter, OperationColumnFilter, StaticValuesFilter from sqladmin.models import Request from starlette.responses import RedirectResponse +from app.admin.filters import RecentDateTimeFilter from app.db.models import Job, JobLog from app.plugins.manager import load_job_class from app.security.fernet import encrypt_json @@ -103,6 +105,16 @@ class JobAdmin(ModelView, model=Job): Job.last_run_at: lambda m, a: _fmt_dt_seconds(m.last_run_at), } + # 搜索:顶部 Search 输入框 + column_searchable_list = [Job.id, Job.handler_path, Job.cron_expr] + + # 筛选:右侧 Filters 栏 + column_filters = [ + BooleanFilter(Job.enabled), + OperationColumnFilter(Job.handler_path), + RecentDateTimeFilter(Job.created_at, title="创建时间", parameter_name="created_at_recent"), + ] + @action( name="run_now", label="立即运行", @@ -228,6 +240,25 @@ class JobLogAdmin(ModelView, model=JobLog): "finished_at": "结束时间", } + # 搜索:任务ID / message / celery_task_id + column_searchable_list = [JobLog.job_id, JobLog.message, JobLog.celery_task_id] + + # 筛选:状态 / 重试次数 / 最近开始时间 + column_filters = [ + StaticValuesFilter( + JobLog.status, + options=( + ("RUNNING", "运行中"), + ("SUCCESS", "成功"), + ("FAILURE", "失败"), + ("RETRY", "重试"), + ), + title="状态", + ), + OperationColumnFilter(JobLog.attempt), + RecentDateTimeFilter(JobLog.started_at, title="开始时间", parameter_name="started_at_recent"), + ] + column_formatters = { JobLog.started_at: lambda m, a: _fmt_dt_seconds(m.started_at), JobLog.finished_at: lambda m, a: _fmt_dt_seconds(m.finished_at),