feat: Refactor template handling, improve scheduler functions, and add timezone utilities

- Moved Jinja2 template setup to a shared configuration file (templates_config.py) for consistent usage across routers.
- Introduced timezone utilities in a new module (timezone.py) to handle UTC to local time conversions and formatting.
- Updated all relevant routers to use the new shared template configuration and timezone filters.
- Enhanced templates to utilize local time formatting for various datetime fields, improving user experience with timezone awareness.
This commit is contained in:
serversdwn
2026-01-23 06:05:39 +00:00
parent c771a86675
commit 8431784708
23 changed files with 418 additions and 141 deletions

View File

@@ -9,17 +9,19 @@ Provides API endpoints for the Projects system:
"""
from fastapi import APIRouter, Request, Depends, HTTPException, Query
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse
from sqlalchemy.orm import Session
from sqlalchemy import func, and_
from datetime import datetime, timedelta
from typing import Optional
from collections import OrderedDict
import uuid
import json
import logging
import io
from backend.utils.timezone import utc_to_local, format_local_datetime
from backend.database import get_db
from backend.models import (
Project,
@@ -31,9 +33,9 @@ from backend.models import (
RecurringSchedule,
RosterUnit,
)
from backend.templates_config import templates
router = APIRouter(prefix="/api/projects", tags=["projects"])
templates = Jinja2Templates(directory="templates")
logger = logging.getLogger(__name__)
@@ -461,16 +463,37 @@ async def get_project_schedules(
if status:
query = query.filter(ScheduledAction.execution_status == status)
schedules = query.order_by(ScheduledAction.scheduled_time.desc()).all()
# For pending actions, show soonest first (ascending)
# For completed/failed, show most recent first (descending)
if status == "pending":
schedules = query.order_by(ScheduledAction.scheduled_time.asc()).all()
else:
schedules = query.order_by(ScheduledAction.scheduled_time.desc()).all()
# Enrich with location details
schedules_data = []
# Enrich with location details and group by date
schedules_by_date = OrderedDict()
for schedule in schedules:
location = None
if schedule.location_id:
location = db.query(MonitoringLocation).filter_by(id=schedule.location_id).first()
schedules_data.append({
# Get local date for grouping
if schedule.scheduled_time:
local_dt = utc_to_local(schedule.scheduled_time)
date_key = local_dt.strftime("%Y-%m-%d")
date_display = local_dt.strftime("%A, %B %d, %Y") # "Wednesday, January 22, 2026"
else:
date_key = "unknown"
date_display = "Unknown Date"
if date_key not in schedules_by_date:
schedules_by_date[date_key] = {
"date_display": date_display,
"date_key": date_key,
"actions": [],
}
schedules_by_date[date_key]["actions"].append({
"schedule": schedule,
"location": location,
})
@@ -478,7 +501,7 @@ async def get_project_schedules(
return templates.TemplateResponse("partials/projects/schedule_list.html", {
"request": request,
"project_id": project_id,
"schedules": schedules_data,
"schedules_by_date": schedules_by_date,
})