- Add Quill editor to content-page-edit.html - Add Quill editor to vendor-product-edit.html - Add Quill snow theme CSS - Update background-tasks.html and platform-homepage.html templates Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
283 lines
16 KiB
HTML
283 lines
16 KiB
HTML
{# app/templates/admin/background-tasks.html #}
|
|
{% extends "admin/base.html" %}
|
|
{% from 'shared/macros/alerts.html' import loading_state, error_state %}
|
|
{% from 'shared/macros/headers.html' import page_header_flex, refresh_button %}
|
|
|
|
{% block title %}Background Tasks{% endblock %}
|
|
|
|
{% block alpine_data %}backgroundTasks(){% endblock %}
|
|
|
|
{% block extra_scripts %}
|
|
<script src="/static/admin/js/background-tasks.js"></script>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
{% call page_header_flex(title='Background Tasks', subtitle='Monitor running and completed background tasks') %}
|
|
<!-- Flower Dashboard Link (Celery Monitoring) -->
|
|
<a href="{{ flower_url }}"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg active:bg-purple-600 hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple mr-2"
|
|
title="Open Flower dashboard for detailed Celery task monitoring">
|
|
<span x-html="$icon('chart-bar', 'w-4 h-4 mr-2')"></span>
|
|
Flower Dashboard
|
|
</a>
|
|
{{ refresh_button(variant='secondary') }}
|
|
{% endcall %}
|
|
|
|
{{ loading_state('Loading tasks...') }}
|
|
|
|
{{ error_state('Error loading tasks') }}
|
|
|
|
<!-- Dashboard Content -->
|
|
<div x-show="!loading && !error">
|
|
<!-- Stats Cards -->
|
|
<div class="grid gap-6 mb-8 md:grid-cols-2 xl:grid-cols-4">
|
|
<!-- Running Tasks -->
|
|
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
|
<div class="p-3 mr-4 text-yellow-500 bg-yellow-100 rounded-full dark:text-yellow-100 dark:bg-yellow-500">
|
|
<span x-html="$icon('refresh', 'w-5 h-5 animate-spin')"></span>
|
|
</div>
|
|
<div>
|
|
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Running</p>
|
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.running">0</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Completed Today -->
|
|
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
|
<div class="p-3 mr-4 text-green-500 bg-green-100 rounded-full dark:text-green-100 dark:bg-green-500">
|
|
<span x-html="$icon('check-circle', 'w-5 h-5')"></span>
|
|
</div>
|
|
<div>
|
|
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Today</p>
|
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.tasks_today">0</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Failed -->
|
|
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
|
<div class="p-3 mr-4 text-red-500 bg-red-100 rounded-full dark:text-red-100 dark:bg-red-500">
|
|
<span x-html="$icon('x-circle', 'w-5 h-5')"></span>
|
|
</div>
|
|
<div>
|
|
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Failed</p>
|
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.failed">0</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Total -->
|
|
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
|
<div class="p-3 mr-4 text-purple-500 bg-purple-100 rounded-full dark:text-purple-100 dark:bg-purple-500">
|
|
<span x-html="$icon('collection', 'w-5 h-5')"></span>
|
|
</div>
|
|
<div>
|
|
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Total</p>
|
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.total_tasks">0</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Running Tasks Section -->
|
|
<div class="mb-8" x-show="runningTasks.length > 0">
|
|
<div class="p-6 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
|
<h4 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200 flex items-center">
|
|
<span x-html="$icon('refresh', 'w-5 h-5 mr-2 animate-spin text-yellow-500')"></span>
|
|
Currently Running
|
|
</h4>
|
|
<div class="space-y-3">
|
|
<template x-for="task in runningTasks" :key="task.task_type + '-' + task.id">
|
|
<div class="p-4 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg border border-yellow-200 dark:border-yellow-800">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<span class="px-2 py-1 text-xs font-semibold rounded-full mr-3"
|
|
:class="{
|
|
'bg-blue-100 text-blue-700 dark:bg-blue-700 dark:text-blue-100': task.task_type === 'import',
|
|
'bg-purple-100 text-purple-700 dark:bg-purple-700 dark:text-purple-100': task.task_type === 'test_run'
|
|
}"
|
|
x-text="task.task_type === 'import' ? 'Import' : 'Test Run'">
|
|
</span>
|
|
<div>
|
|
<p class="font-medium text-gray-800 dark:text-gray-200" x-text="task.description"></p>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
Started by <span x-text="task.triggered_by || 'system'"></span>
|
|
at <span x-text="task.started_at ? new Date(task.started_at).toLocaleTimeString() : 'N/A'"></span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="text-right">
|
|
<p class="text-lg font-bold text-yellow-600 dark:text-yellow-400" x-text="formatDuration(task.duration_seconds)"></p>
|
|
<p class="text-xs text-gray-500">elapsed</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Task Type Stats -->
|
|
<div class="grid gap-6 mb-8 md:grid-cols-2">
|
|
<!-- Import Jobs Stats -->
|
|
<div class="p-6 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
|
<h4 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200 flex items-center">
|
|
<span x-html="$icon('cube', 'w-5 h-5 mr-2 text-blue-500')"></span>
|
|
Import Jobs
|
|
</h4>
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div class="text-center p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
|
<p class="text-2xl font-bold text-gray-700 dark:text-gray-200" x-text="stats.import_jobs?.total || 0"></p>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">Total</p>
|
|
</div>
|
|
<div class="text-center p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
|
<p class="text-2xl font-bold text-yellow-600" x-text="stats.import_jobs?.running || 0"></p>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">Running</p>
|
|
</div>
|
|
<div class="text-center p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
|
<p class="text-2xl font-bold text-green-600" x-text="stats.import_jobs?.completed || 0"></p>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">Completed</p>
|
|
</div>
|
|
<div class="text-center p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
|
<p class="text-2xl font-bold text-red-600" x-text="stats.import_jobs?.failed || 0"></p>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">Failed</p>
|
|
</div>
|
|
</div>
|
|
<div class="mt-4 text-center">
|
|
<a href="/admin/imports" class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400">
|
|
View Import Jobs →
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Test Runs Stats -->
|
|
<div class="p-6 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
|
<h4 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200 flex items-center">
|
|
<span x-html="$icon('beaker', 'w-5 h-5 mr-2 text-purple-500')"></span>
|
|
Test Runs
|
|
</h4>
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div class="text-center p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
|
<p class="text-2xl font-bold text-gray-700 dark:text-gray-200" x-text="stats.test_runs?.total || 0"></p>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">Total</p>
|
|
</div>
|
|
<div class="text-center p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
|
<p class="text-2xl font-bold text-yellow-600" x-text="stats.test_runs?.running || 0"></p>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">Running</p>
|
|
</div>
|
|
<div class="text-center p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
|
<p class="text-2xl font-bold text-green-600" x-text="stats.test_runs?.completed || 0"></p>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">Passed</p>
|
|
</div>
|
|
<div class="text-center p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
|
<p class="text-2xl font-bold text-red-600" x-text="stats.test_runs?.failed || 0"></p>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">Failed</p>
|
|
</div>
|
|
</div>
|
|
<div class="mt-4 text-center">
|
|
<a href="/admin/testing" class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400">
|
|
View Test Dashboard →
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filter Tabs -->
|
|
<div class="mb-4">
|
|
<div class="flex space-x-2">
|
|
<button @click="filterType = null; loadTasks()"
|
|
:class="filterType === null ? 'bg-purple-600 text-white' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300'"
|
|
class="px-4 py-2 text-sm font-medium rounded-lg border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700">
|
|
All Tasks
|
|
</button>
|
|
<button @click="filterType = 'import'; loadTasks()"
|
|
:class="filterType === 'import' ? 'bg-blue-600 text-white' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300'"
|
|
class="px-4 py-2 text-sm font-medium rounded-lg border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700">
|
|
Imports
|
|
</button>
|
|
<button @click="filterType = 'test_run'; loadTasks()"
|
|
:class="filterType === 'test_run' ? 'bg-purple-600 text-white' : 'bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300'"
|
|
class="px-4 py-2 text-sm font-medium rounded-lg border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700">
|
|
Test Runs
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tasks Table -->
|
|
<div class="p-6 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
|
<h4 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
|
Recent Tasks
|
|
</h4>
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full whitespace-nowrap">
|
|
<thead>
|
|
<tr class="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase border-b dark:border-gray-700 bg-gray-50 dark:text-gray-400 dark:bg-gray-800">
|
|
<th class="px-4 py-3">Type</th>
|
|
<th class="px-4 py-3">Description</th>
|
|
<th class="px-4 py-3">Started</th>
|
|
<th class="px-4 py-3">Duration</th>
|
|
<th class="px-4 py-3">Triggered By</th>
|
|
<th class="px-4 py-3">Status</th>
|
|
<th class="px-4 py-3">Celery</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800">
|
|
<template x-for="task in tasks" :key="task.task_type + '-' + task.id">
|
|
<tr class="text-gray-700 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700">
|
|
<td class="px-4 py-3">
|
|
<span class="px-2 py-1 text-xs font-semibold rounded-full"
|
|
:class="{
|
|
'bg-blue-100 text-blue-700 dark:bg-blue-700 dark:text-blue-100': task.task_type === 'import',
|
|
'bg-purple-100 text-purple-700 dark:bg-purple-700 dark:text-purple-100': task.task_type === 'test_run'
|
|
}"
|
|
x-text="task.task_type === 'import' ? 'Import' : 'Test Run'">
|
|
</span>
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<p class="font-medium truncate max-w-xs" x-text="task.description"></p>
|
|
<p x-show="task.error_message" class="text-xs text-red-500 truncate max-w-xs" x-text="task.error_message"></p>
|
|
</td>
|
|
<td class="px-4 py-3 text-sm" x-text="task.started_at ? new Date(task.started_at).toLocaleString() : 'N/A'"></td>
|
|
<td class="px-4 py-3 text-sm" x-text="formatDuration(task.duration_seconds)"></td>
|
|
<td class="px-4 py-3 text-sm" x-text="task.triggered_by || 'system'"></td>
|
|
<td class="px-4 py-3">
|
|
<span class="px-2 py-1 text-xs font-semibold rounded-full"
|
|
:class="{
|
|
'bg-green-100 text-green-700 dark:bg-green-700 dark:text-green-100': task.status === 'completed' || task.status === 'passed',
|
|
'bg-yellow-100 text-yellow-700 dark:bg-yellow-700 dark:text-yellow-100': task.status === 'running' || task.status === 'processing' || task.status === 'pending',
|
|
'bg-red-100 text-red-700 dark:bg-red-700 dark:text-red-100': task.status === 'failed' || task.status === 'error',
|
|
'bg-orange-100 text-orange-700 dark:bg-orange-700 dark:text-orange-100': task.status === 'completed_with_errors',
|
|
'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-100': !['completed', 'passed', 'running', 'processing', 'pending', 'failed', 'error', 'completed_with_errors'].includes(task.status)
|
|
}"
|
|
x-text="task.status">
|
|
</span>
|
|
</td>
|
|
<td class="px-4 py-3 text-sm">
|
|
<template x-if="task.celery_task_id">
|
|
<a :href="'{{ flower_url }}/task/' + task.celery_task_id"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="text-purple-600 hover:text-purple-800 dark:text-purple-400 dark:hover:text-purple-300 underline"
|
|
title="View in Flower">
|
|
<span x-html="$icon('external-link', 'w-4 h-4 inline')"></span>
|
|
<span x-text="task.celery_task_id.substring(0, 8) + '...'"></span>
|
|
</a>
|
|
</template>
|
|
<template x-if="!task.celery_task_id">
|
|
<span class="text-gray-400 text-xs">-</span>
|
|
</template>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<template x-if="tasks.length === 0">
|
|
<div class="text-center py-8 text-gray-500 dark:text-gray-400">
|
|
<span x-html="$icon('collection', 'w-12 h-12 mx-auto mb-2 text-gray-400')"></span>
|
|
<p>No tasks found</p>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|