Remove UI/UX zip file and add comprehensive scheduler functionality with APScheduler integration, cron support, and detailed documentation
This commit is contained in:
parent
21bee026ff
commit
28276f326b
Binary file not shown.
109
README.md
109
README.md
@ -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`
|
||||
|
||||
3
ansible/.host_status.json
Normal file
3
ansible/.host_status.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"hosts": {}
|
||||
}
|
||||
1250
app/app_optimized.py
1250
app/app_optimized.py
File diff suppressed because it is too large
Load Diff
432
app/index.html
432
app/index.html
@ -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>
|
||||
|
||||
1300
app/main.js
1300
app/main.js
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
268
tasks_logs/.schedule_runs.json
Normal file
268
tasks_logs/.schedule_runs.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
38
tasks_logs/.schedules.json
Normal file
38
tasks_logs/.schedules.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user