236 lines
15 KiB
HTML
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>
|