From 337091d8d1b98f5f8b47d5494048c67e25150124 Mon Sep 17 00:00:00 2001 From: Marsway Date: Tue, 13 Jan 2026 03:05:32 +0800 Subject: [PATCH 1/5] =?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), From 65676ad64b8a77638e2ee889a34cc3b3cb78ee99 Mon Sep 17 00:00:00 2001 From: Marsway Date: Tue, 13 Jan 2026 09:49:09 +0800 Subject: [PATCH 2/5] update --- app/admin/filters.py | 46 ++++++++++++++++++++--------------- app/admin/views.py | 58 ++++++++++++++++++++++---------------------- 2 files changed, 56 insertions(+), 48 deletions(-) diff --git a/app/admin/filters.py b/app/admin/filters.py index 0948725..ef2e564 100644 --- a/app/admin/filters.py +++ b/app/admin/filters.py @@ -6,9 +6,12 @@ from typing import Any class RecentDateTimeFilter: """ - 最近时间筛选(避免依赖 DateTime 的 OperationColumnFilter 支持情况): - - all: 全部 - - 1h/24h/7d/30d: 最近 N + 最近时间范围筛选(用于 started_at/created_at 等 DateTime 列): + - all / 1h / 24h / 7d / 30d + + SQLAdmin 自定义 ColumnFilter 约定: + - 必须提供 title / parameter_name + - 必须实现 lookups(request, model) 与 get_filtered_query(query, value) """ def __init__(self, column: Any, *, title: str, parameter_name: str) -> None: @@ -16,34 +19,39 @@ class RecentDateTimeFilter: self.title = title self.parameter_name = parameter_name - def lookups(self, request, model) -> list[tuple[str, str]]: # noqa: ARG002 (framework signature) + def lookups(self, request, model) -> list[tuple[str, str]]: # noqa: ARG002 return [ ("all", "全部"), - ("1h", "最近 1 小时"), - ("24h", "最近 24 小时"), - ("7d", "最近 7 天"), - ("30d", "最近 30 天"), + ("1h", "最近1小时"), + ("24h", "最近24小时"), + ("7d", "最近7天"), + ("30d", "最近30天"), ] - def get_filtered_query(self, query, value: str): + def get_filtered_query(self, query, value): # type: ignore[no-untyped-def] if not value or value == "all": return query now = datetime.utcnow() + delta: timedelta | None = None if value == "1h": - threshold = now - timedelta(hours=1) + delta = timedelta(hours=1) elif value == "24h": - threshold = now - timedelta(hours=24) + delta = timedelta(hours=24) elif value == "7d": - threshold = now - timedelta(days=7) + delta = timedelta(days=7) elif value == "30d": - threshold = now - timedelta(days=30) - else: + delta = timedelta(days=30) + + if delta is None: return query - cond = self.column >= threshold - # SQLAlchemy Select 在不同版本下可能是 where/filter;这里兼容两者 - if hasattr(query, "where"): - return query.where(cond) - return query.filter(cond) + threshold = now - delta + + # 兼容 SQLAlchemy Query / Select 两种风格 + if hasattr(query, "where"): + return query.where(self.column >= threshold) + if hasattr(query, "filter"): + return query.filter(self.column >= threshold) + return query diff --git a/app/admin/views.py b/app/admin/views.py index 17b40a1..e27a4ac 100644 --- a/app/admin/views.py +++ b/app/admin/views.py @@ -77,6 +77,16 @@ class JobAdmin(ModelView, model=Job): # 列表页模板:加入每行 Run Now list_template = "job_list.html" + # 搜索 + column_searchable_list = [Job.id, Job.handler_path, Job.cron_expr] + + # 筛选 + column_filters = [ + BooleanFilter(Job.enabled), + OperationColumnFilter(Job.handler_path), + RecentDateTimeFilter(Job.created_at, title="创建时间", parameter_name="created_at_recent"), + ] + # 编辑页排除 secret_cfg,避免回显密文;由自定义模板额外渲染一个空输入框 # 注意:SQLAdmin 这里需要字段名字符串(不是 SQLAlchemy Column 对象) form_edit_rules = ["id", "enabled", "cron_expr", "handler_path", "public_cfg"] @@ -105,16 +115,6 @@ 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="立即运行", @@ -226,6 +226,25 @@ class JobLogAdmin(ModelView, model=JobLog): # 为 JobLog 详情页单独指定模板(用于加入 Retry 按钮) details_template = "joblog_details.html" + # 搜索 + column_searchable_list = [JobLog.job_id, JobLog.message, JobLog.celery_task_id] + + # 筛选 + column_filters = [ + StaticValuesFilter( + JobLog.status, + values=[ + ("RUNNING", "运行中"), + ("SUCCESS", "成功"), + ("FAILURE", "失败"), + ("RETRY", "重试"), + ], + title="状态", + ), + OperationColumnFilter(JobLog.attempt), + RecentDateTimeFilter(JobLog.started_at, title="开始时间", parameter_name="started_at_recent"), + ] + column_labels = { "id": "日志ID", "job_id": "任务ID", @@ -240,25 +259,6 @@ 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), From c56c3c0bef4471fbd597558047faf0673619a946 Mon Sep 17 00:00:00 2001 From: Marsway Date: Tue, 13 Jan 2026 09:51:52 +0800 Subject: [PATCH 3/5] update --- docker-compose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 936e6ef..99c8c5a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,9 +12,9 @@ services: POSTGRES_PASSWORD: connecthub_pwd_change_me volumes: - ./data/pgdata:/var/lib/postgresql/data - # 如需宿主机直连可打开该映射 - # ports: - # - "5432:5432" + 如需宿主机直连可打开该映射 + ports: + - "5432:5432" backend: build: From a98697e37696828afaba1ffc1e187b73f5bc10cd Mon Sep 17 00:00:00 2001 From: Marsway Date: Tue, 13 Jan 2026 09:52:22 +0800 Subject: [PATCH 4/5] update --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 99c8c5a..f73a274 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,7 @@ services: POSTGRES_PASSWORD: connecthub_pwd_change_me volumes: - ./data/pgdata:/var/lib/postgresql/data - 如需宿主机直连可打开该映射 + # 如需宿主机直连可打开该映射 ports: - "5432:5432" From 1435653b0bc6dddc3276cc508aadff0dbb8f8593 Mon Sep 17 00:00:00 2001 From: Marsway Date: Tue, 13 Jan 2026 09:58:34 +0800 Subject: [PATCH 5/5] update --- app/admin/filters.py | 57 -------------------------------------------- app/admin/views.py | 31 ------------------------ app/tasks/execute.py | 42 ++++++++++++++++---------------- 3 files changed, 21 insertions(+), 109 deletions(-) delete mode 100644 app/admin/filters.py diff --git a/app/admin/filters.py b/app/admin/filters.py deleted file mode 100644 index ef2e564..0000000 --- a/app/admin/filters.py +++ /dev/null @@ -1,57 +0,0 @@ -from __future__ import annotations - -from datetime import datetime, timedelta -from typing import Any - - -class RecentDateTimeFilter: - """ - 最近时间范围筛选(用于 started_at/created_at 等 DateTime 列): - - all / 1h / 24h / 7d / 30d - - SQLAdmin 自定义 ColumnFilter 约定: - - 必须提供 title / parameter_name - - 必须实现 lookups(request, model) 与 get_filtered_query(query, value) - """ - - 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 - return [ - ("all", "全部"), - ("1h", "最近1小时"), - ("24h", "最近24小时"), - ("7d", "最近7天"), - ("30d", "最近30天"), - ] - - def get_filtered_query(self, query, value): # type: ignore[no-untyped-def] - if not value or value == "all": - return query - - now = datetime.utcnow() - delta: timedelta | None = None - if value == "1h": - delta = timedelta(hours=1) - elif value == "24h": - delta = timedelta(hours=24) - elif value == "7d": - delta = timedelta(days=7) - elif value == "30d": - delta = timedelta(days=30) - - if delta is None: - return query - - threshold = now - delta - - # 兼容 SQLAlchemy Query / Select 两种风格 - if hasattr(query, "where"): - return query.where(self.column >= threshold) - if hasattr(query, "filter"): - return query.filter(self.column >= threshold) - return query - diff --git a/app/admin/views.py b/app/admin/views.py index e27a4ac..9cc6158 100644 --- a/app/admin/views.py +++ b/app/admin/views.py @@ -8,11 +8,9 @@ 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 @@ -77,16 +75,6 @@ class JobAdmin(ModelView, model=Job): # 列表页模板:加入每行 Run Now list_template = "job_list.html" - # 搜索 - column_searchable_list = [Job.id, Job.handler_path, Job.cron_expr] - - # 筛选 - column_filters = [ - BooleanFilter(Job.enabled), - OperationColumnFilter(Job.handler_path), - RecentDateTimeFilter(Job.created_at, title="创建时间", parameter_name="created_at_recent"), - ] - # 编辑页排除 secret_cfg,避免回显密文;由自定义模板额外渲染一个空输入框 # 注意:SQLAdmin 这里需要字段名字符串(不是 SQLAlchemy Column 对象) form_edit_rules = ["id", "enabled", "cron_expr", "handler_path", "public_cfg"] @@ -226,25 +214,6 @@ class JobLogAdmin(ModelView, model=JobLog): # 为 JobLog 详情页单独指定模板(用于加入 Retry 按钮) details_template = "joblog_details.html" - # 搜索 - column_searchable_list = [JobLog.job_id, JobLog.message, JobLog.celery_task_id] - - # 筛选 - column_filters = [ - StaticValuesFilter( - JobLog.status, - values=[ - ("RUNNING", "运行中"), - ("SUCCESS", "成功"), - ("FAILURE", "失败"), - ("RETRY", "重试"), - ], - title="状态", - ), - OperationColumnFilter(JobLog.attempt), - RecentDateTimeFilter(JobLog.started_at, title="开始时间", parameter_name="started_at_recent"), - ] - column_labels = { "id": "日志ID", "job_id": "任务ID", diff --git a/app/tasks/execute.py b/app/tasks/execute.py index 74f8ecb..44217eb 100644 --- a/app/tasks/execute.py +++ b/app/tasks/execute.py @@ -169,30 +169,30 @@ def execute_job(self, job_id: str | None = None, snapshot_params: dict[str, Any] ) else: if not snapshot: - snapshot = snapshot_params or { - "job_id": job_id, - "handler_path": handler_path if "handler_path" in locals() else "", - "public_cfg": public_cfg if "public_cfg" in locals() else {}, - "secret_cfg": secret_token if "secret_token" in locals() else "", - "meta": { - "trigger": "celery", + snapshot = snapshot_params or { + "job_id": job_id, + "handler_path": handler_path if "handler_path" in locals() else "", + "public_cfg": public_cfg if "public_cfg" in locals() else {}, + "secret_cfg": secret_token if "secret_token" in locals() else "", + "meta": { + "trigger": "celery", "celery_task_id": celery_task_id, - "started_at": started_at.isoformat(), - }, - } - crud.create_job_log( - session, - job_id=str(job_id or ""), - status=status, - snapshot_params=snapshot, - message=message, - traceback=traceback, - run_log=run_log_text, + "started_at": started_at.isoformat(), + }, + } + crud.create_job_log( + session, + job_id=str(job_id or ""), + status=status, + snapshot_params=snapshot, + message=message, + traceback=traceback, + run_log=run_log_text, celery_task_id=celery_task_id, attempt=attempt, - started_at=started_at, - finished_at=finished_at, - ) + started_at=started_at, + finished_at=finished_at, + ) session.close() return {"status": status.value, "job_id": job_id, "result": result, "message": message}