job_history.html 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. {% extends "base.html" %}
  2. {% block title %}Historique — {{ job.name }}{% endblock %}
  3. {% block content %}
  4. <div class="mb-6 flex items-center gap-4">
  5. <a href="{{ url_for('jobs.index') }}" class="text-gray-400 hover:text-gray-600 text-sm">← Dashboard</a>
  6. <div>
  7. <h1 class="text-xl font-bold text-gray-900">{{ job.name }}</h1>
  8. {% if job.type == 'ynh_app' and job.config_json %}
  9. {% set _cfg = job.config_json | fromjson %}
  10. {% set _app_id = _cfg.get('app_id') %}
  11. {% if _app_id %}
  12. {% set _label = ynh_app_labels.get(_app_id, '') %}
  13. {% if _label and _label != _app_id %}
  14. <span class="text-xs text-gray-500">{{ _label }}</span>
  15. {% else %}
  16. <span class="font-mono text-xs text-gray-400">{{ _app_id }}</span>
  17. {% endif %}
  18. {% endif %}
  19. {% endif %}
  20. </div>
  21. <span class="bg-gray-100 text-gray-600 text-xs px-2 py-0.5 rounded font-mono">{{ job.type }}</span>
  22. <span class="text-gray-400 text-sm font-mono">{{ job.cron_expr }}</span>
  23. </div>
  24. <div class="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden">
  25. <div class="px-6 py-4 border-b border-gray-100">
  26. <h2 class="text-sm font-semibold text-gray-700">
  27. Historique des exécutions
  28. <span class="text-gray-400 font-normal">({{ runs | length }} entrées)</span>
  29. </h2>
  30. </div>
  31. {% if not runs %}
  32. <div class="px-6 py-10 text-center text-gray-400 text-sm">
  33. Aucune exécution enregistrée pour ce job.
  34. </div>
  35. {% else %}
  36. <div class="overflow-x-auto">
  37. <table class="w-full text-sm">
  38. <thead>
  39. <tr class="text-xs text-gray-500 uppercase tracking-wide bg-gray-50">
  40. <th class="px-6 py-3 text-left font-medium">Début</th>
  41. <th class="px-6 py-3 text-left font-medium">Fin</th>
  42. <th class="px-6 py-3 text-left font-medium">Durée</th>
  43. <th class="px-6 py-3 text-left font-medium">Statut</th>
  44. <th class="px-6 py-3 text-left font-medium">Archive</th>
  45. <th class="px-6 py-3 text-left font-medium">Taille</th>
  46. <th class="px-6 py-3 text-left font-medium">Log</th>
  47. <th class="px-6 py-3 text-left font-medium">Action</th>
  48. </tr>
  49. </thead>
  50. <tbody class="divide-y divide-gray-100">
  51. {% for run in runs %}
  52. <tr class="hover:bg-gray-50">
  53. <td class="px-6 py-3 text-xs text-gray-700 whitespace-nowrap">
  54. {{ run.started_at.strftime('%d/%m/%Y %H:%M:%S') if run.started_at else '—' }}
  55. </td>
  56. <td class="px-6 py-3 text-xs text-gray-500 whitespace-nowrap">
  57. {{ run.finished_at.strftime('%H:%M:%S') if run.finished_at else '—' }}
  58. </td>
  59. <td class="px-6 py-3 text-xs text-gray-500">
  60. {% if run.duration_seconds is not none %}
  61. {% if run.duration_seconds >= 60 %}
  62. {{ (run.duration_seconds // 60) }}min {{ run.duration_seconds % 60 }}s
  63. {% else %}
  64. {{ run.duration_seconds }}s
  65. {% endif %}
  66. {% else %}
  67. {% endif %}
  68. </td>
  69. <td class="px-6 py-3">
  70. {% if run.status == 'success' %}
  71. <span class="bg-green-100 text-green-700 text-xs font-medium px-2 py-0.5 rounded-full">✓ succès</span>
  72. {% elif run.status == 'warning' %}
  73. <span class="bg-amber-100 text-amber-700 text-xs font-medium px-2 py-0.5 rounded-full">⚠ transfert partiel</span>
  74. {% elif run.status == 'error' %}
  75. <span class="bg-red-100 text-red-700 text-xs font-medium px-2 py-0.5 rounded-full">✗ erreur</span>
  76. {% elif run.status == 'running' %}
  77. <span class="bg-blue-100 text-blue-700 text-xs font-medium px-2 py-0.5 rounded-full animate-pulse">⟳ en cours</span>
  78. {% else %}
  79. <span class="text-gray-400 text-xs">{{ run.status or '—' }}</span>
  80. {% endif %}
  81. </td>
  82. <td class="px-6 py-3 text-xs font-mono text-gray-600">
  83. {{ run.archive_name or '—' }}
  84. </td>
  85. <td class="px-6 py-3 text-xs text-gray-500">
  86. {{ run.size_human if run.size_bytes else '—' }}
  87. </td>
  88. <td class="px-6 py-3">
  89. {% if run.log_text %}
  90. <details>
  91. <summary class="cursor-pointer text-xs text-blue-600 hover:underline">Voir</summary>
  92. <pre class="mt-2 text-xs bg-gray-900 text-gray-100 p-3 rounded-lg overflow-x-auto max-w-xl whitespace-pre-wrap">{{ run.log_text }}</pre>
  93. </details>
  94. {% else %}
  95. <span class="text-gray-300 text-xs">—</span>
  96. {% endif %}
  97. </td>
  98. <td class="px-6 py-3">
  99. {% if run.status in ('success', 'warning') and run.archive_name %}
  100. <div class="flex flex-col gap-1">
  101. <a href="{{ url_for('jobs.archive_restore', archive_name=run.archive_name) }}"
  102. class="text-xs text-orange-600 hover:text-orange-800 hover:underline whitespace-nowrap">
  103. ↩ Restaurer
  104. </a>
  105. {% for jd in job.job_destinations if jd.dest_type == 'ssh' %}
  106. <form method="post" action="{{ url_for('dest.archive_transfer', archive_name=run.archive_name) }}">
  107. <input type="hidden" name="destination_id" value="{{ jd.dest_id }}">
  108. <button type="submit" class="text-xs text-blue-600 hover:text-blue-800 hover:underline whitespace-nowrap text-left">
  109. ↑ {{ jd.label }}
  110. </button>
  111. </form>
  112. {% endfor %}
  113. </div>
  114. {% else %}
  115. <span class="text-gray-300 text-xs">—</span>
  116. {% endif %}
  117. </td>
  118. </tr>
  119. {% endfor %}
  120. </tbody>
  121. </table>
  122. </div>
  123. {% endif %}
  124. </div>
  125. {% endblock %}