Remove JSON-based ad-hoc history storage and migrate to SQLAlchemy database backend with improved duration parsing and comprehensive CRUD operations
This commit is contained in:
parent
ad3a8a5639
commit
123ca2cc08
52
alembic/versions/0002_add_schedule_columns.py
Normal file
52
alembic/versions/0002_add_schedule_columns.py
Normal file
@ -0,0 +1,52 @@
|
||||
"""Add missing columns to schedules table for full scheduler support
|
||||
|
||||
Revision ID: 0002_add_schedule_columns
|
||||
Revises: 0001_initial
|
||||
Create Date: 2025-12-05
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "0002_add_schedule_columns"
|
||||
down_revision = "0001_initial"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Ajouter les colonnes manquantes à la table schedules
|
||||
op.add_column("schedules", sa.Column("description", sa.Text(), nullable=True))
|
||||
op.add_column("schedules", sa.Column("target_type", sa.String(), nullable=True, server_default="group"))
|
||||
op.add_column("schedules", sa.Column("extra_vars", sa.JSON(), nullable=True))
|
||||
op.add_column("schedules", sa.Column("timezone", sa.String(), nullable=True, server_default="America/Montreal"))
|
||||
op.add_column("schedules", sa.Column("start_at", sa.DateTime(timezone=True), nullable=True))
|
||||
op.add_column("schedules", sa.Column("end_at", sa.DateTime(timezone=True), nullable=True))
|
||||
op.add_column("schedules", sa.Column("last_status", sa.String(), nullable=True, server_default="never"))
|
||||
op.add_column("schedules", sa.Column("retry_on_failure", sa.Integer(), nullable=True, server_default="0"))
|
||||
op.add_column("schedules", sa.Column("timeout", sa.Integer(), nullable=True, server_default="3600"))
|
||||
op.add_column("schedules", sa.Column("run_count", sa.Integer(), nullable=True, server_default="0"))
|
||||
op.add_column("schedules", sa.Column("success_count", sa.Integer(), nullable=True, server_default="0"))
|
||||
op.add_column("schedules", sa.Column("failure_count", sa.Integer(), nullable=True, server_default="0"))
|
||||
|
||||
# Ajouter hosts_impacted à schedule_runs
|
||||
op.add_column("schedule_runs", sa.Column("hosts_impacted", sa.Integer(), nullable=True, server_default="0"))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("schedule_runs", "hosts_impacted")
|
||||
op.drop_column("schedules", "failure_count")
|
||||
op.drop_column("schedules", "success_count")
|
||||
op.drop_column("schedules", "run_count")
|
||||
op.drop_column("schedules", "timeout")
|
||||
op.drop_column("schedules", "retry_on_failure")
|
||||
op.drop_column("schedules", "last_status")
|
||||
op.drop_column("schedules", "end_at")
|
||||
op.drop_column("schedules", "start_at")
|
||||
op.drop_column("schedules", "timezone")
|
||||
op.drop_column("schedules", "extra_vars")
|
||||
op.drop_column("schedules", "target_type")
|
||||
op.drop_column("schedules", "description")
|
||||
@ -1,3 +0,0 @@
|
||||
{
|
||||
"hosts": {}
|
||||
}
|
||||
1668
app/app_optimized.py
1668
app/app_optimized.py
File diff suppressed because it is too large
Load Diff
@ -35,6 +35,13 @@ class HostRepository:
|
||||
result = await self.session.execute(stmt)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_by_name(self, name: str, include_deleted: bool = False) -> Optional[Host]:
|
||||
stmt = select(Host).where(Host.name == name)
|
||||
if not include_deleted:
|
||||
stmt = stmt.where(Host.deleted_at.is_(None))
|
||||
result = await self.session.execute(stmt)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def create(self, *, id: str, name: str, ip_address: str, ansible_group: Optional[str] = None,
|
||||
status: str = "unknown", reachable: bool = False, last_seen: Optional[datetime] = None) -> Host:
|
||||
host = Host(
|
||||
|
||||
69
app/main.js
69
app/main.js
@ -245,6 +245,10 @@ class DashboardManager {
|
||||
// Tâche terminée - mettre à jour et rafraîchir les logs
|
||||
this.handleTaskCompleted(data.data);
|
||||
break;
|
||||
case 'task_cancelled':
|
||||
// Tâche annulée - mettre à jour l'UI
|
||||
this.handleTaskCancelled(data.data);
|
||||
break;
|
||||
case 'task_progress':
|
||||
// Mise à jour de progression - mettre à jour l'UI dynamiquement
|
||||
this.handleTaskProgress(data.data);
|
||||
@ -476,9 +480,13 @@ class DashboardManager {
|
||||
</div>
|
||||
<div class="flex flex-col space-y-1 ml-2">
|
||||
<button class="p-1.5 bg-gray-700 rounded hover:bg-gray-600 transition-colors"
|
||||
onclick="dashboard.viewTaskDetails(${task.id})" title="Voir les détails">
|
||||
onclick="dashboard.viewTaskDetails('${task.id}')" title="Voir les détails">
|
||||
<i class="fas fa-eye text-gray-300 text-xs"></i>
|
||||
</button>
|
||||
<button class="p-1.5 bg-red-700 rounded hover:bg-red-600 transition-colors"
|
||||
onclick="dashboard.cancelTask('${task.id}')" title="Annuler la tâche">
|
||||
<i class="fas fa-times text-white text-xs"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -566,6 +574,22 @@ class DashboardManager {
|
||||
);
|
||||
}
|
||||
|
||||
handleTaskCancelled(taskData) {
|
||||
console.log('Tâche annulée:', taskData);
|
||||
|
||||
// Retirer la tâche de la liste des tâches en cours
|
||||
this.tasks = this.tasks.filter(t => String(t.id) !== String(taskData.id));
|
||||
|
||||
// Mettre à jour l'UI
|
||||
this.updateRunningTasksUI(this.tasks.filter(t => t.status === 'running' || t.status === 'pending'));
|
||||
|
||||
// Rafraîchir les logs de tâches
|
||||
this.refreshTaskLogs();
|
||||
|
||||
// Notification
|
||||
this.showNotification('Tâche annulée', 'warning');
|
||||
}
|
||||
|
||||
async loadLogs() {
|
||||
try {
|
||||
const logsData = await this.apiCall('/api/logs');
|
||||
@ -4116,6 +4140,45 @@ class DashboardManager {
|
||||
await this.executeTask(action, task.host);
|
||||
}
|
||||
|
||||
async cancelTask(taskId) {
|
||||
if (!confirm('Êtes-vous sûr de vouloir annuler cette tâche ?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/tasks/${taskId}/cancel`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-API-Key': this.apiKey,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || 'Erreur lors de l\'annulation');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
this.showNotification(result.message || 'Tâche annulée avec succès', 'success');
|
||||
|
||||
// Mettre à jour la liste des tâches
|
||||
const task = this.tasks.find(t => String(t.id) === String(taskId));
|
||||
if (task) {
|
||||
task.status = 'cancelled';
|
||||
task.error = 'Tâche annulée par l\'utilisateur';
|
||||
}
|
||||
|
||||
// Rafraîchir l'affichage
|
||||
this.pollRunningTasks();
|
||||
this.renderTasks();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur annulation tâche:', error);
|
||||
this.showNotification(error.message || 'Erreur lors de l\'annulation de la tâche', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
showAdHocConsole() {
|
||||
console.log('Opening Ad-Hoc Console with:', {
|
||||
adhocHistory: this.adhocHistory,
|
||||
@ -4860,7 +4923,9 @@ class DashboardManager {
|
||||
command: formData.get('command'),
|
||||
module: formData.get('module'),
|
||||
become: formData.get('become') === 'on',
|
||||
timeout: parseInt(formData.get('timeout')) || 60
|
||||
timeout: parseInt(formData.get('timeout')) || 60,
|
||||
// catégorie d'historique choisie dans le select
|
||||
category: formData.get('save_category') || 'default'
|
||||
};
|
||||
|
||||
const resultDiv = document.getElementById('adhoc-result');
|
||||
|
||||
@ -33,12 +33,15 @@ DATABASE_URL = os.environ.get("DATABASE_URL", f"sqlite+aiosqlite:///{DEFAULT_DB_
|
||||
def _ensure_sqlite_dir(db_url: str) -> None:
|
||||
if not db_url.startswith("sqlite"):
|
||||
return
|
||||
parsed = urlparse(db_url.replace("sqlite+aiosqlite", "sqlite"))
|
||||
if parsed.scheme != "sqlite":
|
||||
return
|
||||
db_path = Path(parsed.path)
|
||||
if db_path.parent:
|
||||
db_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
# Extraire le chemin après sqlite+aiosqlite:///
|
||||
# Sur Windows, le chemin peut être C:\... donc on ne peut pas utiliser urlparse
|
||||
prefix = "sqlite+aiosqlite:///"
|
||||
if db_url.startswith(prefix):
|
||||
path_str = db_url[len(prefix):]
|
||||
# Sur Windows, le chemin peut commencer par une lettre de lecteur (C:)
|
||||
db_path = Path(path_str)
|
||||
if db_path.parent and str(db_path.parent) != ".":
|
||||
db_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
DEFAULT_DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
_ensure_sqlite_dir(DATABASE_URL)
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, String, Text
|
||||
from sqlalchemy import Boolean, DateTime, Integer, JSON, String, Text
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
@ -15,18 +15,30 @@ class Schedule(Base):
|
||||
|
||||
id: Mapped[str] = mapped_column(String, primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String, nullable=False)
|
||||
description: Mapped[Optional[str]] = mapped_column(Text)
|
||||
playbook: Mapped[str] = mapped_column(String, nullable=False)
|
||||
target_type: Mapped[Optional[str]] = mapped_column(String, default="group")
|
||||
target: Mapped[str] = mapped_column(String, nullable=False)
|
||||
extra_vars: Mapped[Optional[Dict[str, Any]]] = mapped_column(JSON)
|
||||
schedule_type: Mapped[str] = mapped_column(String, nullable=False)
|
||||
schedule_time: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
recurrence_type: Mapped[Optional[str]] = mapped_column(String)
|
||||
recurrence_time: Mapped[Optional[str]] = mapped_column(String)
|
||||
recurrence_days: Mapped[Optional[str]] = mapped_column(Text)
|
||||
cron_expression: Mapped[Optional[str]] = mapped_column(String)
|
||||
timezone: Mapped[Optional[str]] = mapped_column(String, default="America/Montreal")
|
||||
start_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
end_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
||||
tags: Mapped[Optional[str]] = mapped_column(Text)
|
||||
next_run: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
last_run: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
last_status: Mapped[Optional[str]] = mapped_column(String, default="never")
|
||||
retry_on_failure: Mapped[Optional[int]] = mapped_column(Integer, default=0)
|
||||
timeout: Mapped[Optional[int]] = mapped_column(Integer, default=3600)
|
||||
run_count: Mapped[Optional[int]] = mapped_column(Integer, default=0)
|
||||
success_count: Mapped[Optional[int]] = mapped_column(Integer, default=0)
|
||||
failure_count: Mapped[Optional[int]] = mapped_column(Integer, default=0)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now())
|
||||
deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
|
||||
@ -20,6 +20,7 @@ class ScheduleRun(Base):
|
||||
started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||
completed_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True))
|
||||
duration: Mapped[Optional[float]] = mapped_column(Float)
|
||||
hosts_impacted: Mapped[Optional[int]] = mapped_column(Integer, default=0)
|
||||
error_message: Mapped[Optional[str]] = mapped_column(Text)
|
||||
output: Mapped[Optional[str]] = mapped_column(Text)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
|
||||
BIN
data/homelab.db
BIN
data/homelab.db
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,79 +0,0 @@
|
||||
{
|
||||
"hosts": {
|
||||
"dev.lab.home": {
|
||||
"bootstrap_ok": true,
|
||||
"bootstrap_date": "2025-12-02T20:20:03.555927+00:00",
|
||||
"details": "Bootstrap réussi via API (user: automation)"
|
||||
},
|
||||
"media.labb.home": {
|
||||
"bootstrap_ok": true,
|
||||
"bootstrap_date": "2025-12-02T01:52:34.259129+00:00",
|
||||
"details": "Bootstrap réussi via API (user: automation)"
|
||||
},
|
||||
"ali2v.xeon.home": {
|
||||
"bootstrap_ok": true,
|
||||
"bootstrap_date": "2025-12-02T14:35:47.874004+00:00",
|
||||
"details": "Bootstrap réussi via API (user: automation)"
|
||||
},
|
||||
"raspi.4gb.home": {
|
||||
"bootstrap_ok": true,
|
||||
"bootstrap_date": "2025-12-02T16:09:22.961007+00:00",
|
||||
"details": "Bootstrap réussi via API (user: automation)"
|
||||
},
|
||||
"raspi.8gb.home": {
|
||||
"bootstrap_ok": true,
|
||||
"bootstrap_date": "2025-12-02T16:10:53.117121+00:00",
|
||||
"details": "Bootstrap réussi via API (user: automation)"
|
||||
},
|
||||
"orangepi.pc.home": {
|
||||
"bootstrap_ok": true,
|
||||
"bootstrap_date": "2025-12-02T16:11:47.008381+00:00",
|
||||
"details": "Bootstrap réussi via API (user: automation)"
|
||||
},
|
||||
"jump.point.home": {
|
||||
"bootstrap_ok": true,
|
||||
"bootstrap_date": "2025-12-02T18:56:57.635706+00:00",
|
||||
"details": "Bootstrap réussi via API (user: automation)"
|
||||
},
|
||||
"hp.nas.home": {
|
||||
"bootstrap_ok": true,
|
||||
"bootstrap_date": "2025-12-02T20:25:44.595352+00:00",
|
||||
"details": "Bootstrap réussi via API (user: automation)"
|
||||
},
|
||||
"hp2.i7.home": {
|
||||
"bootstrap_ok": true,
|
||||
"bootstrap_date": "2025-12-02T20:25:51.895846+00:00",
|
||||
"details": "Bootstrap réussi via API (user: automation)"
|
||||
},
|
||||
"hp3.i5.home": {
|
||||
"bootstrap_ok": true,
|
||||
"bootstrap_date": "2025-12-02T20:25:59.998069+00:00",
|
||||
"details": "Bootstrap réussi via API (user: automation)"
|
||||
},
|
||||
"mimi.pc.home": {
|
||||
"bootstrap_ok": true,
|
||||
"bootstrap_date": "2025-12-02T20:26:08.419143+00:00",
|
||||
"details": "Bootstrap réussi via API (user: automation)"
|
||||
},
|
||||
"dev.prod.home": {
|
||||
"bootstrap_ok": true,
|
||||
"bootstrap_date": "2025-12-02T21:02:48.893923+00:00",
|
||||
"details": "Bootstrap réussi via API (user: automation)"
|
||||
},
|
||||
"automate.prod.home": {
|
||||
"bootstrap_ok": true,
|
||||
"bootstrap_date": "2025-12-02T21:03:44.363353+00:00",
|
||||
"details": "Bootstrap réussi via API (user: automation)"
|
||||
},
|
||||
"ali2v.truenas.home": {
|
||||
"bootstrap_ok": true,
|
||||
"bootstrap_date": "2025-12-02T21:47:48.804941+00:00",
|
||||
"details": "Bootstrap réussi via API (user: automation)"
|
||||
},
|
||||
"hp.truenas.home": {
|
||||
"bootstrap_ok": true,
|
||||
"bootstrap_date": "2025-12-03T00:43:57.196419+00:00",
|
||||
"details": "Bootstrap réussi via API (user: automation)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,436 +0,0 @@
|
||||
{
|
||||
"runs": [
|
||||
{
|
||||
"id": "run_e16db5ac6f5c",
|
||||
"schedule_id": "sched_110c001afe0c",
|
||||
"task_id": "2",
|
||||
"started_at": "2025-12-05 02:35:00.012993+00:00",
|
||||
"finished_at": "2025-12-05 02:35:32.549542+00:00",
|
||||
"status": "success",
|
||||
"duration_seconds": 32.45821054699991,
|
||||
"hosts_impacted": 15,
|
||||
"error_message": null,
|
||||
"retry_attempt": 0
|
||||
},
|
||||
{
|
||||
"id": "run_6c434a169263",
|
||||
"schedule_id": "sched_110c001afe0c",
|
||||
"task_id": "1",
|
||||
"started_at": "2025-12-05 02:30:00.004595+00:00",
|
||||
"finished_at": "2025-12-05 02:30:30.003032+00:00",
|
||||
"status": "success",
|
||||
"duration_seconds": 29.95905439800117,
|
||||
"hosts_impacted": 15,
|
||||
"error_message": null,
|
||||
"retry_attempt": 0
|
||||
},
|
||||
{
|
||||
"id": "run_debf96da90dd",
|
||||
"schedule_id": "sched_110c001afe0c",
|
||||
"task_id": "2",
|
||||
"started_at": "2025-12-05 02:25:00.016354+00:00",
|
||||
"finished_at": "2025-12-05 02:25:27.580495+00:00",
|
||||
"status": "success",
|
||||
"duration_seconds": 27.521959419998893,
|
||||
"hosts_impacted": 15,
|
||||
"error_message": null,
|
||||
"retry_attempt": 0
|
||||
},
|
||||
{
|
||||
"id": "run_bda871b98a7c",
|
||||
"schedule_id": "sched_31a7ffb99bfd",
|
||||
"task_id": "1",
|
||||
"started_at": "2025-12-05 02:20:00.004169+00:00",
|
||||
"finished_at": "2025-12-05 02:20:28.118352+00:00",
|
||||
"status": "success",
|
||||
"duration_seconds": 28.0753927859987,
|
||||
"hosts_impacted": 15,
|
||||
"error_message": null,
|
||||
"retry_attempt": 0
|
||||
},
|
||||
{
|
||||
"id": "run_9acaf3ee6040",
|
||||
"schedule_id": "sched_d5370726086b",
|
||||
"task_id": "4",
|
||||
"started_at": "2025-12-05 02:05:01.066895+00:00",
|
||||
"finished_at": null,
|
||||
"status": "running",
|
||||
"duration_seconds": null,
|
||||
"hosts_impacted": 0,
|
||||
"error_message": null,
|
||||
"retry_attempt": 0
|
||||
},
|
||||
{
|
||||
"id": "run_25dee59d8f54",
|
||||
"schedule_id": "sched_178b8e511908",
|
||||
"task_id": "3",
|
||||
"started_at": "2025-12-05 02:05:00.942939+00:00",
|
||||
"finished_at": null,
|
||||
"status": "running",
|
||||
"duration_seconds": null,
|
||||
"hosts_impacted": 0,
|
||||
"error_message": null,
|
||||
"retry_attempt": 0
|
||||
},
|
||||
{
|
||||
"id": "run_06b2fe4c75f9",
|
||||
"schedule_id": "sched_d5370726086b",
|
||||
"task_id": "2",
|
||||
"started_at": "2025-12-05 02:00:00.048675+00:00",
|
||||
"finished_at": "2025-12-05 02:00:31.174698+00:00",
|
||||
"status": "success",
|
||||
"duration_seconds": 31.10493237799892,
|
||||
"hosts_impacted": 15,
|
||||
"error_message": null,
|
||||
"retry_attempt": 0
|
||||
},
|
||||
{
|
||||
"id": "run_5a3ada10451e",
|
||||
"schedule_id": "sched_178b8e511908",
|
||||
"task_id": "1",
|
||||
"started_at": "2025-12-05 02:00:00.004396+00:00",
|
||||
"finished_at": "2025-12-05 02:00:30.956215+00:00",
|
||||
"status": "success",
|
||||
"duration_seconds": 30.92840002899902,
|
||||
"hosts_impacted": 15,
|
||||
"error_message": null,
|
||||
"retry_attempt": 0
|
||||
},
|
||||
{
|
||||
"id": "run_484f67657ee4",
|
||||
"schedule_id": "sched_d5370726086b",
|
||||
"task_id": "3",
|
||||
"started_at": "2025-12-05 01:55:00.084088+00:00",
|
||||
"finished_at": "2025-12-05 01:55:32.096250+00:00",
|
||||
"status": "success",
|
||||
"duration_seconds": 31.975180113000533,
|
||||
"hosts_impacted": 15,
|
||||
"error_message": null,
|
||||
"retry_attempt": 0
|
||||
},
|
||||
{
|
||||
"id": "run_7c9cbee2fe69",
|
||||
"schedule_id": "sched_178b8e511908",
|
||||
"task_id": "2",
|
||||
"started_at": "2025-12-05 01:55:00.018967+00:00",
|
||||
"finished_at": "2025-12-05 01:55:32.306141+00:00",
|
||||
"status": "success",
|
||||
"duration_seconds": 32.26106233700193,
|
||||
"hosts_impacted": 15,
|
||||
"error_message": null,
|
||||
"retry_attempt": 0
|
||||
},
|
||||
{
|
||||
"id": "run_a45e3d80323d",
|
||||
"schedule_id": "sched_d5370726086b",
|
||||
"task_id": "1",
|
||||
"started_at": "2025-12-05 01:50:00.003670+00:00",
|
||||
"finished_at": "2025-12-05 01:50:27.635237+00:00",
|
||||
"status": "success",
|
||||
"duration_seconds": 27.58177596600217,
|
||||
"hosts_impacted": 15,
|
||||
"error_message": null,
|
||||
"retry_attempt": 0
|
||||
},
|
||||
{
|
||||
"id": "run_6ebb5bb47219",
|
||||
"schedule_id": "sched_d5370726086b",
|
||||
"task_id": "2",
|
||||
"started_at": "2025-12-05 01:45:00.003641+00:00",
|
||||
"finished_at": "2025-12-05 01:45:26.015984+00:00",
|
||||
"status": "success",
|
||||
"duration_seconds": 25.9568110279979,
|
||||
"hosts_impacted": 15,
|
||||
"error_message": null,
|
||||
"retry_attempt": 0
|
||||
},
|
||||
{
|
||||
"id": "run_f07c8820abcf",
|
||||
"schedule_id": "sched_d5370726086b",
|
||||
"task_id": "1",
|
||||
"started_at": "2025-12-05 01:40:00.003609+00:00",
|
||||
"finished_at": "2025-12-05 01:40:27.800302+00:00",
|
||||
"status": "success",
|
||||
"duration_seconds": 27.77215807200264,
|
||||
"hosts_impacted": 15,
|
||||
"error_message": null,
|
||||
"retry_attempt": 0
|
||||
},
|
||||
{
|
||||
"id": "run_c831165b16d9",
|
||||
"schedule_id": "sched_d5370726086b",
|
||||
"task_id": null,
|
||||
"started_at": "2025-12-05 01:35:00.003976+00:00",
|
||||
"finished_at": null,
|
||||
"status": "running",
|
||||
"duration_seconds": null,
|
||||
"hosts_impacted": 0,
|
||||
"error_message": null,
|
||||
"retry_attempt": 0
|
||||
},
|
||||
{
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
{
|
||||
"schedules": [
|
||||
{
|
||||
"id": "sched_110c001afe0c",
|
||||
"name": "Health-check-5min",
|
||||
"description": null,
|
||||
"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-04T21:40:00-05:00",
|
||||
"last_run_at": "2025-12-05 02:35:00.012919+00:00",
|
||||
"last_status": "success",
|
||||
"enabled": true,
|
||||
"retry_on_failure": 0,
|
||||
"timeout": 3600,
|
||||
"tags": [
|
||||
"Test"
|
||||
],
|
||||
"run_count": 3,
|
||||
"success_count": 3,
|
||||
"failure_count": 0,
|
||||
"created_at": "2025-12-05 02:24:06.110100+00:00",
|
||||
"updated_at": "2025-12-05 02:35:32.549928+00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user