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

@@ -10,7 +10,7 @@
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Location: {{ item.location.name }}</p>
{% endif %}
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
Assigned: {% if item.assignment.assigned_at %}{{ item.assignment.assigned_at.strftime('%Y-%m-%d %H:%M') }}{% else %}Unknown{% endif %}
Assigned: {% if item.assignment.assigned_at %}{{ item.assignment.assigned_at|local_datetime }}{% else %}Unknown{% endif %}
</p>
</div>
<button onclick="unassignUnit('{{ item.assignment.id }}')" class="text-xs px-3 py-1 rounded-full bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300">

View File

@@ -81,7 +81,7 @@
{% for action in upcoming_actions %}
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-3">
<p class="font-medium text-gray-900 dark:text-white">{{ action.action_type }}</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">{{ action.scheduled_time.strftime('%Y-%m-%d %H:%M') }}</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">{{ action.scheduled_time|local_datetime }} {{ timezone_abbr() }}</p>
{% if action.description %}
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">{{ action.description }}</p>
{% endif %}

View File

@@ -74,7 +74,7 @@
{% if item.schedule.next_occurrence %}
<div class="text-xs">
<span class="text-gray-400">Next:</span>
{{ item.schedule.next_occurrence.strftime('%Y-%m-%d %H:%M') }} {{ item.schedule.timezone }}
{{ item.schedule.next_occurrence|local_datetime }} {{ timezone_abbr() }}
</div>
{% endif %}
</div>

View File

@@ -1,102 +1,139 @@
<!-- Scheduled Actions List -->
{% if schedules %}
<div class="space-y-4">
{% for item in schedules %}
<div class="border border-gray-200 dark:border-gray-700 rounded-lg p-4 hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors">
<div class="flex items-start justify-between gap-3">
<div class="min-w-0 flex-1">
<div class="flex items-center gap-3 mb-2">
<h4 class="font-semibold text-gray-900 dark:text-white">
{{ item.schedule.action_type }}
</h4>
{% if item.schedule.execution_status == 'pending' %}
<span class="px-2 py-1 text-xs font-medium bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300 rounded-full">
Pending
</span>
{% elif item.schedule.execution_status == 'completed' %}
<span class="px-2 py-1 text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300 rounded-full">
Completed
</span>
{% elif item.schedule.execution_status == 'failed' %}
<span class="px-2 py-1 text-xs font-medium bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300 rounded-full">
Failed
</span>
{% elif item.schedule.execution_status == 'cancelled' %}
<span class="px-2 py-1 text-xs font-medium bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300 rounded-full">
Cancelled
</span>
{% endif %}
</div>
<div class="grid grid-cols-2 gap-3 text-sm text-gray-600 dark:text-gray-400">
{% if item.location %}
<div>
<span class="text-xs text-gray-500">Location:</span>
<a href="/projects/{{ project_id }}/nrl/{{ item.location.id }}"
class="text-seismo-orange hover:text-seismo-navy font-medium ml-1">
{{ item.location.name }}
</a>
</div>
{% endif %}
<div>
<span class="text-xs text-gray-500">Scheduled:</span>
<span class="ml-1">{{ item.schedule.scheduled_time.strftime('%Y-%m-%d %H:%M') if item.schedule.scheduled_time else 'N/A' }}</span>
</div>
{% if item.schedule.executed_at %}
<div>
<span class="text-xs text-gray-500">Executed:</span>
<span class="ml-1">{{ item.schedule.executed_at.strftime('%Y-%m-%d %H:%M') }}</span>
</div>
{% endif %}
{% if item.schedule.created_at %}
<div>
<span class="text-xs text-gray-500">Created:</span>
<span class="ml-1">{{ item.schedule.created_at.strftime('%Y-%m-%d %H:%M') }}</span>
</div>
{% endif %}
</div>
{% if item.schedule.description %}
<p class="text-xs text-gray-500 dark:text-gray-400 mt-2">
{{ item.schedule.description }}
</p>
{% endif %}
{% if item.schedule.result_message %}
<div class="mt-2 text-xs">
<span class="text-gray-500">Result:</span>
<span class="ml-1 text-gray-700 dark:text-gray-300">{{ item.schedule.result_message }}</span>
</div>
{% endif %}
{% if item.schedule.error_message %}
<div class="mt-2 p-2 bg-red-50 dark:bg-red-900/20 rounded text-xs">
<span class="text-red-600 dark:text-red-400 font-medium">Error:</span>
<span class="ml-1 text-red-700 dark:text-red-300">{{ item.schedule.error_message }}</span>
</div>
{% endif %}
<!-- Scheduled Actions List - Grouped by Date -->
{% if schedules_by_date %}
<div class="space-y-6">
{% for date_key, date_group in schedules_by_date.items() %}
<div>
<!-- Date Header -->
<div class="flex items-center gap-3 mb-3">
<div class="flex-shrink-0 w-10 h-10 bg-seismo-orange/10 dark:bg-seismo-orange/20 rounded-lg flex items-center justify-center">
<svg class="w-5 h-5 text-seismo-orange" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
</svg>
</div>
<div class="flex items-center gap-2">
{% if item.schedule.execution_status == 'pending' %}
<button onclick="executeSchedule('{{ item.schedule.id }}')"
class="px-3 py-1 text-xs bg-seismo-orange text-white rounded-lg hover:bg-seismo-navy transition-colors">
Execute Now
</button>
<button onclick="cancelSchedule('{{ item.schedule.id }}')"
class="px-3 py-1 text-xs bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300 rounded-lg hover:bg-red-200 dark:hover:bg-red-900/50 transition-colors">
Cancel
</button>
{% endif %}
<button onclick="viewScheduleDetails('{{ item.schedule.id }}')"
class="px-3 py-1 text-xs bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors">
Details
</button>
<div>
<h3 class="font-semibold text-gray-900 dark:text-white">{{ date_group.date_display }}</h3>
<p class="text-xs text-gray-500">{{ date_group.actions|length }} action{{ 's' if date_group.actions|length != 1 else '' }}</p>
</div>
</div>
<!-- Actions for this date -->
<div class="space-y-3 ml-13 pl-3 border-l-2 border-gray-200 dark:border-gray-700">
{% for item in date_group.actions %}
<div class="bg-white dark:bg-slate-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4 hover:shadow-md transition-shadow">
<div class="flex items-start justify-between gap-3">
<div class="min-w-0 flex-1">
<div class="flex items-center gap-3 mb-2">
<!-- Action type with icon -->
{% if item.schedule.action_type == 'start' %}
<span class="flex items-center gap-1.5 text-green-600 dark:text-green-400 font-semibold">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd"/>
</svg>
Start
</span>
{% elif item.schedule.action_type == 'stop' %}
<span class="flex items-center gap-1.5 text-red-600 dark:text-red-400 font-semibold">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8 7a1 1 0 00-1 1v4a1 1 0 001 1h4a1 1 0 001-1V8a1 1 0 00-1-1H8z" clip-rule="evenodd"/>
</svg>
Stop
</span>
{% elif item.schedule.action_type == 'download' %}
<span class="flex items-center gap-1.5 text-blue-600 dark:text-blue-400 font-semibold">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd"/>
</svg>
Download
</span>
{% else %}
<span class="font-semibold text-gray-900 dark:text-white">{{ item.schedule.action_type }}</span>
{% endif %}
<!-- Status badge -->
{% if item.schedule.execution_status == 'pending' %}
<span class="px-2 py-0.5 text-xs font-medium bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300 rounded-full">
Pending
</span>
{% elif item.schedule.execution_status == 'completed' %}
<span class="px-2 py-0.5 text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300 rounded-full">
Completed
</span>
{% elif item.schedule.execution_status == 'failed' %}
<span class="px-2 py-0.5 text-xs font-medium bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300 rounded-full">
Failed
</span>
{% elif item.schedule.execution_status == 'cancelled' %}
<span class="px-2 py-0.5 text-xs font-medium bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300 rounded-full">
Cancelled
</span>
{% endif %}
</div>
<div class="flex flex-wrap gap-4 text-sm text-gray-600 dark:text-gray-400">
<!-- Time -->
<div class="flex items-center gap-1">
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span>{{ item.schedule.scheduled_time|local_datetime('%H:%M') if item.schedule.scheduled_time else 'N/A' }}</span>
</div>
<!-- Location -->
{% if item.location %}
<div class="flex items-center gap-1">
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
<a href="/projects/{{ project_id }}/nrl/{{ item.location.id }}"
class="text-seismo-orange hover:text-seismo-navy font-medium">
{{ item.location.name }}
</a>
</div>
{% endif %}
{% if item.schedule.executed_at %}
<div class="flex items-center gap-1">
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span>Executed {{ item.schedule.executed_at|local_datetime('%H:%M') }}</span>
</div>
{% endif %}
</div>
{% if item.schedule.error_message %}
<div class="mt-2 p-2 bg-red-50 dark:bg-red-900/20 rounded text-xs">
<span class="text-red-600 dark:text-red-400 font-medium">Error:</span>
<span class="ml-1 text-red-700 dark:text-red-300">{{ item.schedule.error_message }}</span>
</div>
{% endif %}
</div>
<!-- Actions -->
<div class="flex items-center gap-2 flex-shrink-0">
{% if item.schedule.execution_status == 'pending' %}
<button onclick="executeSchedule('{{ item.schedule.id }}')"
class="p-2 text-green-600 hover:bg-green-100 dark:hover:bg-green-900/30 rounded-lg transition-colors"
title="Execute Now">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</button>
<button onclick="cancelSchedule('{{ item.schedule.id }}')"
class="p-2 text-red-600 hover:bg-red-100 dark:hover:bg-red-900/30 rounded-lg transition-colors"
title="Cancel">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
@@ -148,9 +185,4 @@ function cancelSchedule(scheduleId) {
alert('Error cancelling schedule: ' + error);
});
}
function viewScheduleDetails(scheduleId) {
// TODO: Implement schedule details modal
alert('Schedule details coming soon: ' + scheduleId);
}
</script>

View File

@@ -41,13 +41,13 @@
<div>
<span class="text-xs text-gray-500">Started:</span>
<span class="ml-1">{{ item.session.started_at.strftime('%Y-%m-%d %H:%M') if item.session.started_at else 'N/A' }}</span>
<span class="ml-1">{{ item.session.started_at|local_datetime if item.session.started_at else 'N/A' }}</span>
</div>
{% if item.session.stopped_at %}
<div>
<span class="text-xs text-gray-500">Ended:</span>
<span class="ml-1">{{ item.session.stopped_at.strftime('%Y-%m-%d %H:%M') }}</span>
<span class="ml-1">{{ item.session.stopped_at|local_datetime }}</span>
</div>
{% endif %}

View File

@@ -24,7 +24,7 @@
</svg>
<div>
<div class="font-semibold text-gray-900 dark:text-white">
{{ session.started_at.strftime('%Y-%m-%d %H:%M') if session.started_at else 'Unknown Date' }}
{{ session.started_at|local_datetime if session.started_at else 'Unknown Date' }}
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">
{% if unit %}{{ unit.id }}{% else %}Unknown Unit{% endif %}
@@ -155,7 +155,7 @@
<!-- Download Time -->
{% if file.downloaded_at %}
<span class="mx-1"></span>
{{ file.downloaded_at.strftime('%Y-%m-%d %H:%M') }}
{{ file.downloaded_at|local_datetime }}
{% endif %}
<!-- Source Info from Metadata -->

View File

@@ -60,7 +60,7 @@
{% if item.assignment.assigned_at %}
<div class="col-span-2">
<span class="text-xs text-gray-500">Assigned:</span>
<span class="ml-1">{{ item.assignment.assigned_at.strftime('%Y-%m-%d %H:%M') }}</span>
<span class="ml-1">{{ item.assignment.assigned_at|local_datetime }}</span>
</div>
{% endif %}
</div>

View File

@@ -51,7 +51,7 @@
<div class="mt-2 text-xs text-gray-500 dark:text-gray-400">
{% if unit.slm_last_check %}
Last check: {{ unit.slm_last_check.strftime('%Y-%m-%d %H:%M') }}
Last check: {{ unit.slm_last_check|local_datetime }}
{% else %}
No recent check-in
{% endif %}