Remove UI/UX zip file and add comprehensive scheduler functionality with APScheduler integration, cron support, and detailed documentation

This commit is contained in:
Bruno Charest 2025-12-04 16:01:10 -05:00
parent 21bee026ff
commit 28276f326b
9 changed files with 3306 additions and 99 deletions

Binary file not shown.

109
README.md
View File

@ -27,6 +27,14 @@ Une application moderne et professionnelle pour la gestion automatisée d'homela
- **Actions Rapides** : Upgrade, Reboot, Health-check, Backup en un clic
- **Groupes Ansible** : Sélection des cibles par groupe (proxmox, lab, prod, etc.)
### Planificateur (Scheduler)
- **Planification de Playbooks** : Exécution automatique des playbooks (unique ou récurrente)
- **Interface Multi-étapes** : Création intuitive de schedules en 3 étapes
- **Vue Liste/Calendrier** : Visualisation des schedules planifiés
- **Expression Cron** : Support des expressions cron personnalisées
- **Historique d'exécution** : Traçabilité complète de chaque run
- **Intégration Dashboard** : Widget des prochaines exécutions
## 🛠️ Technologies Utilisées
### Frontend
@ -41,6 +49,8 @@ Une application moderne et professionnelle pour la gestion automatisée d'homela
- **Pydantic** : Validation de données
- **WebSockets** : Communication temps réel
- **Uvicorn** : Serveur ASGI performant
- **APScheduler** : Planification de tâches en arrière-plan
- **Croniter** : Parsing d'expressions cron
## 📁 Structure du Projet
@ -53,6 +63,7 @@ homelab-automation-api-v2/
│ └── requirements.txt # Dépendances Python
├── ansible/
│ ├── ansible.cfg # Configuration Ansible
│ ├── .host_status.json # Cache JSON de l'état des hôtes
│ ├── inventory/
│ │ └── hosts.yml # Inventaire des hôtes
│ ├── group_vars/
@ -62,9 +73,40 @@ homelab-automation-api-v2/
│ ├── vm-reboot.yml # Redémarrage des hôtes
│ ├── health-check.yml # Vérification de santé
│ └── backup-config.yml # Sauvegarde de configuration
├── tasks_logs/
│ ├── .bootstrap_status.json # État du bootstrap Ansible/SSH par hôte
│ ├── .schedule_runs.json # Historique des exécutions de schedules
│ └── 2025/
│ └── 12/
│ └── *.json # Logs détaillés des tâches/schedules par date
├── test_schedule.json # Exemple de définition de schedule
└── README.md # Documentation
```
### Fichiers JSON requis
Les fichiers JSON suivants sont utilisés par l'application pour stocker l'état et l'historique des opérations :
- **`ansible/.host_status.json`**
- Contient l'état courant des hôtes connus (reachability, dernier health-check, etc.).
- Utilisé par l'API et le dashboard pour afficher rapidement le statut des machines sans relancer un scan complet.
- **`tasks_logs/.bootstrap_status.json`**
- Mémorise l'état du *bootstrap* Ansible/SSH par hôte (succès, échec, dernier message).
- Permet au dashboard d'indiquer si un hôte est prêt pour l'exécution de playbooks.
- **`tasks_logs/.schedule_runs.json`**
- Journalise les exécutions des *schedules* (id du schedule, heure de run, résultat, durée, message d'erreur éventuel).
- Sert de source de vérité pour l'historique affiché dans l'interface Planificateur.
- **`tasks_logs/<année>/<mois>/*.json`**
- Fichiers horodatés contenant les logs détaillés de chaque exécution de tâche/schedule.
- Permettent de tracer finement ce qui s'est passé pour chaque run (playbook lancé, cible, sortie Ansible, statut).
- **`test_schedule.json`**
- Exemple de définition de schedule utilisé pour les tests et la validation de l'API de planification.
- Peut servir de modèle pour comprendre la structure JSON attendue lors de la création de nouveaux schedules.
## 🚀 Installation et Lancement
### Prérequis
@ -175,6 +217,19 @@ curl -H "X-API-Key: dev-key-12345" http://localhost:8000/api/hosts
- `POST /api/ansible/bootstrap` - Bootstrap un hôte pour Ansible (crée user, SSH, sudo, Python)
- `GET /api/ansible/ssh-config` - Diagnostic de la configuration SSH
**Planificateur (Schedules)**
- `GET /api/schedules` - Liste tous les schedules
- `POST /api/schedules` - Crée un nouveau schedule
- `GET /api/schedules/{id}` - Récupère un schedule spécifique
- `PUT /api/schedules/{id}` - Met à jour un schedule
- `DELETE /api/schedules/{id}` - Supprime un schedule
- `POST /api/schedules/{id}/run` - Exécution forcée immédiate
- `POST /api/schedules/{id}/pause` - Met en pause un schedule
- `POST /api/schedules/{id}/resume` - Reprend un schedule
- `GET /api/schedules/{id}/runs` - Historique des exécutions
- `GET /api/schedules/stats` - Statistiques globales
- `POST /api/schedules/validate-cron` - Valide une expression cron
#### Exemples d'utilisation Ansible
**Lister les playbooks disponibles :**
@ -214,6 +269,60 @@ curl -X POST -H "X-API-Key: dev-key-12345" -H "Content-Type: application/json" \
http://localhost:8000/api/ansible/adhoc
```
#### Exemples d'utilisation du Planificateur
**Créer un schedule quotidien :**
```bash
curl -X POST -H "X-API-Key: dev-key-12345" -H "Content-Type: application/json" \
-d '{
"name": "Backup quotidien",
"playbook": "backup-config.yml",
"target": "all",
"schedule_type": "recurring",
"recurrence": {"type": "daily", "time": "02:00"},
"tags": ["Backup", "Production"]
}' \
http://localhost:8000/api/schedules
```
**Créer un schedule hebdomadaire (lundi et vendredi) :**
```bash
curl -X POST -H "X-API-Key: dev-key-12345" -H "Content-Type: application/json" \
-d '{
"name": "Health check bi-hebdo",
"playbook": "health-check.yml",
"target": "proxmox",
"schedule_type": "recurring",
"recurrence": {"type": "weekly", "time": "08:00", "days": [1, 5]}
}' \
http://localhost:8000/api/schedules
```
**Créer un schedule avec expression cron :**
```bash
curl -X POST -H "X-API-Key: dev-key-12345" -H "Content-Type: application/json" \
-d '{
"name": "Maintenance mensuelle",
"playbook": "vm-upgrade.yml",
"target": "lab",
"schedule_type": "recurring",
"recurrence": {"type": "custom", "cron_expression": "0 3 1 * *"}
}' \
http://localhost:8000/api/schedules
```
**Lancer un schedule immédiatement :**
```bash
curl -X POST -H "X-API-Key: dev-key-12345" \
http://localhost:8000/api/schedules/{schedule_id}/run
```
**Voir l'historique des exécutions :**
```bash
curl -H "X-API-Key: dev-key-12345" \
http://localhost:8000/api/schedules/{schedule_id}/runs
```
### Documentation API
- **Swagger UI** : `http://localhost:8000/api/docs`

View File

@ -0,0 +1,3 @@
{
"hosts": {}
}

File diff suppressed because it is too large Load Diff

View File

@ -1273,6 +1273,257 @@
border-color: #d1d5db;
color: #111827;
}
/* ===== SCHEDULER PAGE STYLES ===== */
.schedule-card {
background: rgba(42, 42, 42, 0.4);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 16px 20px;
transition: all 0.2s ease;
}
.schedule-card:hover {
background: rgba(42, 42, 42, 0.7);
border-color: rgba(124, 58, 237, 0.3);
transform: translateX(4px);
}
.schedule-card.paused {
opacity: 0.7;
border-left: 3px solid #f59e0b;
}
.schedule-card.active {
border-left: 3px solid #10b981;
}
.schedule-status-chip {
font-size: 0.7rem;
padding: 2px 8px;
border-radius: 9999px;
font-weight: 500;
}
.schedule-status-chip.active {
background-color: rgba(16, 185, 129, 0.2);
color: #10b981;
}
.schedule-status-chip.paused {
background-color: rgba(245, 158, 11, 0.2);
color: #f59e0b;
}
.schedule-status-chip.running {
background-color: rgba(59, 130, 246, 0.2);
color: #3b82f6;
}
.schedule-status-chip.success {
background-color: rgba(16, 185, 129, 0.2);
color: #10b981;
}
.schedule-status-chip.failed {
background-color: rgba(239, 68, 68, 0.2);
color: #ef4444;
}
.schedule-status-chip.scheduled {
background-color: rgba(124, 58, 237, 0.2);
color: #a78bfa;
}
.schedule-tag {
font-size: 0.65rem;
padding: 2px 6px;
border-radius: 4px;
background-color: rgba(107, 114, 128, 0.3);
color: #9ca3af;
}
.schedule-action-btn {
padding: 6px 10px;
border-radius: 6px;
font-size: 0.75rem;
transition: all 0.15s ease;
}
.schedule-action-btn:hover {
transform: scale(1.05);
}
.schedule-action-btn.run {
background: rgba(16, 185, 129, 0.2);
color: #10b981;
}
.schedule-action-btn.run:hover {
background: rgba(16, 185, 129, 0.4);
}
.schedule-action-btn.pause {
background: rgba(245, 158, 11, 0.2);
color: #f59e0b;
}
.schedule-action-btn.pause:hover {
background: rgba(245, 158, 11, 0.4);
}
.schedule-action-btn.edit {
background: rgba(59, 130, 246, 0.2);
color: #60a5fa;
}
.schedule-action-btn.edit:hover {
background: rgba(59, 130, 246, 0.4);
}
.schedule-action-btn.delete {
background: rgba(239, 68, 68, 0.2);
color: #f87171;
}
.schedule-action-btn.delete:hover {
background: rgba(239, 68, 68, 0.4);
}
.schedule-filter-btn.active {
background-color: #7c3aed !important;
color: white !important;
}
/* Calendar styles */
.schedule-calendar-day {
min-height: 80px;
background: rgba(42, 42, 42, 0.3);
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 8px;
padding: 8px;
transition: all 0.2s ease;
}
.schedule-calendar-day:hover {
background: rgba(42, 42, 42, 0.6);
border-color: rgba(124, 58, 237, 0.3);
}
.schedule-calendar-day.today {
border-color: #7c3aed;
background: rgba(124, 58, 237, 0.1);
}
.schedule-calendar-day.other-month {
opacity: 0.4;
}
.schedule-calendar-event {
font-size: 0.65rem;
padding: 2px 4px;
border-radius: 4px;
margin-top: 2px;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.schedule-calendar-event.success {
background-color: rgba(16, 185, 129, 0.3);
color: #10b981;
}
.schedule-calendar-event.failed {
background-color: rgba(239, 68, 68, 0.3);
color: #ef4444;
}
.schedule-calendar-event.scheduled {
background-color: rgba(59, 130, 246, 0.3);
color: #60a5fa;
}
/* Modal multi-step */
.schedule-modal-step {
display: none;
}
.schedule-modal-step.active {
display: block;
animation: fadeIn 0.3s ease;
}
.schedule-step-indicator {
display: flex;
justify-content: center;
gap: 8px;
margin-bottom: 24px;
}
.schedule-step-dot {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.875rem;
font-weight: 600;
background: rgba(107, 114, 128, 0.3);
color: #9ca3af;
transition: all 0.3s ease;
}
.schedule-step-dot.active {
background: #7c3aed;
color: white;
}
.schedule-step-dot.completed {
background: #10b981;
color: white;
}
.schedule-step-connector {
width: 40px;
height: 2px;
background: rgba(107, 114, 128, 0.3);
align-self: center;
}
.schedule-step-connector.active {
background: #7c3aed;
}
/* Recurrence preview */
.recurrence-preview {
background: rgba(124, 58, 237, 0.1);
border: 1px solid rgba(124, 58, 237, 0.3);
border-radius: 8px;
padding: 12px 16px;
font-size: 0.875rem;
}
/* Light theme overrides */
body.light-theme .schedule-card {
background: rgba(255, 255, 255, 0.6);
border-color: #d1d5db;
}
body.light-theme .schedule-card:hover {
background: rgba(255, 255, 255, 0.9);
}
body.light-theme .schedule-calendar-day {
background: rgba(255, 255, 255, 0.6);
border-color: #e5e7eb;
}
body.light-theme .schedule-calendar-day:hover {
background: rgba(255, 255, 255, 0.9);
}
</style>
</head>
<body>
@ -1291,6 +1542,7 @@
<a href="#" data-page="hosts" class="nav-link text-gray-300 hover:text-white transition-colors">Hosts</a>
<a href="#" data-page="playbooks" class="nav-link text-gray-300 hover:text-white transition-colors">Playbooks</a>
<a href="#" data-page="tasks" class="nav-link text-gray-300 hover:text-white transition-colors">Tasks</a>
<a href="#" data-page="schedules" class="nav-link text-gray-300 hover:text-white transition-colors">Schedules</a>
<a href="#" data-page="logs" class="nav-link text-gray-300 hover:text-white transition-colors">Logs</a>
<a href="#" data-page="help" class="nav-link text-gray-300 hover:text-white transition-colors">Aide</a>
<button id="theme-toggle" class="p-2 rounded-lg bg-gray-800 hover:bg-gray-700 transition-colors">
@ -1423,6 +1675,41 @@
</button>
</div>
</div>
<!-- Schedules Widget -->
<div class="glass-card p-6 fade-in">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold">
<i class="fas fa-calendar-alt text-purple-400 mr-2"></i>Planificateur
</h3>
<a href="#" data-page="schedules" class="nav-link text-purple-400 text-sm hover:underline">Voir tout →</a>
</div>
<!-- Stats mini -->
<div class="grid grid-cols-3 gap-2 mb-4">
<div class="text-center p-2 bg-gray-800/50 rounded">
<div class="text-lg font-bold text-green-400" id="dashboard-schedules-active">0</div>
<div class="text-xs text-gray-500">Actifs</div>
</div>
<div class="text-center p-2 bg-gray-800/50 rounded">
<div class="text-lg font-bold text-blue-400" id="dashboard-schedules-next">--</div>
<div class="text-xs text-gray-500">Prochaine</div>
</div>
<div class="text-center p-2 bg-gray-800/50 rounded">
<div class="text-lg font-bold text-red-400" id="dashboard-schedules-failures">0</div>
<div class="text-xs text-gray-500">Échecs 24h</div>
</div>
</div>
<!-- Next executions -->
<div id="dashboard-upcoming-schedules" class="space-y-2 text-sm">
<p class="text-gray-500 text-center py-4">Chargement...</p>
</div>
<button onclick="showCreateScheduleModal()" class="w-full mt-4 p-2 bg-purple-600 hover:bg-purple-500 rounded-lg text-sm transition-colors">
<i class="fas fa-plus mr-2"></i>Nouveau schedule
</button>
</div>
</div>
</div>
</div>
@ -1702,6 +1989,141 @@
</section>
<!-- END PAGE: TASKS -->
<!-- ==================== PAGE: SCHEDULES ==================== -->
<section id="page-schedules" class="page-section">
<div class="pt-24 pb-16 min-h-screen bg-gradient-to-b from-gray-900 to-black">
<div class="max-w-7xl mx-auto px-6">
<!-- Header -->
<div class="text-center mb-8">
<h1 class="text-4xl font-bold mb-4 gradient-text">
<i class="fas fa-calendar-alt mr-3"></i>Planificateur des Playbooks
</h1>
<p class="text-gray-400 max-w-2xl mx-auto">Planifiez et orchestrez vos playbooks dans le temps - Exécutions automatiques ponctuelles ou récurrentes</p>
</div>
<!-- Actions Header -->
<div class="flex flex-col lg:flex-row items-start lg:items-center justify-between gap-4 mb-6">
<div class="flex items-center gap-4">
<button onclick="showCreateScheduleModal()" class="btn-primary">
<i class="fas fa-plus mr-2"></i>Nouveau Schedule
</button>
<button onclick="dashboard.refreshSchedules()" class="px-4 py-2 bg-gray-700 rounded-lg hover:bg-gray-600 transition-colors">
<i class="fas fa-sync-alt mr-2"></i>Rafraîchir
</button>
</div>
<div class="flex items-center gap-3">
<select id="schedule-view-toggle" onchange="dashboard.toggleScheduleView(this.value)" class="px-4 py-2 bg-gray-700 rounded-lg border border-gray-600 text-sm">
<option value="list">Vue : Liste</option>
<option value="calendar">Vue : Calendrier</option>
</select>
</div>
</div>
<!-- Stats Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
<div class="glass-card p-4 text-center">
<div class="text-2xl font-bold text-green-400" id="schedules-active-count">0</div>
<div class="text-sm text-gray-400">Schedules actifs</div>
</div>
<div class="glass-card p-4 text-center">
<div class="text-2xl font-bold text-orange-400" id="schedules-paused-count">0</div>
<div class="text-sm text-gray-400">En pause</div>
</div>
<div class="glass-card p-4 text-center">
<div class="text-2xl font-bold text-blue-400" id="schedules-next-run">--:--</div>
<div class="text-sm text-gray-400">Prochaine exécution</div>
</div>
<div class="glass-card p-4 text-center">
<div class="text-2xl font-bold text-red-400" id="schedules-failures-24h">0</div>
<div class="text-sm text-gray-400">Échecs 24h</div>
</div>
</div>
<!-- Filters -->
<div class="glass-card p-4 mb-6">
<div class="flex flex-wrap items-center gap-4">
<div class="flex items-center gap-2">
<span class="text-sm text-gray-400"><i class="fas fa-filter mr-1"></i>Filtres:</span>
<button onclick="dashboard.filterSchedules('all')" class="schedule-filter-btn px-3 py-1.5 text-xs rounded-lg bg-purple-600 text-white" data-filter="all">
Tous
</button>
<button onclick="dashboard.filterSchedules('active')" class="schedule-filter-btn px-3 py-1.5 text-xs rounded-lg bg-gray-700 text-gray-300 hover:bg-gray-600" data-filter="active">
Actifs
</button>
<button onclick="dashboard.filterSchedules('paused')" class="schedule-filter-btn px-3 py-1.5 text-xs rounded-lg bg-gray-700 text-gray-300 hover:bg-gray-600" data-filter="paused">
En pause
</button>
</div>
<div class="flex-1"></div>
<div class="relative">
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-500"></i>
<input type="text" id="schedule-search" placeholder="Rechercher..."
class="pl-10 pr-4 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm w-64"
oninput="dashboard.searchSchedules(this.value)">
</div>
</div>
</div>
<!-- List View -->
<div id="schedules-list-view" class="glass-card p-6">
<div id="schedules-list" class="space-y-3">
<!-- Schedules will be populated by JavaScript -->
</div>
<!-- Empty State -->
<div id="schedules-empty" class="hidden text-center py-16">
<div class="w-20 h-20 mx-auto mb-6 bg-purple-600/20 rounded-full flex items-center justify-center">
<i class="fas fa-calendar-plus text-4xl text-purple-400"></i>
</div>
<h3 class="text-xl font-semibold mb-2">Aucun schedule configuré</h3>
<p class="text-gray-400 mb-6 max-w-md mx-auto">
Créez votre premier schedule pour automatiser l'exécution de vos playbooks Ansible.
</p>
<button onclick="showCreateScheduleModal()" class="btn-primary">
<i class="fas fa-plus mr-2"></i>Créer votre premier schedule
</button>
</div>
</div>
<!-- Calendar View (Hidden by default) -->
<div id="schedules-calendar-view" class="glass-card p-6 hidden">
<div class="flex items-center justify-between mb-6">
<button onclick="dashboard.prevCalendarMonth()" class="p-2 bg-gray-700 rounded-lg hover:bg-gray-600">
<i class="fas fa-chevron-left"></i>
</button>
<h3 id="schedule-calendar-title" class="text-xl font-semibold">Décembre 2025</h3>
<button onclick="dashboard.nextCalendarMonth()" class="p-2 bg-gray-700 rounded-lg hover:bg-gray-600">
<i class="fas fa-chevron-right"></i>
</button>
</div>
<div class="grid grid-cols-7 gap-1 mb-2">
<div class="text-center text-xs text-gray-500 py-2">Lun</div>
<div class="text-center text-xs text-gray-500 py-2">Mar</div>
<div class="text-center text-xs text-gray-500 py-2">Mer</div>
<div class="text-center text-xs text-gray-500 py-2">Jeu</div>
<div class="text-center text-xs text-gray-500 py-2">Ven</div>
<div class="text-center text-xs text-gray-500 py-2">Sam</div>
<div class="text-center text-xs text-gray-500 py-2">Dim</div>
</div>
<div id="schedule-calendar-grid" class="grid grid-cols-7 gap-1">
<!-- Calendar days will be populated by JavaScript -->
</div>
</div>
<!-- Upcoming Executions -->
<div class="glass-card p-6 mt-6">
<h3 class="text-lg font-semibold mb-4">
<i class="fas fa-clock text-blue-400 mr-2"></i>Prochaines exécutions
</h3>
<div id="schedules-upcoming" class="space-y-2">
<!-- Upcoming executions will be populated by JavaScript -->
</div>
</div>
</div>
</div>
</section>
<!-- END PAGE: SCHEDULES -->
<!-- ==================== PAGE: LOGS ==================== -->
<section id="page-logs" class="page-section">
<div id="logs" class="pt-24 pb-16 min-h-screen bg-gradient-to-b from-black to-gray-900">
@ -2385,6 +2807,16 @@ homelab-automation/
</div>
<script src="/static/main.js"></script>
<script>
// Fallback global helper in case main.js didn't expose it
if (typeof window.showCreateScheduleModal === 'undefined') {
window.showCreateScheduleModal = function(prefilledPlaybook = null) {
if (window.dashboard && typeof dashboard.showCreateScheduleModal === 'function') {
dashboard.showCreateScheduleModal(prefilledPlaybook);
}
};
}
</script>
<!-- Navigation & Help Page Scripts -->
<script>

File diff suppressed because it is too large Load Diff

View File

@ -7,4 +7,7 @@ websockets>=14.0
aiofiles>=24.1.0
python-dotenv>=1.0.1
requests>=2.32.0
httpx>=0.28.0
httpx>=0.28.0
apscheduler>=3.10.0
croniter>=2.0.0
pytz>=2024.1

View File

@ -0,0 +1,268 @@
{
"runs": [
{
"id": "run_9eaff32da049",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 24,
"started_at": "2025-12-04 20:30:00.003167+00:00",
"finished_at": "2025-12-04 20:30:23.731178+00:00",
"status": "success",
"duration_seconds": 23.703570538998974,
"hosts_impacted": 15,
"error_message": null,
"retry_attempt": 0
},
{
"id": "run_17e5d474fa3b",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 23,
"started_at": "2025-12-04 20:25:00.003921+00:00",
"finished_at": "2025-12-04 20:25:33.861123+00:00",
"status": "success",
"duration_seconds": 33.836465951999344,
"hosts_impacted": 15,
"error_message": null,
"retry_attempt": 0
},
{
"id": "run_ac3b635e2ca0",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 22,
"started_at": "2025-12-04 20:20:00.002730+00:00",
"finished_at": "2025-12-04 20:20:24.021329+00:00",
"status": "success",
"duration_seconds": 23.990758482001183,
"hosts_impacted": 15,
"error_message": null,
"retry_attempt": 0
},
{
"id": "run_ae3840e2d42a",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 21,
"started_at": "2025-12-04 20:15:00.003766+00:00",
"finished_at": "2025-12-04 20:15:30.504433+00:00",
"status": "success",
"duration_seconds": 30.471468816998822,
"hosts_impacted": 15,
"error_message": null,
"retry_attempt": 0
},
{
"id": "run_c747bc5687ab",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 20,
"started_at": "2025-12-04 20:10:00.003667+00:00",
"finished_at": "2025-12-04 20:10:23.552886+00:00",
"status": "success",
"duration_seconds": 23.524676467999598,
"hosts_impacted": 15,
"error_message": null,
"retry_attempt": 0
},
{
"id": "run_45014bc6cc03",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 19,
"started_at": "2025-12-04 20:05:00.002690+00:00",
"finished_at": "2025-12-04 20:05:23.450713+00:00",
"status": "success",
"duration_seconds": 23.4251933380001,
"hosts_impacted": 15,
"error_message": null,
"retry_attempt": 0
},
{
"id": "run_e44e428d9a2c",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 18,
"started_at": "2025-12-04 20:00:00.003643+00:00",
"finished_at": "2025-12-04 20:00:23.328830+00:00",
"status": "success",
"duration_seconds": 23.302303992000816,
"hosts_impacted": 15,
"error_message": null,
"retry_attempt": 0
},
{
"id": "run_a25301c67cc5",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 17,
"started_at": "2025-12-04 19:55:00.003143+00:00",
"finished_at": "2025-12-04 19:55:23.603376+00:00",
"status": "success",
"duration_seconds": 23.577402816999893,
"hosts_impacted": 15,
"error_message": null,
"retry_attempt": 0
},
{
"id": "run_565da9652657",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 16,
"started_at": "2025-12-04 19:50:00.003353+00:00",
"finished_at": "2025-12-04 19:50:24.356543+00:00",
"status": "failed",
"duration_seconds": 24.328575002000434,
"hosts_impacted": 15,
"error_message": "",
"retry_attempt": 0
},
{
"id": "run_3b74b1e74163",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 15,
"started_at": "2025-12-04 19:45:00.002519+00:00",
"finished_at": "2025-12-04 19:45:23.751357+00:00",
"status": "success",
"duration_seconds": 23.722472203999132,
"hosts_impacted": 15,
"error_message": null,
"retry_attempt": 0
},
{
"id": "run_dbde0be5bc63",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 14,
"started_at": "2025-12-04 19:40:00.003007+00:00",
"finished_at": "2025-12-04 19:40:23.751729+00:00",
"status": "success",
"duration_seconds": 23.723020589999578,
"hosts_impacted": 15,
"error_message": null,
"retry_attempt": 0
},
{
"id": "run_15a1ad527d50",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 13,
"started_at": "2025-12-04 19:35:00.003399+00:00",
"finished_at": "2025-12-04 19:35:32.533102+00:00",
"status": "success",
"duration_seconds": 32.507498991999455,
"hosts_impacted": 15,
"error_message": null,
"retry_attempt": 0
},
{
"id": "run_be8bcb150d04",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 12,
"started_at": "2025-12-04 19:30:00.003063+00:00",
"finished_at": "2025-12-04 19:30:23.472387+00:00",
"status": "success",
"duration_seconds": 23.440744194000217,
"hosts_impacted": 15,
"error_message": null,
"retry_attempt": 0
},
{
"id": "run_f4d7d06f0c37",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 11,
"started_at": "2025-12-04 19:25:00.004160+00:00",
"finished_at": "2025-12-04 19:25:23.853468+00:00",
"status": "success",
"duration_seconds": 23.823963333001302,
"hosts_impacted": 15,
"error_message": null,
"retry_attempt": 0
},
{
"id": "run_66dc096ac544",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 10,
"started_at": "2025-12-04 19:20:00.003618+00:00",
"finished_at": "2025-12-04 19:20:24.884824+00:00",
"status": "success",
"duration_seconds": 24.857591686999513,
"hosts_impacted": 15,
"error_message": null,
"retry_attempt": 0
},
{
"id": "run_64b0ef374c3e",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 8,
"started_at": "2025-12-04 19:15:00.003439+00:00",
"finished_at": "2025-12-04 19:15:23.510149+00:00",
"status": "success",
"duration_seconds": 23.48368282699994,
"hosts_impacted": 15,
"error_message": null,
"retry_attempt": 0
},
{
"id": "run_2e7db2553a2b",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 7,
"started_at": "2025-12-04 19:10:00.003912+00:00",
"finished_at": "2025-12-04 19:10:23.758800+00:00",
"status": "success",
"duration_seconds": 23.731919878000554,
"hosts_impacted": 15,
"error_message": null,
"retry_attempt": 0
},
{
"id": "run_f11f2032c99d",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 6,
"started_at": "2025-12-04 19:05:00.005462+00:00",
"finished_at": "2025-12-04 19:05:27.950812+00:00",
"status": "success",
"duration_seconds": 27.921534645000065,
"hosts_impacted": 15,
"error_message": null,
"retry_attempt": 0
},
{
"id": "run_6654ede83db4",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 5,
"started_at": "2025-12-04 19:00:00.002793+00:00",
"finished_at": "2025-12-04 19:00:19.946667+00:00",
"status": "success",
"duration_seconds": 19.924723819000064,
"hosts_impacted": 15,
"error_message": null,
"retry_attempt": 0
},
{
"id": "run_98742a32df11",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 4,
"started_at": "2025-12-04 18:59:33.945907+00:00",
"finished_at": "2025-12-04 18:59:58.108811+00:00",
"status": "success",
"duration_seconds": 24.139778345000195,
"hosts_impacted": 15,
"error_message": null,
"retry_attempt": 0
},
{
"id": "run_079e0bef133a",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 3,
"started_at": "2025-12-04 18:55:00.002791+00:00",
"finished_at": "2025-12-04 18:55:39.881649+00:00",
"status": "failed",
"duration_seconds": 39.856552030000785,
"hosts_impacted": 15,
"error_message": "",
"retry_attempt": 0
},
{
"id": "run_719217dc687f",
"schedule_id": "sched_31a7ffb99bfd",
"task_id": 1,
"started_at": "2025-12-04 18:50:00.002822+00:00",
"finished_at": "2025-12-04 18:50:40.203643+00:00",
"status": "failed",
"duration_seconds": 40.181820916000106,
"hosts_impacted": 15,
"error_message": "",
"retry_attempt": 0
}
]
}

View File

@ -0,0 +1,38 @@
{
"schedules": [
{
"id": "sched_31a7ffb99bfd",
"name": "Health-check-5min",
"description": "Health-check-5min",
"playbook": "health-check.yml",
"target_type": "group",
"target": "all",
"extra_vars": null,
"schedule_type": "recurring",
"recurrence": {
"type": "custom",
"time": "02:00",
"days": null,
"day_of_month": null,
"cron_expression": "*/5 * * * *"
},
"timezone": "America/Montreal",
"start_at": null,
"end_at": null,
"next_run_at": "2025-12-04 15:35:00-05:00",
"last_run_at": "2025-12-04 20:30:00.003138+00:00",
"last_status": "success",
"enabled": false,
"retry_on_failure": 0,
"timeout": 3600,
"tags": [
"Test"
],
"run_count": 22,
"success_count": 19,
"failure_count": 3,
"created_at": "2025-12-04 18:45:18.318152+00:00",
"updated_at": "2025-12-04 20:30:29.181594+00:00"
}
]
}