Nový DB helper bez ORM: app/db/scheduled_scripts.py
Nové route/UI: app/routes/scheduled_scripts.py
Router registrovaný v app/main.py
Menu doplněné v app/templates/layout.py
Job filtr rozšířený o run_script v app/routes/jobs.py
Implementováno:
GET /portal/scheduled-scripts
GET /portal/scheduled-scripts/{id}
GET/POST /portal/scheduled-scripts/new
GET/POST /portal/scheduled-scripts/{id}/edit
POST /portal/scheduled-scripts/{id}/run-now
POST /portal/scheduled-scripts/{id}/toggle
POST /portal/scheduled-scripts/{id}/delete
Ruční spuštění vytváří run_script job přes existující create_job(...) se source="portal_manual" a payloadem podle zadání. Mazání je blokované, pokud is_running = 1. Validace script_name, schedule_type a timeout_seconds je v route vrstvě.
Audit eventy jsou přidané:
scheduled_script.created
scheduled_script.updated
scheduled_script.deleted
scheduled_script.enabled
scheduled_script.disabled
scheduled_script.run_now
This commit is contained in:
JiriUhlir
2026-06-03 12:18:39 +02:00
parent 46f8024d4e
commit f7a8007af8
5 changed files with 632 additions and 2 deletions
+172
View File
@@ -0,0 +1,172 @@
from typing import Any
from app.db.database import get_connection
from app.db.migrations import run_migrations
def get_scheduled_scripts():
run_migrations()
con = get_connection()
rows = con.execute(
"""
SELECT
id,
name,
description,
script_name,
schedule_type,
schedule_time,
timeout_seconds,
COALESCE(is_enabled, 1) AS is_enabled,
COALESCE(is_running, 0) AS is_running,
last_run_at,
last_status,
last_job_id,
last_error_text,
next_run_at,
created_at,
updated_at
FROM scheduled_scripts
ORDER BY name
"""
).fetchall()
con.close()
return [dict(row) for row in rows]
def get_scheduled_script(script_id: int):
run_migrations()
con = get_connection()
row = con.execute(
"""
SELECT
id,
name,
description,
script_name,
schedule_type,
schedule_time,
timeout_seconds,
COALESCE(is_enabled, 1) AS is_enabled,
COALESCE(is_running, 0) AS is_running,
last_run_at,
last_status,
last_job_id,
last_error_text,
next_run_at,
created_at,
updated_at
FROM scheduled_scripts
WHERE id = ?
""",
(script_id,),
).fetchone()
con.close()
return dict(row) if row else None
def create_scheduled_script(metadata: dict[str, Any]) -> int:
run_migrations()
con = get_connection()
cur = con.execute(
"""
INSERT INTO scheduled_scripts (
name,
description,
script_name,
schedule_type,
schedule_time,
timeout_seconds,
is_enabled,
is_running,
created_at,
updated_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
""",
(
metadata.get("name"),
metadata.get("description") or None,
metadata.get("script_name"),
metadata.get("schedule_type"),
metadata.get("schedule_time") or None,
metadata.get("timeout_seconds"),
1 if metadata.get("is_enabled") else 0,
),
)
script_id = cur.lastrowid
con.commit()
con.close()
return script_id
def update_scheduled_script(script_id: int, metadata: dict[str, Any]) -> None:
run_migrations()
con = get_connection()
con.execute(
"""
UPDATE scheduled_scripts
SET name = ?,
description = ?,
schedule_type = ?,
schedule_time = ?,
timeout_seconds = ?,
is_enabled = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
""",
(
metadata.get("name"),
metadata.get("description") or None,
metadata.get("schedule_type"),
metadata.get("schedule_time") or None,
metadata.get("timeout_seconds"),
1 if metadata.get("is_enabled") else 0,
script_id,
),
)
con.commit()
con.close()
def set_scheduled_script_enabled(script_id: int, is_enabled: bool) -> None:
run_migrations()
con = get_connection()
con.execute(
"""
UPDATE scheduled_scripts
SET is_enabled = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
""",
(1 if is_enabled else 0, script_id),
)
con.commit()
con.close()
def delete_scheduled_script(script_id: int) -> bool:
run_migrations()
con = get_connection()
cur = con.execute(
"""
DELETE FROM scheduled_scripts
WHERE id = ?
AND COALESCE(is_running, 0) = 0
""",
(script_id,),
)
deleted = cur.rowcount == 1
con.commit()
con.close()
return deleted