Files
terra-view/templates/partials/projects/schedule_list.html
2026-01-23 19:07:42 +00:00

236 lines
15 KiB
HTML

<!-- 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>
<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 %}
<!-- Execution result details for completed/failed actions -->
{% if item.result and item.schedule.execution_status in ['completed', 'failed'] %}
<div class="mt-2 p-2 bg-gray-50 dark:bg-gray-700/50 rounded text-xs space-y-1">
{% if item.result.cycle_response %}
{% set cycle = item.result.cycle_response %}
{% if cycle.new_index is defined and cycle.new_index is not none %}
<div class="flex items-center gap-2">
<span class="text-gray-500 dark:text-gray-400">Index:</span>
<span class="font-mono text-gray-700 dark:text-gray-300">{{ '%04d'|format(cycle.new_index) }}</span>
{% if cycle.old_index is defined and cycle.old_index is not none %}
<span class="text-gray-400">(was {{ '%04d'|format(cycle.old_index) }})</span>
{% endif %}
</div>
{% endif %}
{% if cycle.downloaded_folder %}
<div class="flex items-center gap-2">
<span class="text-gray-500 dark:text-gray-400">Folder:</span>
<span class="font-mono text-gray-700 dark:text-gray-300">{{ cycle.downloaded_folder }}</span>
{% if cycle.download_success %}
<span class="text-green-600 dark:text-green-400">Downloaded</span>
{% elif cycle.download_attempted %}
<span class="text-red-600 dark:text-red-400">Download failed</span>
{% endif %}
</div>
{% endif %}
{% if cycle.clock_synced %}
<div class="flex items-center gap-2">
<span class="text-gray-500 dark:text-gray-400">Clock synced:</span>
<span class="text-green-600 dark:text-green-400">Yes</span>
</div>
{% endif %}
{% elif item.result.device_response %}
{% set dev = item.result.device_response %}
<div class="flex items-center gap-2">
<span class="text-gray-500 dark:text-gray-400">Device:</span>
<span class="text-gray-700 dark:text-gray-300">{{ dev.message or dev.status }}</span>
</div>
{% endif %}
{% if item.result.session_id %}
<div class="flex items-center gap-2">
<span class="text-gray-500 dark:text-gray-400">Session:</span>
<span class="font-mono text-xs text-gray-600 dark:text-gray-400">{{ item.result.session_id[:8] }}...</span>
</div>
{% endif %}
</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>
{% else %}
<div class="text-center py-12">
<svg class="w-16 h-16 mx-auto mb-4 text-gray-400" 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>
<p class="text-gray-500 dark:text-gray-400 mb-2">No scheduled actions yet</p>
<p class="text-sm text-gray-400 dark:text-gray-500">Create schedules to automate tasks</p>
</div>
{% endif %}
<script>
function executeSchedule(scheduleId) {
if (!confirm('Execute this scheduled action now?')) return;
fetch(`/api/projects/{{ project_id }}/schedules/${scheduleId}/execute`, {
method: 'POST',
})
.then(response => response.json())
.then(data => {
if (data.success) {
htmx.trigger('#project-schedules', 'refresh');
} else {
alert('Error: ' + (data.message || 'Unknown error'));
}
})
.catch(error => {
alert('Error executing schedule: ' + error);
});
}
function cancelSchedule(scheduleId) {
if (!confirm('Cancel this scheduled action?')) return;
fetch(`/api/projects/{{ project_id }}/schedules/${scheduleId}/cancel`, {
method: 'POST',
})
.then(response => response.json())
.then(data => {
if (data.success) {
htmx.trigger('#project-schedules', 'refresh');
} else {
alert('Error: ' + (data.message || 'Unknown error'));
}
})
.catch(error => {
alert('Error cancelling schedule: ' + error);
});
}
</script>