Enhance host status tracking by parsing Ansible PLAY RECAP to update host reachability and last_seen timestamps after health-check playbook executions, add inventory group resolution to host API responses, and trigger automatic data refresh in dashboard after task completion to reflect updated host health indicators
Some checks failed
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
Some checks failed
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
This commit is contained in:
parent
6c51fb5c75
commit
46823eb42d
@ -999,6 +999,12 @@ class DashboardManager {
|
|||||||
// Rafraîchir les logs de tâches pour voir la tâche terminée
|
// Rafraîchir les logs de tâches pour voir la tâche terminée
|
||||||
this.refreshTaskLogs();
|
this.refreshTaskLogs();
|
||||||
|
|
||||||
|
// Rafraîchir aussi les hosts/métriques : un health-check met à jour status/last_seen côté backend
|
||||||
|
// (sinon l'indicateur de santé peut rester sur l'ancien état)
|
||||||
|
this.loadAllData().catch((e) => {
|
||||||
|
console.error('Erreur rafraîchissement données après fin de tâche:', e);
|
||||||
|
});
|
||||||
|
|
||||||
// Notification
|
// Notification
|
||||||
const status = taskData.status || 'completed';
|
const status = taskData.status || 'completed';
|
||||||
const isSuccess = status === 'completed';
|
const isSuccess = status === 'completed';
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import subprocess
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from time import perf_counter
|
from time import perf_counter
|
||||||
from typing import Optional, Dict, Any
|
from typing import Optional, Dict, Any
|
||||||
|
import re
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
@ -14,6 +15,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.core.dependencies import get_db, verify_api_key
|
from app.core.dependencies import get_db, verify_api_key
|
||||||
from app.crud.task import TaskRepository
|
from app.crud.task import TaskRepository
|
||||||
|
from app.crud.host import HostRepository
|
||||||
from app.schemas.ansible import AnsibleExecutionRequest, AdHocCommandRequest, AdHocCommandResult, BootstrapRequest
|
from app.schemas.ansible import AnsibleExecutionRequest, AdHocCommandRequest, AdHocCommandResult, BootstrapRequest
|
||||||
from app.schemas.task_api import Task
|
from app.schemas.task_api import Task
|
||||||
from app.schemas.common import CommandResult
|
from app.schemas.common import CommandResult
|
||||||
@ -35,6 +37,50 @@ router = APIRouter()
|
|||||||
task_log_service = TaskLogService(settings.tasks_logs_dir)
|
task_log_service = TaskLogService(settings.tasks_logs_dir)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_ansible_play_recap(stdout: str) -> dict[str, dict[str, int]]:
|
||||||
|
if not stdout:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
recap: dict[str, dict[str, int]] = {}
|
||||||
|
pattern = re.compile(
|
||||||
|
r"^\s*(?P<host>[^\s:]+)\s*:\s*.*?unreachable=(?P<unreachable>\d+)\s+failed=(?P<failed>\d+)\b",
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
for line in stdout.splitlines():
|
||||||
|
m = pattern.match(line)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
host = m.group("host")
|
||||||
|
recap[host] = {
|
||||||
|
"unreachable": int(m.group("unreachable")),
|
||||||
|
"failed": int(m.group("failed")),
|
||||||
|
}
|
||||||
|
|
||||||
|
return recap
|
||||||
|
|
||||||
|
|
||||||
|
async def _resolve_host_from_recap_name(
|
||||||
|
host_repo: HostRepository,
|
||||||
|
recap_host_name: str,
|
||||||
|
inventory_alias_to_host: dict[str, str],
|
||||||
|
):
|
||||||
|
host = await host_repo.get_by_name(recap_host_name)
|
||||||
|
if not host:
|
||||||
|
host = await host_repo.get_by_ip(recap_host_name)
|
||||||
|
if host:
|
||||||
|
return host
|
||||||
|
|
||||||
|
mapped = inventory_alias_to_host.get(recap_host_name)
|
||||||
|
if mapped:
|
||||||
|
host = await host_repo.get_by_ip(mapped)
|
||||||
|
if not host:
|
||||||
|
host = await host_repo.get_by_name(mapped)
|
||||||
|
if host:
|
||||||
|
return host
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@router.get("/playbooks")
|
@router.get("/playbooks")
|
||||||
async def get_ansible_playbooks(
|
async def get_ansible_playbooks(
|
||||||
target: Optional[str] = None,
|
target: Optional[str] = None,
|
||||||
@ -327,6 +373,56 @@ async def execute_ansible_playbook(
|
|||||||
error_message=task.error,
|
error_message=task.error,
|
||||||
result_data={"output": result.get("stdout", "")[:5000]}
|
result_data={"output": result.get("stdout", "")[:5000]}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Si c'est un health-check, mettre à jour le statut/last_seen des hosts impliqués
|
||||||
|
if request.playbook and "health-check" in request.playbook and request.target:
|
||||||
|
try:
|
||||||
|
host_repo = HostRepository(db_session)
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
inventory_alias_to_host: dict[str, str] = {}
|
||||||
|
try:
|
||||||
|
for inv_host in ansible_service.get_hosts_from_inventory():
|
||||||
|
if inv_host.name and inv_host.ansible_host:
|
||||||
|
inventory_alias_to_host[inv_host.name] = inv_host.ansible_host
|
||||||
|
except Exception:
|
||||||
|
inventory_alias_to_host = {}
|
||||||
|
|
||||||
|
recap = _parse_ansible_play_recap(result.get("stdout", "") or "")
|
||||||
|
if recap:
|
||||||
|
for host_name, counters in recap.items():
|
||||||
|
unreachable = int(counters.get("unreachable", 0) or 0)
|
||||||
|
reachable = unreachable == 0
|
||||||
|
status = "online" if reachable else "offline"
|
||||||
|
|
||||||
|
host = await _resolve_host_from_recap_name(
|
||||||
|
host_repo,
|
||||||
|
host_name,
|
||||||
|
inventory_alias_to_host,
|
||||||
|
)
|
||||||
|
if host:
|
||||||
|
await host_repo.update(
|
||||||
|
host,
|
||||||
|
status=status,
|
||||||
|
reachable=reachable,
|
||||||
|
last_seen=now,
|
||||||
|
)
|
||||||
|
elif request.target != "all":
|
||||||
|
host = await _resolve_host_from_recap_name(
|
||||||
|
host_repo,
|
||||||
|
request.target,
|
||||||
|
inventory_alias_to_host,
|
||||||
|
)
|
||||||
|
if host:
|
||||||
|
await host_repo.update(
|
||||||
|
host,
|
||||||
|
status="online" if bool(result.get("success")) else "offline",
|
||||||
|
reachable=bool(result.get("success")),
|
||||||
|
last_seen=now,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
await db_session.commit()
|
await db_session.commit()
|
||||||
|
|
||||||
# Notification
|
# Notification
|
||||||
|
|||||||
@ -18,8 +18,13 @@ from app.services import ansible_service, ws_manager
|
|||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
def _host_to_response(host, bootstrap=None) -> dict:
|
def _host_to_response(host, bootstrap=None, inventory_groups: Optional[List[str]] = None) -> dict:
|
||||||
"""Convertit un modèle Host DB en réponse API."""
|
"""Convertit un modèle Host DB en réponse API."""
|
||||||
|
groups: List[str] = [host.ansible_group] if host.ansible_group else []
|
||||||
|
if inventory_groups:
|
||||||
|
for g in inventory_groups:
|
||||||
|
if g and g not in groups:
|
||||||
|
groups.append(g)
|
||||||
return {
|
return {
|
||||||
"id": host.id,
|
"id": host.id,
|
||||||
"name": host.name,
|
"name": host.name,
|
||||||
@ -28,7 +33,7 @@ def _host_to_response(host, bootstrap=None) -> dict:
|
|||||||
"os": "Linux",
|
"os": "Linux",
|
||||||
"last_seen": host.last_seen,
|
"last_seen": host.last_seen,
|
||||||
"created_at": host.created_at,
|
"created_at": host.created_at,
|
||||||
"groups": [host.ansible_group] if host.ansible_group else [],
|
"groups": groups,
|
||||||
"bootstrap_ok": bootstrap.status == "success" if bootstrap else False,
|
"bootstrap_ok": bootstrap.status == "success" if bootstrap else False,
|
||||||
"bootstrap_date": bootstrap.last_attempt if bootstrap else None,
|
"bootstrap_date": bootstrap.last_attempt if bootstrap else None,
|
||||||
}
|
}
|
||||||
@ -62,7 +67,15 @@ async def get_host_by_name(
|
|||||||
raise HTTPException(status_code=404, detail=f"Hôte '{host_name}' non trouvé")
|
raise HTTPException(status_code=404, detail=f"Hôte '{host_name}' non trouvé")
|
||||||
|
|
||||||
bootstrap = await bs_repo.latest_for_host(host.id)
|
bootstrap = await bs_repo.latest_for_host(host.id)
|
||||||
return _host_to_response(host, bootstrap)
|
inventory_groups: Optional[List[str]] = None
|
||||||
|
try:
|
||||||
|
inv_hosts = ansible_service.get_hosts_from_inventory()
|
||||||
|
inv_match = next((h for h in inv_hosts if h.name == host.name), None)
|
||||||
|
inventory_groups = inv_match.groups if inv_match else None
|
||||||
|
except Exception:
|
||||||
|
inventory_groups = None
|
||||||
|
|
||||||
|
return _host_to_response(host, bootstrap, inventory_groups=inventory_groups)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/refresh")
|
@router.post("/refresh")
|
||||||
@ -154,7 +167,15 @@ async def get_host(
|
|||||||
raise HTTPException(status_code=404, detail="Hôte non trouvé")
|
raise HTTPException(status_code=404, detail="Hôte non trouvé")
|
||||||
|
|
||||||
bootstrap = await bs_repo.latest_for_host(host.id)
|
bootstrap = await bs_repo.latest_for_host(host.id)
|
||||||
return _host_to_response(host, bootstrap)
|
inventory_groups: Optional[List[str]] = None
|
||||||
|
try:
|
||||||
|
inv_hosts = ansible_service.get_hosts_from_inventory()
|
||||||
|
inv_match = next((h for h in inv_hosts if h.name == host.name), None)
|
||||||
|
inventory_groups = inv_match.groups if inv_match else None
|
||||||
|
except Exception:
|
||||||
|
inventory_groups = None
|
||||||
|
|
||||||
|
return _host_to_response(host, bootstrap, inventory_groups=inventory_groups)
|
||||||
|
|
||||||
|
|
||||||
@router.get("")
|
@router.get("")
|
||||||
@ -198,6 +219,13 @@ async def get_hosts(
|
|||||||
})
|
})
|
||||||
return fallback_results
|
return fallback_results
|
||||||
|
|
||||||
|
inventory_groups_map = {}
|
||||||
|
try:
|
||||||
|
for inv_host in ansible_service.get_hosts_from_inventory():
|
||||||
|
inventory_groups_map[inv_host.name] = inv_host.groups
|
||||||
|
except Exception:
|
||||||
|
inventory_groups_map = {}
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for host in hosts:
|
for host in hosts:
|
||||||
bootstrap = await bs_repo.latest_for_host(host.id)
|
bootstrap = await bs_repo.latest_for_host(host.id)
|
||||||
@ -206,7 +234,11 @@ async def get_hosts(
|
|||||||
continue
|
continue
|
||||||
if bootstrap_status == "not_configured" and bootstrap and bootstrap.status == "success":
|
if bootstrap_status == "not_configured" and bootstrap and bootstrap.status == "success":
|
||||||
continue
|
continue
|
||||||
result.append(_host_to_response(host, bootstrap))
|
result.append(_host_to_response(
|
||||||
|
host,
|
||||||
|
bootstrap,
|
||||||
|
inventory_groups=inventory_groups_map.get(host.name),
|
||||||
|
))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import asyncio
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
import re
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
@ -16,6 +17,7 @@ from app.core.constants import ACTION_PLAYBOOK_MAP, ACTION_DISPLAY_NAMES
|
|||||||
from app.core.dependencies import get_db, verify_api_key
|
from app.core.dependencies import get_db, verify_api_key
|
||||||
from app.crud.task import TaskRepository
|
from app.crud.task import TaskRepository
|
||||||
from app.crud.log import LogRepository
|
from app.crud.log import LogRepository
|
||||||
|
from app.crud.host import HostRepository
|
||||||
from app.schemas.task_api import TaskRequest
|
from app.schemas.task_api import TaskRequest
|
||||||
from app.services import ws_manager, db, ansible_service, notification_service
|
from app.services import ws_manager, db, ansible_service, notification_service
|
||||||
from app.services.task_log_service import TaskLogService
|
from app.services.task_log_service import TaskLogService
|
||||||
@ -33,6 +35,56 @@ task_log_service = TaskLogService(settings.tasks_logs_dir)
|
|||||||
running_task_handles = {}
|
running_task_handles = {}
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_ansible_play_recap(stdout: str) -> dict[str, dict[str, int]]:
|
||||||
|
"""Parse Ansible PLAY RECAP to extract per-host unreachable/failed counters.
|
||||||
|
|
||||||
|
Returns a mapping host_name -> {"unreachable": int, "failed": int}.
|
||||||
|
"""
|
||||||
|
if not stdout:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Typical line:
|
||||||
|
# host1 : ok=10 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
||||||
|
recap: dict[str, dict[str, int]] = {}
|
||||||
|
pattern = re.compile(
|
||||||
|
r"^\s*(?P<host>[^\s:]+)\s*:\s*.*?unreachable=(?P<unreachable>\d+)\s+failed=(?P<failed>\d+)\b",
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
for line in stdout.splitlines():
|
||||||
|
m = pattern.match(line)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
host = m.group("host")
|
||||||
|
recap[host] = {
|
||||||
|
"unreachable": int(m.group("unreachable")),
|
||||||
|
"failed": int(m.group("failed")),
|
||||||
|
}
|
||||||
|
|
||||||
|
return recap
|
||||||
|
|
||||||
|
|
||||||
|
async def _resolve_host_from_recap_name(
|
||||||
|
host_repo: HostRepository,
|
||||||
|
recap_host_name: str,
|
||||||
|
inventory_alias_to_host: dict[str, str],
|
||||||
|
):
|
||||||
|
host = await host_repo.get_by_name(recap_host_name)
|
||||||
|
if not host:
|
||||||
|
host = await host_repo.get_by_ip(recap_host_name)
|
||||||
|
if host:
|
||||||
|
return host
|
||||||
|
|
||||||
|
mapped = inventory_alias_to_host.get(recap_host_name)
|
||||||
|
if mapped:
|
||||||
|
host = await host_repo.get_by_ip(mapped)
|
||||||
|
if not host:
|
||||||
|
host = await host_repo.get_by_name(mapped)
|
||||||
|
if host:
|
||||||
|
return host
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@router.get("")
|
@router.get("")
|
||||||
async def get_tasks(
|
async def get_tasks(
|
||||||
limit: int = 100,
|
limit: int = 100,
|
||||||
@ -428,7 +480,68 @@ async def _execute_task_playbook(
|
|||||||
error_message=mem_task.error,
|
error_message=mem_task.error,
|
||||||
result_data={"output": result.get("stdout", "")[:5000]}
|
result_data={"output": result.get("stdout", "")[:5000]}
|
||||||
)
|
)
|
||||||
await session.commit()
|
|
||||||
|
# Si c'est un health-check ciblé, mettre à jour le statut/last_seen de l'hôte
|
||||||
|
if playbook and "health-check" in playbook and target:
|
||||||
|
try:
|
||||||
|
host_repo = HostRepository(session)
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
inventory_alias_to_host: dict[str, str] = {}
|
||||||
|
try:
|
||||||
|
for inv_host in ansible_service.get_hosts_from_inventory():
|
||||||
|
if inv_host.name and inv_host.ansible_host:
|
||||||
|
inventory_alias_to_host[inv_host.name] = inv_host.ansible_host
|
||||||
|
except Exception:
|
||||||
|
inventory_alias_to_host = {}
|
||||||
|
|
||||||
|
recap = _parse_ansible_play_recap(result.get("stdout", "") or "")
|
||||||
|
|
||||||
|
# If we have a recap, update each host that appears in the execution.
|
||||||
|
if recap:
|
||||||
|
for host_name, counters in recap.items():
|
||||||
|
unreachable = int(counters.get("unreachable", 0) or 0)
|
||||||
|
failed = int(counters.get("failed", 0) or 0)
|
||||||
|
|
||||||
|
# reachable means Ansible could contact the host (unreachable=0)
|
||||||
|
reachable = unreachable == 0
|
||||||
|
|
||||||
|
# status represents connectivity (not task success)
|
||||||
|
status = "online" if reachable else "offline"
|
||||||
|
|
||||||
|
host = await _resolve_host_from_recap_name(
|
||||||
|
host_repo,
|
||||||
|
host_name,
|
||||||
|
inventory_alias_to_host,
|
||||||
|
)
|
||||||
|
|
||||||
|
if host:
|
||||||
|
await host_repo.update(
|
||||||
|
host,
|
||||||
|
status=status,
|
||||||
|
reachable=reachable,
|
||||||
|
last_seen=now,
|
||||||
|
)
|
||||||
|
# No recap: keep old behavior for targeted host, but avoid touching all hosts blindly.
|
||||||
|
elif target != "all":
|
||||||
|
host = await _resolve_host_from_recap_name(
|
||||||
|
host_repo,
|
||||||
|
target,
|
||||||
|
inventory_alias_to_host,
|
||||||
|
)
|
||||||
|
|
||||||
|
if host:
|
||||||
|
await host_repo.update(
|
||||||
|
host,
|
||||||
|
status="online" if bool(success) else "offline",
|
||||||
|
reachable=bool(success),
|
||||||
|
last_seen=now,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
# Ne pas faire échouer la tâche si la MAJ de statut host échoue
|
||||||
|
pass
|
||||||
|
|
||||||
|
await session.commit()
|
||||||
|
|
||||||
# Sauvegarder le log markdown
|
# Sauvegarder le log markdown
|
||||||
try:
|
try:
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,58 @@
|
|||||||
|
# ✅ Vérification de santé
|
||||||
|
|
||||||
|
## Informations
|
||||||
|
|
||||||
|
| Propriété | Valeur |
|
||||||
|
|-----------|--------|
|
||||||
|
| **ID** | `13e2b84be54a41eb945785378b60f5f6` |
|
||||||
|
| **Nom** | Vérification de santé |
|
||||||
|
| **Cible** | `ali2v.xeon.home` |
|
||||||
|
| **Statut** | completed |
|
||||||
|
| **Type** | Manuel |
|
||||||
|
| **Progression** | 100% |
|
||||||
|
| **Début** | 2025-12-22T02:16:51.460152+00:00 |
|
||||||
|
| **Fin** | 2025-12-22T02:16:59.573843+00:00 |
|
||||||
|
| **Durée** | 8.1s |
|
||||||
|
|
||||||
|
## Sortie
|
||||||
|
|
||||||
|
```
|
||||||
|
Using /mnt/c/dev/git/python/homelab-automation-api-v2/ansible/ansible.cfg as config file
|
||||||
|
|
||||||
|
PLAY [Health check on target host] *********************************************
|
||||||
|
|
||||||
|
TASK [Check if host is reachable (ping)] ***************************************
|
||||||
|
ok: [ali2v.xeon.home] => {"changed": false, "ping": "pong"}
|
||||||
|
|
||||||
|
TASK [Gather minimal facts] ****************************************************
|
||||||
|
ok: [ali2v.xeon.home]
|
||||||
|
|
||||||
|
TASK [Get system uptime] *******************************************************
|
||||||
|
ok: [ali2v.xeon.home] => {"changed": false, "cmd": ["uptime"], "delta": "0:00:00.002844", "end": "2025-12-21 21:16:55.811614", "msg": "", "rc": 0, "start": "2025-12-21 21:16:55.808770", "stderr": "", "stderr_lines": [], "stdout": " 21:16:55 up 1 day, 22:16, 1 user, load average: 0.07, 0.26, 0.30", "stdout_lines": [" 21:16:55 up 1 day, 22:16, 1 user, load average: 0.07, 0.26, 0.30"]}
|
||||||
|
|
||||||
|
TASK [Get disk usage] **********************************************************
|
||||||
|
ok: [ali2v.xeon.home] => {"changed": false, "cmd": "df -h / | tail -1 | awk '{print $5}'", "delta": "0:00:00.004099", "end": "2025-12-21 21:16:56.243269", "msg": "", "rc": 0, "start": "2025-12-21 21:16:56.239170", "stderr": "", "stderr_lines": [], "stdout": "22%", "stdout_lines": ["22%"]}
|
||||||
|
|
||||||
|
TASK [Get memory usage (Linux)] ************************************************
|
||||||
|
ok: [ali2v.xeon.home] => {"changed": false, "cmd": "if command -v free >/dev/null 2>&1; then\n free -m | grep Mem | awk '{printf \"%.1f%%\", $3/$2 * 100}'\nelse\n # Fallback for systems without free command\n cat /proc/meminfo | awk '/MemTotal/{total=$2} /MemAvailable/{avail=$2} END{printf \"%.1f%%\", (total-avail)/total*100}'\nfi\n", "delta": "0:00:00.004345", "end": "2025-12-21 21:16:56.958250", "msg": "", "rc": 0, "start": "2025-12-21 21:16:56.953905", "stderr": "", "stderr_lines": [], "stdout": "20.6%", "stdout_lines": ["20.6%"]}
|
||||||
|
|
||||||
|
TASK [Get CPU temperature (ARM/SBC)] *******************************************
|
||||||
|
ok: [ali2v.xeon.home] => {"changed": false, "cmd": "if [ -f /sys/class/thermal/thermal_zone0/temp ]; then\n temp=$(cat /sys/class/thermal/thermal_zone0/temp)\n # Use awk instead of bc for better compatibility\n echo \"${temp}\" | awk '{printf \"%.1f°C\", $1/1000}'\nelse\n echo \"N/A\"\nfi\n", "delta": "0:00:00.004879", "end": "2025-12-21 21:16:57.736676", "msg": "", "rc": 0, "start": "2025-12-21 21:16:57.731797", "stderr": "", "stderr_lines": [], "stdout": "45.0°C", "stdout_lines": ["45.0°C"]}
|
||||||
|
|
||||||
|
TASK [Get CPU load] ************************************************************
|
||||||
|
ok: [ali2v.xeon.home] => {"changed": false, "cmd": "if [ -f /proc/loadavg ]; then\n cat /proc/loadavg | awk '{print $1}'\nelse\n uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | tr -d ' '\nfi\n", "delta": "0:00:00.004063", "end": "2025-12-21 21:16:58.470317", "msg": "", "rc": 0, "start": "2025-12-21 21:16:58.466254", "stderr": "", "stderr_lines": [], "stdout": "0.06", "stdout_lines": ["0.06"]}
|
||||||
|
|
||||||
|
TASK [Display health status] ***************************************************
|
||||||
|
ok: [ali2v.xeon.home] => {
|
||||||
|
"msg": "═══════════════════════════════════════\nHost: ali2v.xeon.home\nStatus: OK\n═══════════════════════════════════════\nUptime: 21:16:55 up 1 day, 22:16, 1 user, load average: 0.07, 0.26, 0.30\nDisk Usage: 22%\nMemory Usage: 20.6%\nCPU Load: 0.06\nCPU Temp: 45.0°C\n═══════════════════════════════════════\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
PLAY RECAP *********************************************************************
|
||||||
|
ali2v.xeon.home : ok=8 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
*Généré automatiquement par Homelab Automation Dashboard*
|
||||||
|
*Date: 2025-12-22T02:16:59.680539+00:00*
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
# ✅ Playbook: Health Check
|
||||||
|
|
||||||
|
## Informations
|
||||||
|
|
||||||
|
| Propriété | Valeur |
|
||||||
|
|-----------|--------|
|
||||||
|
| **ID** | `pb_4e00871ceb5d` |
|
||||||
|
| **Nom** | Playbook: Health Check |
|
||||||
|
| **Cible** | `env_lab` |
|
||||||
|
| **Statut** | completed |
|
||||||
|
| **Type** | Manuel |
|
||||||
|
| **Progression** | 100% |
|
||||||
|
| **Début** | 2025-12-22T02:18:41.245690+00:00 |
|
||||||
|
| **Fin** | 2025-12-22T02:18:50.313464+00:00 |
|
||||||
|
| **Durée** | 9.7s |
|
||||||
|
|
||||||
|
## Sortie
|
||||||
|
|
||||||
|
```
|
||||||
|
Using /mnt/c/dev/git/python/homelab-automation-api-v2/ansible/ansible.cfg as config file
|
||||||
|
|
||||||
|
PLAY [Health check on target host] *********************************************
|
||||||
|
|
||||||
|
TASK [Check if host is reachable (ping)] ***************************************
|
||||||
|
ok: [dev.lab.home] => {"changed": false, "ping": "pong"}
|
||||||
|
ok: [media.labb.home] => {"changed": false, "ping": "pong"}
|
||||||
|
|
||||||
|
TASK [Gather minimal facts] ****************************************************
|
||||||
|
ok: [dev.lab.home]
|
||||||
|
ok: [media.labb.home]
|
||||||
|
|
||||||
|
TASK [Get system uptime] *******************************************************
|
||||||
|
ok: [dev.lab.home] => {"changed": false, "cmd": ["uptime"], "delta": "0:00:00.002993", "end": "2025-12-21 21:18:46.738790", "msg": "", "rc": 0, "start": "2025-12-21 21:18:46.735797", "stderr": "", "stderr_lines": [], "stdout": " 21:18:46 up 3:33, 0 user, load average: 0.09, 0.18, 0.17", "stdout_lines": [" 21:18:46 up 3:33, 0 user, load average: 0.09, 0.18, 0.17"]}
|
||||||
|
ok: [media.labb.home] => {"changed": false, "cmd": ["uptime"], "delta": "0:00:00.003514", "end": "2025-12-21 21:18:08.052826", "msg": "", "rc": 0, "start": "2025-12-21 21:18:08.049312", "stderr": "", "stderr_lines": [], "stdout": " 21:18:08 up 22 days, 5:20, 0 user, load average: 0.00, 0.02, 0.00", "stdout_lines": [" 21:18:08 up 22 days, 5:20, 0 user, load average: 0.00, 0.02, 0.00"]}
|
||||||
|
|
||||||
|
TASK [Get disk usage] **********************************************************
|
||||||
|
ok: [dev.lab.home] => {"changed": false, "cmd": "df -h / | tail -1 | awk '{print $5}'", "delta": "0:00:00.005481", "end": "2025-12-21 21:18:47.378500", "msg": "", "rc": 0, "start": "2025-12-21 21:18:47.373019", "stderr": "", "stderr_lines": [], "stdout": "50%", "stdout_lines": ["50%"]}
|
||||||
|
ok: [media.labb.home] => {"changed": false, "cmd": "df -h / | tail -1 | awk '{print $5}'", "delta": "0:00:00.005819", "end": "2025-12-21 21:18:08.717887", "msg": "", "rc": 0, "start": "2025-12-21 21:18:08.712068", "stderr": "", "stderr_lines": [], "stdout": "25%", "stdout_lines": ["25%"]}
|
||||||
|
|
||||||
|
TASK [Get memory usage (Linux)] ************************************************
|
||||||
|
ok: [dev.lab.home] => {"changed": false, "cmd": "if command -v free >/dev/null 2>&1; then\n free -m | grep Mem | awk '{printf \"%.1f%%\", $3/$2 * 100}'\nelse\n # Fallback for systems without free command\n cat /proc/meminfo | awk '/MemTotal/{total=$2} /MemAvailable/{avail=$2} END{printf \"%.1f%%\", (total-avail)/total*100}'\nfi\n", "delta": "0:00:00.005529", "end": "2025-12-21 21:18:48.084897", "msg": "", "rc": 0, "start": "2025-12-21 21:18:48.079368", "stderr": "", "stderr_lines": [], "stdout": "77.2%", "stdout_lines": ["77.2%"]}
|
||||||
|
ok: [media.labb.home] => {"changed": false, "cmd": "if command -v free >/dev/null 2>&1; then\n free -m | grep Mem | awk '{printf \"%.1f%%\", $3/$2 * 100}'\nelse\n # Fallback for systems without free command\n cat /proc/meminfo | awk '/MemTotal/{total=$2} /MemAvailable/{avail=$2} END{printf \"%.1f%%\", (total-avail)/total*100}'\nfi\n", "delta": "0:00:00.004326", "end": "2025-12-21 21:18:09.398504", "msg": "", "rc": 0, "start": "2025-12-21 21:18:09.394178", "stderr": "", "stderr_lines": [], "stdout": "60.4%", "stdout_lines": ["60.4%"]}
|
||||||
|
|
||||||
|
TASK [Get CPU temperature (ARM/SBC)] *******************************************
|
||||||
|
ok: [dev.lab.home] => {"changed": false, "cmd": "if [ -f /sys/class/thermal/thermal_zone0/temp ]; then\n temp=$(cat /sys/class/thermal/thermal_zone0/temp)\n # Use awk instead of bc for better compatibility\n echo \"${temp}\" | awk '{printf \"%.1f°C\", $1/1000}'\nelse\n echo \"N/A\"\nfi\n", "delta": "0:00:00.002563", "end": "2025-12-21 21:18:48.724451", "msg": "", "rc": 0, "start": "2025-12-21 21:18:48.721888", "stderr": "", "stderr_lines": [], "stdout": "N/A", "stdout_lines": ["N/A"]}
|
||||||
|
ok: [media.labb.home] => {"changed": false, "cmd": "if [ -f /sys/class/thermal/thermal_zone0/temp ]; then\n temp=$(cat /sys/class/thermal/thermal_zone0/temp)\n # Use awk instead of bc for better compatibility\n echo \"${temp}\" | awk '{printf \"%.1f°C\", $1/1000}'\nelse\n echo \"N/A\"\nfi\n", "delta": "0:00:00.003053", "end": "2025-12-21 21:18:10.054191", "msg": "", "rc": 0, "start": "2025-12-21 21:18:10.051138", "stderr": "", "stderr_lines": [], "stdout": "N/A", "stdout_lines": ["N/A"]}
|
||||||
|
|
||||||
|
TASK [Get CPU load] ************************************************************
|
||||||
|
ok: [dev.lab.home] => {"changed": false, "cmd": "if [ -f /proc/loadavg ]; then\n cat /proc/loadavg | awk '{print $1}'\nelse\n uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | tr -d ' '\nfi\n", "delta": "0:00:00.004795", "end": "2025-12-21 21:18:49.383952", "msg": "", "rc": 0, "start": "2025-12-21 21:18:49.379157", "stderr": "", "stderr_lines": [], "stdout": "0.09", "stdout_lines": ["0.09"]}
|
||||||
|
ok: [media.labb.home] => {"changed": false, "cmd": "if [ -f /proc/loadavg ]; then\n cat /proc/loadavg | awk '{print $1}'\nelse\n uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | tr -d ' '\nfi\n", "delta": "0:00:00.005060", "end": "2025-12-21 21:18:10.713741", "msg": "", "rc": 0, "start": "2025-12-21 21:18:10.708681", "stderr": "", "stderr_lines": [], "stdout": "0.00", "stdout_lines": ["0.00"]}
|
||||||
|
|
||||||
|
TASK [Display health status] ***************************************************
|
||||||
|
ok: [dev.lab.home] => {
|
||||||
|
"msg": "═══════════════════════════════════════\nHost: dev.lab.home\nStatus: OK\n═══════════════════════════════════════\nUptime: 21:18:46 up 3:33, 0 user, load average: 0.09, 0.18, 0.17\nDisk Usage: 50%\nMemory Usage: 77.2%\nCPU Load: 0.09\nCPU Temp: N/A\n═══════════════════════════════════════\n"
|
||||||
|
}
|
||||||
|
ok: [media.labb.home] => {
|
||||||
|
"msg": "═══════════════════════════════════════\nHost: media.labb.home\nStatus: OK\n═══════════════════════════════════════\nUptime: 21:18:08 up 22 days, 5:20, 0 user, load average: 0.00, 0.02, 0.00\nDisk Usage: 25%\nMemory Usage: 60.4%\nCPU Load: 0.00\nCPU Temp: N/A\n═══════════════════════════════════════\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
PLAY RECAP *********************************************************************
|
||||||
|
dev.lab.home : ok=8 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
||||||
|
media.labb.home : ok=8 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
*Généré automatiquement par Homelab Automation Dashboard*
|
||||||
|
*Date: 2025-12-22T02:18:50.322512+00:00*
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
# ✅ Playbook: Health Check
|
||||||
|
|
||||||
|
## Informations
|
||||||
|
|
||||||
|
| Propriété | Valeur |
|
||||||
|
|-----------|--------|
|
||||||
|
| **ID** | `pb_f8b6e41ff159` |
|
||||||
|
| **Nom** | Playbook: Health Check |
|
||||||
|
| **Cible** | `env_lab` |
|
||||||
|
| **Statut** | completed |
|
||||||
|
| **Type** | Manuel |
|
||||||
|
| **Progression** | 100% |
|
||||||
|
| **Début** | 2025-12-22T02:44:52.164420+00:00 |
|
||||||
|
| **Fin** | 2025-12-22T02:45:04.128027+00:00 |
|
||||||
|
| **Durée** | 11.9s |
|
||||||
|
|
||||||
|
## Sortie
|
||||||
|
|
||||||
|
```
|
||||||
|
Using /mnt/c/dev/git/python/homelab-automation-api-v2/ansible/ansible.cfg as config file
|
||||||
|
|
||||||
|
PLAY [Health check on target host] *********************************************
|
||||||
|
|
||||||
|
TASK [Check if host is reachable (ping)] ***************************************
|
||||||
|
ok: [dev.lab.home] => {"changed": false, "ping": "pong"}
|
||||||
|
ok: [media.labb.home] => {"changed": false, "ping": "pong"}
|
||||||
|
|
||||||
|
TASK [Gather minimal facts] ****************************************************
|
||||||
|
ok: [dev.lab.home]
|
||||||
|
ok: [media.labb.home]
|
||||||
|
|
||||||
|
TASK [Get system uptime] *******************************************************
|
||||||
|
ok: [dev.lab.home] => {"changed": false, "cmd": ["uptime"], "delta": "0:00:00.002863", "end": "2025-12-21 21:44:59.687460", "msg": "", "rc": 0, "start": "2025-12-21 21:44:59.684597", "stderr": "", "stderr_lines": [], "stdout": " 21:44:59 up 3:59, 0 user, load average: 0.13, 0.16, 0.17", "stdout_lines": [" 21:44:59 up 3:59, 0 user, load average: 0.13, 0.16, 0.17"]}
|
||||||
|
ok: [media.labb.home] => {"changed": false, "cmd": ["uptime"], "delta": "0:00:00.003623", "end": "2025-12-21 21:44:21.007560", "msg": "", "rc": 0, "start": "2025-12-21 21:44:21.003937", "stderr": "", "stderr_lines": [], "stdout": " 21:44:21 up 22 days, 5:46, 0 user, load average: 0.15, 0.05, 0.01", "stdout_lines": [" 21:44:21 up 22 days, 5:46, 0 user, load average: 0.15, 0.05, 0.01"]}
|
||||||
|
|
||||||
|
TASK [Get disk usage] **********************************************************
|
||||||
|
ok: [dev.lab.home] => {"changed": false, "cmd": "df -h / | tail -1 | awk '{print $5}'", "delta": "0:00:00.005219", "end": "2025-12-21 21:45:00.423346", "msg": "", "rc": 0, "start": "2025-12-21 21:45:00.418127", "stderr": "", "stderr_lines": [], "stdout": "50%", "stdout_lines": ["50%"]}
|
||||||
|
ok: [media.labb.home] => {"changed": false, "cmd": "df -h / | tail -1 | awk '{print $5}'", "delta": "0:00:00.005561", "end": "2025-12-21 21:44:21.744850", "msg": "", "rc": 0, "start": "2025-12-21 21:44:21.739289", "stderr": "", "stderr_lines": [], "stdout": "25%", "stdout_lines": ["25%"]}
|
||||||
|
|
||||||
|
TASK [Get memory usage (Linux)] ************************************************
|
||||||
|
ok: [dev.lab.home] => {"changed": false, "cmd": "if command -v free >/dev/null 2>&1; then\n free -m | grep Mem | awk '{printf \"%.1f%%\", $3/$2 * 100}'\nelse\n # Fallback for systems without free command\n cat /proc/meminfo | awk '/MemTotal/{total=$2} /MemAvailable/{avail=$2} END{printf \"%.1f%%\", (total-avail)/total*100}'\nfi\n", "delta": "0:00:00.003939", "end": "2025-12-21 21:45:01.130170", "msg": "", "rc": 0, "start": "2025-12-21 21:45:01.126231", "stderr": "", "stderr_lines": [], "stdout": "77.3%", "stdout_lines": ["77.3%"]}
|
||||||
|
ok: [media.labb.home] => {"changed": false, "cmd": "if command -v free >/dev/null 2>&1; then\n free -m | grep Mem | awk '{printf \"%.1f%%\", $3/$2 * 100}'\nelse\n # Fallback for systems without free command\n cat /proc/meminfo | awk '/MemTotal/{total=$2} /MemAvailable/{avail=$2} END{printf \"%.1f%%\", (total-avail)/total*100}'\nfi\n", "delta": "0:00:00.004259", "end": "2025-12-21 21:44:22.460821", "msg": "", "rc": 0, "start": "2025-12-21 21:44:22.456562", "stderr": "", "stderr_lines": [], "stdout": "60.2%", "stdout_lines": ["60.2%"]}
|
||||||
|
|
||||||
|
TASK [Get CPU temperature (ARM/SBC)] *******************************************
|
||||||
|
ok: [dev.lab.home] => {"changed": false, "cmd": "if [ -f /sys/class/thermal/thermal_zone0/temp ]; then\n temp=$(cat /sys/class/thermal/thermal_zone0/temp)\n # Use awk instead of bc for better compatibility\n echo \"${temp}\" | awk '{printf \"%.1f°C\", $1/1000}'\nelse\n echo \"N/A\"\nfi\n", "delta": "0:00:00.002817", "end": "2025-12-21 21:45:01.803874", "msg": "", "rc": 0, "start": "2025-12-21 21:45:01.801057", "stderr": "", "stderr_lines": [], "stdout": "N/A", "stdout_lines": ["N/A"]}
|
||||||
|
ok: [media.labb.home] => {"changed": false, "cmd": "if [ -f /sys/class/thermal/thermal_zone0/temp ]; then\n temp=$(cat /sys/class/thermal/thermal_zone0/temp)\n # Use awk instead of bc for better compatibility\n echo \"${temp}\" | awk '{printf \"%.1f°C\", $1/1000}'\nelse\n echo \"N/A\"\nfi\n", "delta": "0:00:00.003187", "end": "2025-12-21 21:44:23.165750", "msg": "", "rc": 0, "start": "2025-12-21 21:44:23.162563", "stderr": "", "stderr_lines": [], "stdout": "N/A", "stdout_lines": ["N/A"]}
|
||||||
|
|
||||||
|
TASK [Get CPU load] ************************************************************
|
||||||
|
ok: [dev.lab.home] => {"changed": false, "cmd": "if [ -f /proc/loadavg ]; then\n cat /proc/loadavg | awk '{print $1}'\nelse\n uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | tr -d ' '\nfi\n", "delta": "0:00:00.004399", "end": "2025-12-21 21:45:02.508423", "msg": "", "rc": 0, "start": "2025-12-21 21:45:02.504024", "stderr": "", "stderr_lines": [], "stdout": "0.12", "stdout_lines": ["0.12"]}
|
||||||
|
ok: [media.labb.home] => {"changed": false, "cmd": "if [ -f /proc/loadavg ]; then\n cat /proc/loadavg | awk '{print $1}'\nelse\n uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | tr -d ' '\nfi\n", "delta": "0:00:00.004939", "end": "2025-12-21 21:44:23.843717", "msg": "", "rc": 0, "start": "2025-12-21 21:44:23.838778", "stderr": "", "stderr_lines": [], "stdout": "0.15", "stdout_lines": ["0.15"]}
|
||||||
|
|
||||||
|
TASK [Display health status] ***************************************************
|
||||||
|
ok: [media.labb.home] => {
|
||||||
|
"msg": "═══════════════════════════════════════\nHost: media.labb.home\nStatus: OK\n═══════════════════════════════════════\nUptime: 21:44:21 up 22 days, 5:46, 0 user, load average: 0.15, 0.05, 0.01\nDisk Usage: 25%\nMemory Usage: 60.2%\nCPU Load: 0.15\nCPU Temp: N/A\n═══════════════════════════════════════\n"
|
||||||
|
}
|
||||||
|
ok: [dev.lab.home] => {
|
||||||
|
"msg": "═══════════════════════════════════════\nHost: dev.lab.home\nStatus: OK\n═══════════════════════════════════════\nUptime: 21:44:59 up 3:59, 0 user, load average: 0.13, 0.16, 0.17\nDisk Usage: 50%\nMemory Usage: 77.3%\nCPU Load: 0.12\nCPU Temp: N/A\n═══════════════════════════════════════\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
PLAY RECAP *********************************************************************
|
||||||
|
dev.lab.home : ok=8 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
||||||
|
media.labb.home : ok=8 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
*Généré automatiquement par Homelab Automation Dashboard*
|
||||||
|
*Date: 2025-12-22T02:45:04.136628+00:00*
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
# ✅ Playbook: Health Check
|
||||||
|
|
||||||
|
## Informations
|
||||||
|
|
||||||
|
| Propriété | Valeur |
|
||||||
|
|-----------|--------|
|
||||||
|
| **ID** | `pb_35848579687b` |
|
||||||
|
| **Nom** | Playbook: Health Check |
|
||||||
|
| **Cible** | `role_sbc` |
|
||||||
|
| **Statut** | completed |
|
||||||
|
| **Type** | Manuel |
|
||||||
|
| **Progression** | 100% |
|
||||||
|
| **Début** | 2025-12-22T03:04:43.692808+00:00 |
|
||||||
|
| **Fin** | 2025-12-22T03:05:04.816419+00:00 |
|
||||||
|
| **Durée** | 21.1s |
|
||||||
|
|
||||||
|
## Sortie
|
||||||
|
|
||||||
|
```
|
||||||
|
Using /mnt/c/dev/git/python/homelab-automation-api-v2/ansible/ansible.cfg as config file
|
||||||
|
|
||||||
|
PLAY [Health check on target host] *********************************************
|
||||||
|
|
||||||
|
TASK [Check if host is reachable (ping)] ***************************************
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "ping": "pong"}
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "ping": "pong"}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "ping": "pong"}
|
||||||
|
|
||||||
|
TASK [Gather minimal facts] ****************************************************
|
||||||
|
ok: [raspi.8gb.home]
|
||||||
|
ok: [raspi.4gb.home]
|
||||||
|
ok: [orangepi.pc.home]
|
||||||
|
|
||||||
|
TASK [Get system uptime] *******************************************************
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "cmd": ["uptime"], "delta": "0:00:00.008297", "end": "2025-12-21 22:04:55.585526", "msg": "", "rc": 0, "start": "2025-12-21 22:04:55.577229", "stderr": "", "stderr_lines": [], "stdout": " 22:04:55 up 198 days, 13:37, 1 user, load average: 0.14, 0.16, 0.11", "stdout_lines": [" 22:04:55 up 198 days, 13:37, 1 user, load average: 0.14, 0.16, 0.11"]}
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "cmd": ["uptime"], "delta": "0:00:00.009138", "end": "2025-12-21 22:04:55.658156", "msg": "", "rc": 0, "start": "2025-12-21 22:04:55.649018", "stderr": "", "stderr_lines": [], "stdout": " 22:04:55 up 198 days, 13:37, 1 user, load average: 0.34, 0.23, 0.20", "stdout_lines": [" 22:04:55 up 198 days, 13:37, 1 user, load average: 0.34, 0.23, 0.20"]}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "cmd": ["uptime"], "delta": "0:00:00.019335", "end": "2025-12-21 22:04:56.303262", "msg": "", "rc": 0, "start": "2025-12-21 22:04:56.283927", "stderr": "", "stderr_lines": [], "stdout": " 22:04:56 up 19 days, 11:31, 1 user, load average: 0.29, 0.16, 0.11", "stdout_lines": [" 22:04:56 up 19 days, 11:31, 1 user, load average: 0.29, 0.16, 0.11"]}
|
||||||
|
|
||||||
|
TASK [Get disk usage] **********************************************************
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "cmd": "df -h / | tail -1 | awk '{print $5}'", "delta": "0:00:00.011907", "end": "2025-12-21 22:04:57.332665", "msg": "", "rc": 0, "start": "2025-12-21 22:04:57.320758", "stderr": "", "stderr_lines": [], "stdout": "3%", "stdout_lines": ["3%"]}
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "cmd": "df -h / | tail -1 | awk '{print $5}'", "delta": "0:00:00.016772", "end": "2025-12-21 22:04:57.388120", "msg": "", "rc": 0, "start": "2025-12-21 22:04:57.371348", "stderr": "", "stderr_lines": [], "stdout": "6%", "stdout_lines": ["6%"]}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "cmd": "df -h / | tail -1 | awk '{print $5}'", "delta": "0:00:00.025342", "end": "2025-12-21 22:04:57.991044", "msg": "", "rc": 0, "start": "2025-12-21 22:04:57.965702", "stderr": "", "stderr_lines": [], "stdout": "21%", "stdout_lines": ["21%"]}
|
||||||
|
|
||||||
|
TASK [Get memory usage (Linux)] ************************************************
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "cmd": "if command -v free >/dev/null 2>&1; then\n free -m | grep Mem | awk '{printf \"%.1f%%\", $3/$2 * 100}'\nelse\n # Fallback for systems without free command\n cat /proc/meminfo | awk '/MemTotal/{total=$2} /MemAvailable/{avail=$2} END{printf \"%.1f%%\", (total-avail)/total*100}'\nfi\n", "delta": "0:00:00.010925", "end": "2025-12-21 22:04:58.963435", "msg": "", "rc": 0, "start": "2025-12-21 22:04:58.952510", "stderr": "", "stderr_lines": [], "stdout": "6.8%", "stdout_lines": ["6.8%"]}
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "cmd": "if command -v free >/dev/null 2>&1; then\n free -m | grep Mem | awk '{printf \"%.1f%%\", $3/$2 * 100}'\nelse\n # Fallback for systems without free command\n cat /proc/meminfo | awk '/MemTotal/{total=$2} /MemAvailable/{avail=$2} END{printf \"%.1f%%\", (total-avail)/total*100}'\nfi\n", "delta": "0:00:00.011741", "end": "2025-12-21 22:04:58.999650", "msg": "", "rc": 0, "start": "2025-12-21 22:04:58.987909", "stderr": "", "stderr_lines": [], "stdout": "13.1%", "stdout_lines": ["13.1%"]}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "cmd": "if command -v free >/dev/null 2>&1; then\n free -m | grep Mem | awk '{printf \"%.1f%%\", $3/$2 * 100}'\nelse\n # Fallback for systems without free command\n cat /proc/meminfo | awk '/MemTotal/{total=$2} /MemAvailable/{avail=$2} END{printf \"%.1f%%\", (total-avail)/total*100}'\nfi\n", "delta": "0:00:00.025349", "end": "2025-12-21 22:04:59.630074", "msg": "", "rc": 0, "start": "2025-12-21 22:04:59.604725", "stderr": "", "stderr_lines": [], "stdout": "21.0%", "stdout_lines": ["21.0%"]}
|
||||||
|
|
||||||
|
TASK [Get CPU temperature (ARM/SBC)] *******************************************
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "cmd": "if [ -f /sys/class/thermal/thermal_zone0/temp ]; then\n temp=$(cat /sys/class/thermal/thermal_zone0/temp)\n # Use awk instead of bc for better compatibility\n echo \"${temp}\" | awk '{printf \"%.1f°C\", $1/1000}'\nelse\n echo \"N/A\"\nfi\n", "delta": "0:00:00.011633", "end": "2025-12-21 22:05:00.568277", "msg": "", "rc": 0, "start": "2025-12-21 22:05:00.556644", "stderr": "", "stderr_lines": [], "stdout": "31.6°C", "stdout_lines": ["31.6°C"]}
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "cmd": "if [ -f /sys/class/thermal/thermal_zone0/temp ]; then\n temp=$(cat /sys/class/thermal/thermal_zone0/temp)\n # Use awk instead of bc for better compatibility\n echo \"${temp}\" | awk '{printf \"%.1f°C\", $1/1000}'\nelse\n echo \"N/A\"\nfi\n", "delta": "0:00:00.012898", "end": "2025-12-21 22:05:00.623881", "msg": "", "rc": 0, "start": "2025-12-21 22:05:00.610983", "stderr": "", "stderr_lines": [], "stdout": "35.5°C", "stdout_lines": ["35.5°C"]}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "cmd": "if [ -f /sys/class/thermal/thermal_zone0/temp ]; then\n temp=$(cat /sys/class/thermal/thermal_zone0/temp)\n # Use awk instead of bc for better compatibility\n echo \"${temp}\" | awk '{printf \"%.1f°C\", $1/1000}'\nelse\n echo \"N/A\"\nfi\n", "delta": "0:00:00.028697", "end": "2025-12-21 22:05:01.252436", "msg": "", "rc": 0, "start": "2025-12-21 22:05:01.223739", "stderr": "", "stderr_lines": [], "stdout": "35.7°C", "stdout_lines": ["35.7°C"]}
|
||||||
|
|
||||||
|
TASK [Get CPU load] ************************************************************
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "cmd": "if [ -f /proc/loadavg ]; then\n cat /proc/loadavg | awk '{print $1}'\nelse\n uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | tr -d ' '\nfi\n", "delta": "0:00:00.010036", "end": "2025-12-21 22:05:02.249747", "msg": "", "rc": 0, "start": "2025-12-21 22:05:02.239711", "stderr": "", "stderr_lines": [], "stdout": "0.13", "stdout_lines": ["0.13"]}
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "cmd": "if [ -f /proc/loadavg ]; then\n cat /proc/loadavg | awk '{print $1}'\nelse\n uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | tr -d ' '\nfi\n", "delta": "0:00:00.011128", "end": "2025-12-21 22:05:02.279561", "msg": "", "rc": 0, "start": "2025-12-21 22:05:02.268433", "stderr": "", "stderr_lines": [], "stdout": "0.31", "stdout_lines": ["0.31"]}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "cmd": "if [ -f /proc/loadavg ]; then\n cat /proc/loadavg | awk '{print $1}'\nelse\n uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | tr -d ' '\nfi\n", "delta": "0:00:00.024624", "end": "2025-12-21 22:05:02.902166", "msg": "", "rc": 0, "start": "2025-12-21 22:05:02.877542", "stderr": "", "stderr_lines": [], "stdout": "0.34", "stdout_lines": ["0.34"]}
|
||||||
|
|
||||||
|
TASK [Display health status] ***************************************************
|
||||||
|
ok: [orangepi.pc.home] => {
|
||||||
|
"msg": "═══════════════════════════════════════\nHost: orangepi.pc.home\nStatus: OK\n═══════════════════════════════════════\nUptime: 22:04:56 up 19 days, 11:31, 1 user, load average: 0.29, 0.16, 0.11\nDisk Usage: 21%\nMemory Usage: 21.0%\nCPU Load: 0.34\nCPU Temp: 35.7°C\n═══════════════════════════════════════\n"
|
||||||
|
}
|
||||||
|
ok: [raspi.4gb.home] => {
|
||||||
|
"msg": "═══════════════════════════════════════\nHost: raspi.4gb.home\nStatus: OK\n═══════════════════════════════════════\nUptime: 22:04:55 up 198 days, 13:37, 1 user, load average: 0.34, 0.23, 0.20\nDisk Usage: 6%\nMemory Usage: 13.1%\nCPU Load: 0.31\nCPU Temp: 35.5°C\n═══════════════════════════════════════\n"
|
||||||
|
}
|
||||||
|
ok: [raspi.8gb.home] => {
|
||||||
|
"msg": "═══════════════════════════════════════\nHost: raspi.8gb.home\nStatus: OK\n═══════════════════════════════════════\nUptime: 22:04:55 up 198 days, 13:37, 1 user, load average: 0.14, 0.16, 0.11\nDisk Usage: 3%\nMemory Usage: 6.8%\nCPU Load: 0.13\nCPU Temp: 31.6°C\n═══════════════════════════════════════\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
PLAY RECAP *********************************************************************
|
||||||
|
orangepi.pc.home : ok=8 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
||||||
|
raspi.4gb.home : ok=8 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
||||||
|
raspi.8gb.home : ok=8 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
*Généré automatiquement par Homelab Automation Dashboard*
|
||||||
|
*Date: 2025-12-22T03:05:04.836981+00:00*
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
# ✅ Playbook: Health Check
|
||||||
|
|
||||||
|
## Informations
|
||||||
|
|
||||||
|
| Propriété | Valeur |
|
||||||
|
|-----------|--------|
|
||||||
|
| **ID** | `pb_56aa2f1702ef` |
|
||||||
|
| **Nom** | Playbook: Health Check |
|
||||||
|
| **Cible** | `role_sbc` |
|
||||||
|
| **Statut** | completed |
|
||||||
|
| **Type** | Manuel |
|
||||||
|
| **Progression** | 100% |
|
||||||
|
| **Début** | 2025-12-22T03:22:48.526773+00:00 |
|
||||||
|
| **Fin** | 2025-12-22T03:23:09.484672+00:00 |
|
||||||
|
| **Durée** | 21.6s |
|
||||||
|
|
||||||
|
## Sortie
|
||||||
|
|
||||||
|
```
|
||||||
|
Using /mnt/c/dev/git/python/homelab-automation-api-v2/ansible/ansible.cfg as config file
|
||||||
|
|
||||||
|
PLAY [Health check on target host] *********************************************
|
||||||
|
|
||||||
|
TASK [Check if host is reachable (ping)] ***************************************
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "ping": "pong"}
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "ping": "pong"}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "ping": "pong"}
|
||||||
|
|
||||||
|
TASK [Gather minimal facts] ****************************************************
|
||||||
|
ok: [raspi.8gb.home]
|
||||||
|
ok: [raspi.4gb.home]
|
||||||
|
ok: [orangepi.pc.home]
|
||||||
|
|
||||||
|
TASK [Get system uptime] *******************************************************
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "cmd": ["uptime"], "delta": "0:00:00.008335", "end": "2025-12-21 22:23:01.161234", "msg": "", "rc": 0, "start": "2025-12-21 22:23:01.152899", "stderr": "", "stderr_lines": [], "stdout": " 22:23:01 up 198 days, 13:56, 1 user, load average: 0.17, 0.19, 0.14", "stdout_lines": [" 22:23:01 up 198 days, 13:56, 1 user, load average: 0.17, 0.19, 0.14"]}
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "cmd": ["uptime"], "delta": "0:00:00.009265", "end": "2025-12-21 22:23:01.206222", "msg": "", "rc": 0, "start": "2025-12-21 22:23:01.196957", "stderr": "", "stderr_lines": [], "stdout": " 22:23:01 up 198 days, 13:55, 1 user, load average: 0.23, 0.22, 0.22", "stdout_lines": [" 22:23:01 up 198 days, 13:55, 1 user, load average: 0.23, 0.22, 0.22"]}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "cmd": ["uptime"], "delta": "0:00:00.019386", "end": "2025-12-21 22:23:01.861536", "msg": "", "rc": 0, "start": "2025-12-21 22:23:01.842150", "stderr": "", "stderr_lines": [], "stdout": " 22:23:01 up 19 days, 11:49, 1 user, load average: 0.33, 0.18, 0.15", "stdout_lines": [" 22:23:01 up 19 days, 11:49, 1 user, load average: 0.33, 0.18, 0.15"]}
|
||||||
|
|
||||||
|
TASK [Get disk usage] **********************************************************
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "cmd": "df -h / | tail -1 | awk '{print $5}'", "delta": "0:00:00.010654", "end": "2025-12-21 22:23:02.880120", "msg": "", "rc": 0, "start": "2025-12-21 22:23:02.869466", "stderr": "", "stderr_lines": [], "stdout": "3%", "stdout_lines": ["3%"]}
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "cmd": "df -h / | tail -1 | awk '{print $5}'", "delta": "0:00:00.011445", "end": "2025-12-21 22:23:02.893955", "msg": "", "rc": 0, "start": "2025-12-21 22:23:02.882510", "stderr": "", "stderr_lines": [], "stdout": "6%", "stdout_lines": ["6%"]}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "cmd": "df -h / | tail -1 | awk '{print $5}'", "delta": "0:00:00.025622", "end": "2025-12-21 22:23:03.531944", "msg": "", "rc": 0, "start": "2025-12-21 22:23:03.506322", "stderr": "", "stderr_lines": [], "stdout": "21%", "stdout_lines": ["21%"]}
|
||||||
|
|
||||||
|
TASK [Get memory usage (Linux)] ************************************************
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "cmd": "if command -v free >/dev/null 2>&1; then\n free -m | grep Mem | awk '{printf \"%.1f%%\", $3/$2 * 100}'\nelse\n # Fallback for systems without free command\n cat /proc/meminfo | awk '/MemTotal/{total=$2} /MemAvailable/{avail=$2} END{printf \"%.1f%%\", (total-avail)/total*100}'\nfi\n", "delta": "0:00:00.010846", "end": "2025-12-21 22:23:04.571534", "msg": "", "rc": 0, "start": "2025-12-21 22:23:04.560688", "stderr": "", "stderr_lines": [], "stdout": "6.8%", "stdout_lines": ["6.8%"]}
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "cmd": "if command -v free >/dev/null 2>&1; then\n free -m | grep Mem | awk '{printf \"%.1f%%\", $3/$2 * 100}'\nelse\n # Fallback for systems without free command\n cat /proc/meminfo | awk '/MemTotal/{total=$2} /MemAvailable/{avail=$2} END{printf \"%.1f%%\", (total-avail)/total*100}'\nfi\n", "delta": "0:00:00.011902", "end": "2025-12-21 22:23:04.615751", "msg": "", "rc": 0, "start": "2025-12-21 22:23:04.603849", "stderr": "", "stderr_lines": [], "stdout": "12.9%", "stdout_lines": ["12.9%"]}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "cmd": "if command -v free >/dev/null 2>&1; then\n free -m | grep Mem | awk '{printf \"%.1f%%\", $3/$2 * 100}'\nelse\n # Fallback for systems without free command\n cat /proc/meminfo | awk '/MemTotal/{total=$2} /MemAvailable/{avail=$2} END{printf \"%.1f%%\", (total-avail)/total*100}'\nfi\n", "delta": "0:00:00.025546", "end": "2025-12-21 22:23:05.146668", "msg": "", "rc": 0, "start": "2025-12-21 22:23:05.121122", "stderr": "", "stderr_lines": [], "stdout": "21.3%", "stdout_lines": ["21.3%"]}
|
||||||
|
|
||||||
|
TASK [Get CPU temperature (ARM/SBC)] *******************************************
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "cmd": "if [ -f /sys/class/thermal/thermal_zone0/temp ]; then\n temp=$(cat /sys/class/thermal/thermal_zone0/temp)\n # Use awk instead of bc for better compatibility\n echo \"${temp}\" | awk '{printf \"%.1f°C\", $1/1000}'\nelse\n echo \"N/A\"\nfi\n", "delta": "0:00:00.012031", "end": "2025-12-21 22:23:06.111557", "msg": "", "rc": 0, "start": "2025-12-21 22:23:06.099526", "stderr": "", "stderr_lines": [], "stdout": "31.2°C", "stdout_lines": ["31.2°C"]}
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "cmd": "if [ -f /sys/class/thermal/thermal_zone0/temp ]; then\n temp=$(cat /sys/class/thermal/thermal_zone0/temp)\n # Use awk instead of bc for better compatibility\n echo \"${temp}\" | awk '{printf \"%.1f°C\", $1/1000}'\nelse\n echo \"N/A\"\nfi\n", "delta": "0:00:00.012934", "end": "2025-12-21 22:23:06.136283", "msg": "", "rc": 0, "start": "2025-12-21 22:23:06.123349", "stderr": "", "stderr_lines": [], "stdout": "36.0°C", "stdout_lines": ["36.0°C"]}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "cmd": "if [ -f /sys/class/thermal/thermal_zone0/temp ]; then\n temp=$(cat /sys/class/thermal/thermal_zone0/temp)\n # Use awk instead of bc for better compatibility\n echo \"${temp}\" | awk '{printf \"%.1f°C\", $1/1000}'\nelse\n echo \"N/A\"\nfi\n", "delta": "0:00:00.028299", "end": "2025-12-21 22:23:06.778035", "msg": "", "rc": 0, "start": "2025-12-21 22:23:06.749736", "stderr": "", "stderr_lines": [], "stdout": "34.7°C", "stdout_lines": ["34.7°C"]}
|
||||||
|
|
||||||
|
TASK [Get CPU load] ************************************************************
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "cmd": "if [ -f /proc/loadavg ]; then\n cat /proc/loadavg | awk '{print $1}'\nelse\n uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | tr -d ' '\nfi\n", "delta": "0:00:00.011173", "end": "2025-12-21 22:23:07.826571", "msg": "", "rc": 0, "start": "2025-12-21 22:23:07.815398", "stderr": "", "stderr_lines": [], "stdout": "0.29", "stdout_lines": ["0.29"]}
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "cmd": "if [ -f /proc/loadavg ]; then\n cat /proc/loadavg | awk '{print $1}'\nelse\n uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | tr -d ' '\nfi\n", "delta": "0:00:00.009990", "end": "2025-12-21 22:23:07.825103", "msg": "", "rc": 0, "start": "2025-12-21 22:23:07.815113", "stderr": "", "stderr_lines": [], "stdout": "0.16", "stdout_lines": ["0.16"]}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "cmd": "if [ -f /proc/loadavg ]; then\n cat /proc/loadavg | awk '{print $1}'\nelse\n uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | tr -d ' '\nfi\n", "delta": "0:00:00.024916", "end": "2025-12-21 22:23:08.474920", "msg": "", "rc": 0, "start": "2025-12-21 22:23:08.450004", "stderr": "", "stderr_lines": [], "stdout": "0.38", "stdout_lines": ["0.38"]}
|
||||||
|
|
||||||
|
TASK [Display health status] ***************************************************
|
||||||
|
ok: [orangepi.pc.home] => {
|
||||||
|
"msg": "═══════════════════════════════════════\nHost: orangepi.pc.home\nStatus: OK\n═══════════════════════════════════════\nUptime: 22:23:01 up 19 days, 11:49, 1 user, load average: 0.33, 0.18, 0.15\nDisk Usage: 21%\nMemory Usage: 21.3%\nCPU Load: 0.38\nCPU Temp: 34.7°C\n═══════════════════════════════════════\n"
|
||||||
|
}
|
||||||
|
ok: [raspi.4gb.home] => {
|
||||||
|
"msg": "═══════════════════════════════════════\nHost: raspi.4gb.home\nStatus: OK\n═══════════════════════════════════════\nUptime: 22:23:01 up 198 days, 13:55, 1 user, load average: 0.23, 0.22, 0.22\nDisk Usage: 6%\nMemory Usage: 12.9%\nCPU Load: 0.29\nCPU Temp: 36.0°C\n═══════════════════════════════════════\n"
|
||||||
|
}
|
||||||
|
ok: [raspi.8gb.home] => {
|
||||||
|
"msg": "═══════════════════════════════════════\nHost: raspi.8gb.home\nStatus: OK\n═══════════════════════════════════════\nUptime: 22:23:01 up 198 days, 13:56, 1 user, load average: 0.17, 0.19, 0.14\nDisk Usage: 3%\nMemory Usage: 6.8%\nCPU Load: 0.16\nCPU Temp: 31.2°C\n═══════════════════════════════════════\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
PLAY RECAP *********************************************************************
|
||||||
|
orangepi.pc.home : ok=8 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
||||||
|
raspi.4gb.home : ok=8 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
||||||
|
raspi.8gb.home : ok=8 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
*Généré automatiquement par Homelab Automation Dashboard*
|
||||||
|
*Date: 2025-12-22T03:23:09.508193+00:00*
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
# ✅ Playbook: Health Check
|
||||||
|
|
||||||
|
## Informations
|
||||||
|
|
||||||
|
| Propriété | Valeur |
|
||||||
|
|-----------|--------|
|
||||||
|
| **ID** | `pb_63960fea2661` |
|
||||||
|
| **Nom** | Playbook: Health Check |
|
||||||
|
| **Cible** | `role_sbc` |
|
||||||
|
| **Statut** | completed |
|
||||||
|
| **Type** | Manuel |
|
||||||
|
| **Progression** | 100% |
|
||||||
|
| **Début** | 2025-12-22T15:26:37.036654+00:00 |
|
||||||
|
| **Fin** | 2025-12-22T15:27:00.563786+00:00 |
|
||||||
|
| **Durée** | 24.5s |
|
||||||
|
|
||||||
|
## Sortie
|
||||||
|
|
||||||
|
```
|
||||||
|
Using /mnt/c/dev/git/python/homelab-automation-api-v2/ansible/ansible.cfg as config file
|
||||||
|
|
||||||
|
PLAY [Health check on target host] *********************************************
|
||||||
|
|
||||||
|
TASK [Check if host is reachable (ping)] ***************************************
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "ping": "pong"}
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "ping": "pong"}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "ping": "pong"}
|
||||||
|
|
||||||
|
TASK [Gather minimal facts] ****************************************************
|
||||||
|
ok: [raspi.8gb.home]
|
||||||
|
ok: [raspi.4gb.home]
|
||||||
|
ok: [orangepi.pc.home]
|
||||||
|
|
||||||
|
TASK [Get system uptime] *******************************************************
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "cmd": ["uptime"], "delta": "0:00:00.008279", "end": "2025-12-22 10:26:50.756667", "msg": "", "rc": 0, "start": "2025-12-22 10:26:50.748388", "stderr": "", "stderr_lines": [], "stdout": " 10:26:50 up 199 days, 1:59, 1 user, load average: 0.03, 0.07, 0.08", "stdout_lines": [" 10:26:50 up 199 days, 1:59, 1 user, load average: 0.03, 0.07, 0.08"]}
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "cmd": ["uptime"], "delta": "0:00:00.009115", "end": "2025-12-22 10:26:50.810590", "msg": "", "rc": 0, "start": "2025-12-22 10:26:50.801475", "stderr": "", "stderr_lines": [], "stdout": " 10:26:50 up 199 days, 1:59, 1 user, load average: 0.20, 0.22, 0.19", "stdout_lines": [" 10:26:50 up 199 days, 1:59, 1 user, load average: 0.20, 0.22, 0.19"]}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "cmd": ["uptime"], "delta": "0:00:00.019959", "end": "2025-12-22 10:26:51.484561", "msg": "", "rc": 0, "start": "2025-12-22 10:26:51.464602", "stderr": "", "stderr_lines": [], "stdout": " 10:26:51 up 19 days, 23:53, 1 user, load average: 0.16, 0.09, 0.03", "stdout_lines": [" 10:26:51 up 19 days, 23:53, 1 user, load average: 0.16, 0.09, 0.03"]}
|
||||||
|
|
||||||
|
TASK [Get disk usage] **********************************************************
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "cmd": "df -h / | tail -1 | awk '{print $5}'", "delta": "0:00:00.011372", "end": "2025-12-22 10:26:52.656174", "msg": "", "rc": 0, "start": "2025-12-22 10:26:52.644802", "stderr": "", "stderr_lines": [], "stdout": "6%", "stdout_lines": ["6%"]}
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "cmd": "df -h / | tail -1 | awk '{print $5}'", "delta": "0:00:00.010592", "end": "2025-12-22 10:26:52.660537", "msg": "", "rc": 0, "start": "2025-12-22 10:26:52.649945", "stderr": "", "stderr_lines": [], "stdout": "3%", "stdout_lines": ["3%"]}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "cmd": "df -h / | tail -1 | awk '{print $5}'", "delta": "0:00:00.025473", "end": "2025-12-22 10:26:53.236551", "msg": "", "rc": 0, "start": "2025-12-22 10:26:53.211078", "stderr": "", "stderr_lines": [], "stdout": "21%", "stdout_lines": ["21%"]}
|
||||||
|
|
||||||
|
TASK [Get memory usage (Linux)] ************************************************
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "cmd": "if command -v free >/dev/null 2>&1; then\n free -m | grep Mem | awk '{printf \"%.1f%%\", $3/$2 * 100}'\nelse\n # Fallback for systems without free command\n cat /proc/meminfo | awk '/MemTotal/{total=$2} /MemAvailable/{avail=$2} END{printf \"%.1f%%\", (total-avail)/total*100}'\nfi\n", "delta": "0:00:00.011943", "end": "2025-12-22 10:26:54.363957", "msg": "", "rc": 0, "start": "2025-12-22 10:26:54.352014", "stderr": "", "stderr_lines": [], "stdout": "13.0%", "stdout_lines": ["13.0%"]}
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "cmd": "if command -v free >/dev/null 2>&1; then\n free -m | grep Mem | awk '{printf \"%.1f%%\", $3/$2 * 100}'\nelse\n # Fallback for systems without free command\n cat /proc/meminfo | awk '/MemTotal/{total=$2} /MemAvailable/{avail=$2} END{printf \"%.1f%%\", (total-avail)/total*100}'\nfi\n", "delta": "0:00:00.011536", "end": "2025-12-22 10:26:54.380178", "msg": "", "rc": 0, "start": "2025-12-22 10:26:54.368642", "stderr": "", "stderr_lines": [], "stdout": "6.8%", "stdout_lines": ["6.8%"]}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "cmd": "if command -v free >/dev/null 2>&1; then\n free -m | grep Mem | awk '{printf \"%.1f%%\", $3/$2 * 100}'\nelse\n # Fallback for systems without free command\n cat /proc/meminfo | awk '/MemTotal/{total=$2} /MemAvailable/{avail=$2} END{printf \"%.1f%%\", (total-avail)/total*100}'\nfi\n", "delta": "0:00:00.025869", "end": "2025-12-22 10:26:55.060191", "msg": "", "rc": 0, "start": "2025-12-22 10:26:55.034322", "stderr": "", "stderr_lines": [], "stdout": "19.3%", "stdout_lines": ["19.3%"]}
|
||||||
|
|
||||||
|
TASK [Get CPU temperature (ARM/SBC)] *******************************************
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "cmd": "if [ -f /sys/class/thermal/thermal_zone0/temp ]; then\n temp=$(cat /sys/class/thermal/thermal_zone0/temp)\n # Use awk instead of bc for better compatibility\n echo \"${temp}\" | awk '{printf \"%.1f°C\", $1/1000}'\nelse\n echo \"N/A\"\nfi\n", "delta": "0:00:00.011952", "end": "2025-12-22 10:26:56.056283", "msg": "", "rc": 0, "start": "2025-12-22 10:26:56.044331", "stderr": "", "stderr_lines": [], "stdout": "31.6°C", "stdout_lines": ["31.6°C"]}
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "cmd": "if [ -f /sys/class/thermal/thermal_zone0/temp ]; then\n temp=$(cat /sys/class/thermal/thermal_zone0/temp)\n # Use awk instead of bc for better compatibility\n echo \"${temp}\" | awk '{printf \"%.1f°C\", $1/1000}'\nelse\n echo \"N/A\"\nfi\n", "delta": "0:00:00.012804", "end": "2025-12-22 10:26:56.092442", "msg": "", "rc": 0, "start": "2025-12-22 10:26:56.079638", "stderr": "", "stderr_lines": [], "stdout": "36.0°C", "stdout_lines": ["36.0°C"]}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "cmd": "if [ -f /sys/class/thermal/thermal_zone0/temp ]; then\n temp=$(cat /sys/class/thermal/thermal_zone0/temp)\n # Use awk instead of bc for better compatibility\n echo \"${temp}\" | awk '{printf \"%.1f°C\", $1/1000}'\nelse\n echo \"N/A\"\nfi\n", "delta": "0:00:00.028841", "end": "2025-12-22 10:26:56.739179", "msg": "", "rc": 0, "start": "2025-12-22 10:26:56.710338", "stderr": "", "stderr_lines": [], "stdout": "34.9°C", "stdout_lines": ["34.9°C"]}
|
||||||
|
|
||||||
|
TASK [Get CPU load] ************************************************************
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "cmd": "if [ -f /proc/loadavg ]; then\n cat /proc/loadavg | awk '{print $1}'\nelse\n uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | tr -d ' '\nfi\n", "delta": "0:00:00.010256", "end": "2025-12-22 10:26:58.000512", "msg": "", "rc": 0, "start": "2025-12-22 10:26:57.990256", "stderr": "", "stderr_lines": [], "stdout": "0.02", "stdout_lines": ["0.02"]}
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "cmd": "if [ -f /proc/loadavg ]; then\n cat /proc/loadavg | awk '{print $1}'\nelse\n uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | tr -d ' '\nfi\n", "delta": "0:00:00.011106", "end": "2025-12-22 10:26:58.003816", "msg": "", "rc": 0, "start": "2025-12-22 10:26:57.992710", "stderr": "", "stderr_lines": [], "stdout": "0.26", "stdout_lines": ["0.26"]}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "cmd": "if [ -f /proc/loadavg ]; then\n cat /proc/loadavg | awk '{print $1}'\nelse\n uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | tr -d ' '\nfi\n", "delta": "0:00:00.025269", "end": "2025-12-22 10:26:58.537020", "msg": "", "rc": 0, "start": "2025-12-22 10:26:58.511751", "stderr": "", "stderr_lines": [], "stdout": "0.23", "stdout_lines": ["0.23"]}
|
||||||
|
|
||||||
|
TASK [Display health status] ***************************************************
|
||||||
|
ok: [orangepi.pc.home] => {
|
||||||
|
"msg": "═══════════════════════════════════════\nHost: orangepi.pc.home\nStatus: OK\n═══════════════════════════════════════\nUptime: 10:26:51 up 19 days, 23:53, 1 user, load average: 0.16, 0.09, 0.03\nDisk Usage: 21%\nMemory Usage: 19.3%\nCPU Load: 0.23\nCPU Temp: 34.9°C\n═══════════════════════════════════════\n"
|
||||||
|
}
|
||||||
|
ok: [raspi.4gb.home] => {
|
||||||
|
"msg": "═══════════════════════════════════════\nHost: raspi.4gb.home\nStatus: OK\n═══════════════════════════════════════\nUptime: 10:26:50 up 199 days, 1:59, 1 user, load average: 0.20, 0.22, 0.19\nDisk Usage: 6%\nMemory Usage: 13.0%\nCPU Load: 0.26\nCPU Temp: 36.0°C\n═══════════════════════════════════════\n"
|
||||||
|
}
|
||||||
|
ok: [raspi.8gb.home] => {
|
||||||
|
"msg": "═══════════════════════════════════════\nHost: raspi.8gb.home\nStatus: OK\n═══════════════════════════════════════\nUptime: 10:26:50 up 199 days, 1:59, 1 user, load average: 0.03, 0.07, 0.08\nDisk Usage: 3%\nMemory Usage: 6.8%\nCPU Load: 0.02\nCPU Temp: 31.6°C\n═══════════════════════════════════════\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
PLAY RECAP *********************************************************************
|
||||||
|
orangepi.pc.home : ok=8 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
||||||
|
raspi.4gb.home : ok=8 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
||||||
|
raspi.8gb.home : ok=8 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
*Généré automatiquement par Homelab Automation Dashboard*
|
||||||
|
*Date: 2025-12-22T15:27:00.582682+00:00*
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
# ✅ Playbook: Health Check
|
||||||
|
|
||||||
|
## Informations
|
||||||
|
|
||||||
|
| Propriété | Valeur |
|
||||||
|
|-----------|--------|
|
||||||
|
| **ID** | `pb_12d48ce532b9` |
|
||||||
|
| **Nom** | Playbook: Health Check |
|
||||||
|
| **Cible** | `role_sbc` |
|
||||||
|
| **Statut** | completed |
|
||||||
|
| **Type** | Manuel |
|
||||||
|
| **Progression** | 100% |
|
||||||
|
| **Début** | 2025-12-22T15:39:19.302097+00:00 |
|
||||||
|
| **Fin** | 2025-12-22T15:39:37.816483+00:00 |
|
||||||
|
| **Durée** | 20.2s |
|
||||||
|
|
||||||
|
## Sortie
|
||||||
|
|
||||||
|
```
|
||||||
|
Using /mnt/c/dev/git/python/homelab-automation-api-v2/ansible/ansible.cfg as config file
|
||||||
|
|
||||||
|
PLAY [Health check on target host] *********************************************
|
||||||
|
|
||||||
|
TASK [Check if host is reachable (ping)] ***************************************
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "ping": "pong"}
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "ping": "pong"}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "ping": "pong"}
|
||||||
|
|
||||||
|
TASK [Gather minimal facts] ****************************************************
|
||||||
|
ok: [raspi.8gb.home]
|
||||||
|
ok: [raspi.4gb.home]
|
||||||
|
ok: [orangepi.pc.home]
|
||||||
|
|
||||||
|
TASK [Get system uptime] *******************************************************
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "cmd": ["uptime"], "delta": "0:00:00.009323", "end": "2025-12-22 10:39:29.431125", "msg": "", "rc": 0, "start": "2025-12-22 10:39:29.421802", "stderr": "", "stderr_lines": [], "stdout": " 10:39:29 up 199 days, 2:12, 1 user, load average: 0.24, 0.23, 0.23", "stdout_lines": [" 10:39:29 up 199 days, 2:12, 1 user, load average: 0.24, 0.23, 0.23"]}
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "cmd": ["uptime"], "delta": "0:00:00.008434", "end": "2025-12-22 10:39:29.468651", "msg": "", "rc": 0, "start": "2025-12-22 10:39:29.460217", "stderr": "", "stderr_lines": [], "stdout": " 10:39:29 up 199 days, 2:12, 1 user, load average: 0.38, 0.16, 0.11", "stdout_lines": [" 10:39:29 up 199 days, 2:12, 1 user, load average: 0.38, 0.16, 0.11"]}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "cmd": ["uptime"], "delta": "0:00:00.019670", "end": "2025-12-22 10:39:30.099522", "msg": "", "rc": 0, "start": "2025-12-22 10:39:30.079852", "stderr": "", "stderr_lines": [], "stdout": " 10:39:30 up 20 days, 6 min, 1 user, load average: 0.22, 0.09, 0.05", "stdout_lines": [" 10:39:30 up 20 days, 6 min, 1 user, load average: 0.22, 0.09, 0.05"]}
|
||||||
|
|
||||||
|
TASK [Get disk usage] **********************************************************
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "cmd": "df -h / | tail -1 | awk '{print $5}'", "delta": "0:00:00.010719", "end": "2025-12-22 10:39:30.963692", "msg": "", "rc": 0, "start": "2025-12-22 10:39:30.952973", "stderr": "", "stderr_lines": [], "stdout": "3%", "stdout_lines": ["3%"]}
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "cmd": "df -h / | tail -1 | awk '{print $5}'", "delta": "0:00:00.011443", "end": "2025-12-22 10:39:31.007648", "msg": "", "rc": 0, "start": "2025-12-22 10:39:30.996205", "stderr": "", "stderr_lines": [], "stdout": "6%", "stdout_lines": ["6%"]}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "cmd": "df -h / | tail -1 | awk '{print $5}'", "delta": "0:00:00.025340", "end": "2025-12-22 10:39:31.678391", "msg": "", "rc": 0, "start": "2025-12-22 10:39:31.653051", "stderr": "", "stderr_lines": [], "stdout": "21%", "stdout_lines": ["21%"]}
|
||||||
|
|
||||||
|
TASK [Get memory usage (Linux)] ************************************************
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "cmd": "if command -v free >/dev/null 2>&1; then\n free -m | grep Mem | awk '{printf \"%.1f%%\", $3/$2 * 100}'\nelse\n # Fallback for systems without free command\n cat /proc/meminfo | awk '/MemTotal/{total=$2} /MemAvailable/{avail=$2} END{printf \"%.1f%%\", (total-avail)/total*100}'\nfi\n", "delta": "0:00:00.010884", "end": "2025-12-22 10:39:32.611655", "msg": "", "rc": 0, "start": "2025-12-22 10:39:32.600771", "stderr": "", "stderr_lines": [], "stdout": "6.7%", "stdout_lines": ["6.7%"]}
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "cmd": "if command -v free >/dev/null 2>&1; then\n free -m | grep Mem | awk '{printf \"%.1f%%\", $3/$2 * 100}'\nelse\n # Fallback for systems without free command\n cat /proc/meminfo | awk '/MemTotal/{total=$2} /MemAvailable/{avail=$2} END{printf \"%.1f%%\", (total-avail)/total*100}'\nfi\n", "delta": "0:00:00.011883", "end": "2025-12-22 10:39:32.721361", "msg": "", "rc": 0, "start": "2025-12-22 10:39:32.709478", "stderr": "", "stderr_lines": [], "stdout": "12.9%", "stdout_lines": ["12.9%"]}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "cmd": "if command -v free >/dev/null 2>&1; then\n free -m | grep Mem | awk '{printf \"%.1f%%\", $3/$2 * 100}'\nelse\n # Fallback for systems without free command\n cat /proc/meminfo | awk '/MemTotal/{total=$2} /MemAvailable/{avail=$2} END{printf \"%.1f%%\", (total-avail)/total*100}'\nfi\n", "delta": "0:00:00.026169", "end": "2025-12-22 10:39:33.300903", "msg": "", "rc": 0, "start": "2025-12-22 10:39:33.274734", "stderr": "", "stderr_lines": [], "stdout": "19.6%", "stdout_lines": ["19.6%"]}
|
||||||
|
|
||||||
|
TASK [Get CPU temperature (ARM/SBC)] *******************************************
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "cmd": "if [ -f /sys/class/thermal/thermal_zone0/temp ]; then\n temp=$(cat /sys/class/thermal/thermal_zone0/temp)\n # Use awk instead of bc for better compatibility\n echo \"${temp}\" | awk '{printf \"%.1f°C\", $1/1000}'\nelse\n echo \"N/A\"\nfi\n", "delta": "0:00:00.018230", "end": "2025-12-22 10:39:34.194334", "msg": "", "rc": 0, "start": "2025-12-22 10:39:34.176104", "stderr": "", "stderr_lines": [], "stdout": "32.6°C", "stdout_lines": ["32.6°C"]}
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "cmd": "if [ -f /sys/class/thermal/thermal_zone0/temp ]; then\n temp=$(cat /sys/class/thermal/thermal_zone0/temp)\n # Use awk instead of bc for better compatibility\n echo \"${temp}\" | awk '{printf \"%.1f°C\", $1/1000}'\nelse\n echo \"N/A\"\nfi\n", "delta": "0:00:00.012969", "end": "2025-12-22 10:39:34.217941", "msg": "", "rc": 0, "start": "2025-12-22 10:39:34.204972", "stderr": "", "stderr_lines": [], "stdout": "36.0°C", "stdout_lines": ["36.0°C"]}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "cmd": "if [ -f /sys/class/thermal/thermal_zone0/temp ]; then\n temp=$(cat /sys/class/thermal/thermal_zone0/temp)\n # Use awk instead of bc for better compatibility\n echo \"${temp}\" | awk '{printf \"%.1f°C\", $1/1000}'\nelse\n echo \"N/A\"\nfi\n", "delta": "0:00:00.028826", "end": "2025-12-22 10:39:34.889991", "msg": "", "rc": 0, "start": "2025-12-22 10:39:34.861165", "stderr": "", "stderr_lines": [], "stdout": "35.7°C", "stdout_lines": ["35.7°C"]}
|
||||||
|
|
||||||
|
TASK [Get CPU load] ************************************************************
|
||||||
|
ok: [raspi.8gb.home] => {"changed": false, "cmd": "if [ -f /proc/loadavg ]; then\n cat /proc/loadavg | awk '{print $1}'\nelse\n uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | tr -d ' '\nfi\n", "delta": "0:00:00.010013", "end": "2025-12-22 10:39:35.806369", "msg": "", "rc": 0, "start": "2025-12-22 10:39:35.796356", "stderr": "", "stderr_lines": [], "stdout": "0.32", "stdout_lines": ["0.32"]}
|
||||||
|
ok: [raspi.4gb.home] => {"changed": false, "cmd": "if [ -f /proc/loadavg ]; then\n cat /proc/loadavg | awk '{print $1}'\nelse\n uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | tr -d ' '\nfi\n", "delta": "0:00:00.010938", "end": "2025-12-22 10:39:35.852912", "msg": "", "rc": 0, "start": "2025-12-22 10:39:35.841974", "stderr": "", "stderr_lines": [], "stdout": "0.20", "stdout_lines": ["0.20"]}
|
||||||
|
ok: [orangepi.pc.home] => {"changed": false, "cmd": "if [ -f /proc/loadavg ]; then\n cat /proc/loadavg | awk '{print $1}'\nelse\n uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | tr -d ' '\nfi\n", "delta": "0:00:00.024336", "end": "2025-12-22 10:39:36.528145", "msg": "", "rc": 0, "start": "2025-12-22 10:39:36.503809", "stderr": "", "stderr_lines": [], "stdout": "0.29", "stdout_lines": ["0.29"]}
|
||||||
|
|
||||||
|
TASK [Display health status] ***************************************************
|
||||||
|
ok: [orangepi.pc.home] => {
|
||||||
|
"msg": "═══════════════════════════════════════\nHost: orangepi.pc.home\nStatus: OK\n═══════════════════════════════════════\nUptime: 10:39:30 up 20 days, 6 min, 1 user, load average: 0.22, 0.09, 0.05\nDisk Usage: 21%\nMemory Usage: 19.6%\nCPU Load: 0.29\nCPU Temp: 35.7°C\n═══════════════════════════════════════\n"
|
||||||
|
}
|
||||||
|
ok: [raspi.4gb.home] => {
|
||||||
|
"msg": "═══════════════════════════════════════\nHost: raspi.4gb.home\nStatus: OK\n═══════════════════════════════════════\nUptime: 10:39:29 up 199 days, 2:12, 1 user, load average: 0.24, 0.23, 0.23\nDisk Usage: 6%\nMemory Usage: 12.9%\nCPU Load: 0.20\nCPU Temp: 36.0°C\n═══════════════════════════════════════\n"
|
||||||
|
}
|
||||||
|
ok: [raspi.8gb.home] => {
|
||||||
|
"msg": "═══════════════════════════════════════\nHost: raspi.8gb.home\nStatus: OK\n═══════════════════════════════════════\nUptime: 10:39:29 up 199 days, 2:12, 1 user, load average: 0.38, 0.16, 0.11\nDisk Usage: 3%\nMemory Usage: 6.7%\nCPU Load: 0.32\nCPU Temp: 32.6°C\n═══════════════════════════════════════\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
PLAY RECAP *********************************************************************
|
||||||
|
orangepi.pc.home : ok=8 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
||||||
|
raspi.4gb.home : ok=8 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
||||||
|
raspi.8gb.home : ok=8 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
*Généré automatiquement par Homelab Automation Dashboard*
|
||||||
|
*Date: 2025-12-22T15:39:37.826449+00:00*
|
||||||
File diff suppressed because one or more lines are too long
@ -596,6 +596,43 @@ class TestHostsFallback:
|
|||||||
assert isinstance(response.json(), list)
|
assert isinstance(response.json(), list)
|
||||||
|
|
||||||
|
|
||||||
|
class TestHostsInventoryGroupsMerge:
|
||||||
|
"""Tests pour la fusion des groupes depuis l'inventaire Ansible."""
|
||||||
|
|
||||||
|
async def test_list_hosts_merges_inventory_role_groups(
|
||||||
|
self, client: AsyncClient, db_session, host_factory
|
||||||
|
):
|
||||||
|
"""Les groups incluent les role_* depuis l'inventaire."""
|
||||||
|
await host_factory.create(
|
||||||
|
db_session,
|
||||||
|
name="mergegroups.local",
|
||||||
|
ip_address="10.10.10.10",
|
||||||
|
ansible_group="env_prod",
|
||||||
|
)
|
||||||
|
|
||||||
|
from app.schemas.host_api import AnsibleInventoryHost
|
||||||
|
|
||||||
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
||||||
|
mock_ansible.get_hosts_from_inventory.return_value = [
|
||||||
|
AnsibleInventoryHost(
|
||||||
|
name="mergegroups.local",
|
||||||
|
ansible_host="10.10.10.10",
|
||||||
|
group="env_prod",
|
||||||
|
groups=["env_prod", "role_sbc"],
|
||||||
|
vars={},
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
response = await client.get("/api/hosts")
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
hosts = response.json()
|
||||||
|
our_host = next((h for h in hosts if h["name"] == "mergegroups.local"), None)
|
||||||
|
assert our_host is not None
|
||||||
|
assert "env_prod" in our_host["groups"]
|
||||||
|
assert "role_sbc" in our_host["groups"]
|
||||||
|
|
||||||
|
|
||||||
class TestHostToResponse:
|
class TestHostToResponse:
|
||||||
"""Tests pour la fonction _host_to_response."""
|
"""Tests pour la fonction _host_to_response."""
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@ Couvre:
|
|||||||
import pytest
|
import pytest
|
||||||
from unittest.mock import patch, AsyncMock, MagicMock
|
from unittest.mock import patch, AsyncMock, MagicMock
|
||||||
from httpx import AsyncClient
|
from httpx import AsyncClient
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
pytestmark = pytest.mark.unit
|
pytestmark = pytest.mark.unit
|
||||||
|
|
||||||
@ -172,6 +173,358 @@ class TestExecuteTask:
|
|||||||
assert response.status_code in [200, 400, 422]
|
assert response.status_code in [200, 400, 422]
|
||||||
|
|
||||||
|
|
||||||
|
class TestHostHealthStatusUpdate:
|
||||||
|
"""Tests du lien health-check -> MAJ status/last_seen host en base."""
|
||||||
|
|
||||||
|
async def test_health_check_updates_host_in_db(
|
||||||
|
self,
|
||||||
|
async_engine,
|
||||||
|
db_session,
|
||||||
|
host_factory,
|
||||||
|
mock_ws_manager,
|
||||||
|
mock_notification_service,
|
||||||
|
):
|
||||||
|
# Créer un host en base, avec un nom qui correspondra au target
|
||||||
|
host = await host_factory.create(
|
||||||
|
db_session,
|
||||||
|
name="test-host-1.local",
|
||||||
|
ip_address="192.168.1.10",
|
||||||
|
status="unknown",
|
||||||
|
reachable=False,
|
||||||
|
last_seen=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Exécuter le runner directement (le endpoint /api/tasks lance un asyncio.create_task)
|
||||||
|
from app.routes import tasks as tasks_routes
|
||||||
|
|
||||||
|
# Forcer le runner à utiliser le même engine in-memory que le reste des tests
|
||||||
|
from sqlalchemy.ext.asyncio import async_sessionmaker, AsyncSession
|
||||||
|
test_session_maker = async_sessionmaker(
|
||||||
|
async_engine,
|
||||||
|
class_=AsyncSession,
|
||||||
|
expire_on_commit=False,
|
||||||
|
autoflush=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(tasks_routes.ansible_service, "execute_playbook", new=AsyncMock(return_value={
|
||||||
|
"success": True,
|
||||||
|
"return_code": 0,
|
||||||
|
"stdout": "ok",
|
||||||
|
"stderr": "",
|
||||||
|
})), patch.object(tasks_routes, "async_session_maker", test_session_maker):
|
||||||
|
await tasks_routes._execute_task_playbook(
|
||||||
|
task_id="task-health-1",
|
||||||
|
task_name="Vérification de santé",
|
||||||
|
playbook="health-check.yml",
|
||||||
|
target=host.name,
|
||||||
|
extra_vars=None,
|
||||||
|
check_mode=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Recharger depuis la DB et vérifier MAJ
|
||||||
|
from app.crud.host import HostRepository
|
||||||
|
|
||||||
|
async with test_session_maker() as verify_session:
|
||||||
|
repo = HostRepository(verify_session)
|
||||||
|
updated = await repo.get(host.id)
|
||||||
|
assert updated is not None
|
||||||
|
assert updated.status == "online"
|
||||||
|
assert updated.reachable is True
|
||||||
|
assert updated.last_seen is not None
|
||||||
|
assert isinstance(updated.last_seen, datetime)
|
||||||
|
|
||||||
|
async def test_health_check_all_updates_each_host_from_recap(
|
||||||
|
self,
|
||||||
|
async_engine,
|
||||||
|
db_session,
|
||||||
|
host_factory,
|
||||||
|
mock_ws_manager,
|
||||||
|
mock_notification_service,
|
||||||
|
):
|
||||||
|
host1 = await host_factory.create(
|
||||||
|
db_session,
|
||||||
|
name="host1",
|
||||||
|
ip_address="192.168.1.11",
|
||||||
|
status="unknown",
|
||||||
|
reachable=False,
|
||||||
|
last_seen=None,
|
||||||
|
)
|
||||||
|
host2 = await host_factory.create(
|
||||||
|
db_session,
|
||||||
|
name="host2",
|
||||||
|
ip_address="192.168.1.12",
|
||||||
|
status="unknown",
|
||||||
|
reachable=False,
|
||||||
|
last_seen=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
from app.routes import tasks as tasks_routes
|
||||||
|
from sqlalchemy.ext.asyncio import async_sessionmaker, AsyncSession
|
||||||
|
|
||||||
|
test_session_maker = async_sessionmaker(
|
||||||
|
async_engine,
|
||||||
|
class_=AsyncSession,
|
||||||
|
expire_on_commit=False,
|
||||||
|
autoflush=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
stdout = (
|
||||||
|
"PLAY RECAP\n"
|
||||||
|
"host1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\n"
|
||||||
|
"host2 : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(tasks_routes.ansible_service, "execute_playbook", new=AsyncMock(return_value={
|
||||||
|
"success": False,
|
||||||
|
"return_code": 2,
|
||||||
|
"stdout": stdout,
|
||||||
|
"stderr": "",
|
||||||
|
})), patch.object(tasks_routes, "async_session_maker", test_session_maker):
|
||||||
|
await tasks_routes._execute_task_playbook(
|
||||||
|
task_id="task-health-all-1",
|
||||||
|
task_name="Vérification de santé",
|
||||||
|
playbook="health-check.yml",
|
||||||
|
target="all",
|
||||||
|
extra_vars=None,
|
||||||
|
check_mode=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
from app.crud.host import HostRepository
|
||||||
|
|
||||||
|
async with test_session_maker() as verify_session:
|
||||||
|
repo = HostRepository(verify_session)
|
||||||
|
updated1 = await repo.get(host1.id)
|
||||||
|
updated2 = await repo.get(host2.id)
|
||||||
|
|
||||||
|
assert updated1 is not None
|
||||||
|
assert updated1.status == "online"
|
||||||
|
assert updated1.reachable is True
|
||||||
|
assert updated1.last_seen is not None
|
||||||
|
|
||||||
|
assert updated2 is not None
|
||||||
|
assert updated2.status == "offline"
|
||||||
|
assert updated2.reachable is False
|
||||||
|
assert updated2.last_seen is not None
|
||||||
|
|
||||||
|
|
||||||
|
class TestAnsibleExecuteHealthCheckStatusUpdate:
|
||||||
|
async def test_ansible_execute_health_check_updates_hosts_for_role_group(
|
||||||
|
self,
|
||||||
|
client: AsyncClient,
|
||||||
|
db_session,
|
||||||
|
host_factory,
|
||||||
|
):
|
||||||
|
host1 = await host_factory.create(
|
||||||
|
db_session,
|
||||||
|
name="orangepi.pc.home",
|
||||||
|
ip_address="10.10.0.11",
|
||||||
|
status="unknown",
|
||||||
|
reachable=False,
|
||||||
|
last_seen=None,
|
||||||
|
)
|
||||||
|
host2 = await host_factory.create(
|
||||||
|
db_session,
|
||||||
|
name="raspi.4gb.home",
|
||||||
|
ip_address="10.10.0.12",
|
||||||
|
status="unknown",
|
||||||
|
reachable=False,
|
||||||
|
last_seen=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
from app.schemas.host_api import AnsibleInventoryHost
|
||||||
|
|
||||||
|
inv_hosts = [
|
||||||
|
AnsibleInventoryHost(name="orangepi", ansible_host=host1.ip_address, group="env_homelab", groups=["role_sbc", "env_homelab"], vars={}),
|
||||||
|
AnsibleInventoryHost(name="raspi", ansible_host=host2.ip_address, group="env_homelab", groups=["role_sbc", "env_homelab"], vars={}),
|
||||||
|
]
|
||||||
|
|
||||||
|
stdout = (
|
||||||
|
"PLAY RECAP\n"
|
||||||
|
"orangepi : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\n"
|
||||||
|
"raspi : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch("app.routes.ansible.ansible_service.execute_playbook", new=AsyncMock(return_value={
|
||||||
|
"success": False,
|
||||||
|
"return_code": 2,
|
||||||
|
"stdout": stdout,
|
||||||
|
"stderr": "",
|
||||||
|
"execution_time": 1.0,
|
||||||
|
})), patch("app.routes.ansible.ansible_service.get_hosts_from_inventory", new=MagicMock(return_value=inv_hosts)):
|
||||||
|
resp = await client.post(
|
||||||
|
"/api/ansible/execute",
|
||||||
|
json={
|
||||||
|
"playbook": "health-check.yml",
|
||||||
|
"target": "role_sbc",
|
||||||
|
"check_mode": False,
|
||||||
|
"verbose": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
# Verify host statuses are updated via DB session
|
||||||
|
from app.crud.host import HostRepository
|
||||||
|
|
||||||
|
repo = HostRepository(db_session)
|
||||||
|
updated1 = await repo.get(host1.id)
|
||||||
|
updated2 = await repo.get(host2.id)
|
||||||
|
|
||||||
|
assert updated1 is not None and updated1.status == "online" and updated1.reachable is True and updated1.last_seen is not None
|
||||||
|
assert updated2 is not None and updated2.status == "offline" and updated2.reachable is False and updated2.last_seen is not None
|
||||||
|
|
||||||
|
async def test_health_check_group_updates_each_host_from_recap(
|
||||||
|
self,
|
||||||
|
async_engine,
|
||||||
|
db_session,
|
||||||
|
host_factory,
|
||||||
|
mock_ws_manager,
|
||||||
|
mock_notification_service,
|
||||||
|
):
|
||||||
|
host1 = await host_factory.create(
|
||||||
|
db_session,
|
||||||
|
name="group-host-1",
|
||||||
|
ip_address="10.0.0.1",
|
||||||
|
status="unknown",
|
||||||
|
reachable=False,
|
||||||
|
last_seen=None,
|
||||||
|
)
|
||||||
|
host2 = await host_factory.create(
|
||||||
|
db_session,
|
||||||
|
name="group-host-2",
|
||||||
|
ip_address="10.0.0.2",
|
||||||
|
status="unknown",
|
||||||
|
reachable=False,
|
||||||
|
last_seen=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
from app.routes import tasks as tasks_routes
|
||||||
|
from sqlalchemy.ext.asyncio import async_sessionmaker, AsyncSession
|
||||||
|
|
||||||
|
test_session_maker = async_sessionmaker(
|
||||||
|
async_engine,
|
||||||
|
class_=AsyncSession,
|
||||||
|
expire_on_commit=False,
|
||||||
|
autoflush=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
stdout = (
|
||||||
|
"PLAY RECAP\n"
|
||||||
|
"group-host-1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\n"
|
||||||
|
"group-host-2 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(tasks_routes.ansible_service, "execute_playbook", new=AsyncMock(return_value={
|
||||||
|
"success": True,
|
||||||
|
"return_code": 0,
|
||||||
|
"stdout": stdout,
|
||||||
|
"stderr": "",
|
||||||
|
})), patch.object(tasks_routes, "async_session_maker", test_session_maker):
|
||||||
|
await tasks_routes._execute_task_playbook(
|
||||||
|
task_id="task-health-group-1",
|
||||||
|
task_name="Vérification de santé",
|
||||||
|
playbook="health-check.yml",
|
||||||
|
target="env_test",
|
||||||
|
extra_vars=None,
|
||||||
|
check_mode=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
from app.crud.host import HostRepository
|
||||||
|
|
||||||
|
async with test_session_maker() as verify_session:
|
||||||
|
repo = HostRepository(verify_session)
|
||||||
|
updated1 = await repo.get(host1.id)
|
||||||
|
updated2 = await repo.get(host2.id)
|
||||||
|
|
||||||
|
assert updated1 is not None and updated1.status == "online" and updated1.reachable is True and updated1.last_seen is not None
|
||||||
|
assert updated2 is not None and updated2.status == "online" and updated2.reachable is True and updated2.last_seen is not None
|
||||||
|
|
||||||
|
async def test_health_check_group_updates_hosts_when_recap_uses_inventory_alias(
|
||||||
|
self,
|
||||||
|
async_engine,
|
||||||
|
db_session,
|
||||||
|
host_factory,
|
||||||
|
mock_ws_manager,
|
||||||
|
mock_notification_service,
|
||||||
|
):
|
||||||
|
# DB stores hosts by their FQDN/IP, but Ansible recap may print the inventory alias.
|
||||||
|
host1 = await host_factory.create(
|
||||||
|
db_session,
|
||||||
|
name="sbc-01.local",
|
||||||
|
ip_address="10.42.0.11",
|
||||||
|
status="unknown",
|
||||||
|
reachable=False,
|
||||||
|
last_seen=None,
|
||||||
|
)
|
||||||
|
host2 = await host_factory.create(
|
||||||
|
db_session,
|
||||||
|
name="sbc-02.local",
|
||||||
|
ip_address="10.42.0.12",
|
||||||
|
status="unknown",
|
||||||
|
reachable=False,
|
||||||
|
last_seen=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
from app.routes import tasks as tasks_routes
|
||||||
|
from sqlalchemy.ext.asyncio import async_sessionmaker, AsyncSession
|
||||||
|
from app.schemas.host_api import AnsibleInventoryHost
|
||||||
|
|
||||||
|
test_session_maker = async_sessionmaker(
|
||||||
|
async_engine,
|
||||||
|
class_=AsyncSession,
|
||||||
|
expire_on_commit=False,
|
||||||
|
autoflush=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Inventory aliases for the role group "role_sbc"
|
||||||
|
inv_hosts = [
|
||||||
|
AnsibleInventoryHost(name="sbc-01", ansible_host=host1.ip_address, group="env_test", groups=["role_sbc", "env_test"], vars={}),
|
||||||
|
AnsibleInventoryHost(name="sbc-02", ansible_host=host2.ip_address, group="env_test", groups=["role_sbc", "env_test"], vars={}),
|
||||||
|
]
|
||||||
|
|
||||||
|
stdout = (
|
||||||
|
"PLAY RECAP\n"
|
||||||
|
"sbc-01 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0\n"
|
||||||
|
"sbc-02 : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0 ignored=0\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(tasks_routes.ansible_service, "execute_playbook", new=AsyncMock(return_value={
|
||||||
|
"success": False,
|
||||||
|
"return_code": 2,
|
||||||
|
"stdout": stdout,
|
||||||
|
"stderr": "",
|
||||||
|
})), patch.object(tasks_routes.ansible_service, "get_hosts_from_inventory", new=MagicMock(return_value=inv_hosts)), patch.object(
|
||||||
|
tasks_routes,
|
||||||
|
"async_session_maker",
|
||||||
|
test_session_maker,
|
||||||
|
):
|
||||||
|
await tasks_routes._execute_task_playbook(
|
||||||
|
task_id="task-health-role-alias-1",
|
||||||
|
task_name="Vérification de santé",
|
||||||
|
playbook="health-check.yml",
|
||||||
|
target="role_sbc",
|
||||||
|
extra_vars=None,
|
||||||
|
check_mode=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
from app.crud.host import HostRepository
|
||||||
|
|
||||||
|
async with test_session_maker() as verify_session:
|
||||||
|
repo = HostRepository(verify_session)
|
||||||
|
updated1 = await repo.get(host1.id)
|
||||||
|
updated2 = await repo.get(host2.id)
|
||||||
|
|
||||||
|
assert updated1 is not None
|
||||||
|
assert updated1.status == "online"
|
||||||
|
assert updated1.reachable is True
|
||||||
|
assert updated1.last_seen is not None
|
||||||
|
|
||||||
|
assert updated2 is not None
|
||||||
|
assert updated2.status == "offline"
|
||||||
|
assert updated2.reachable is False
|
||||||
|
assert updated2.last_seen is not None
|
||||||
|
|
||||||
|
|
||||||
class TestGetTaskById:
|
class TestGetTaskById:
|
||||||
"""Tests pour GET /api/tasks/{task_id}."""
|
"""Tests pour GET /api/tasks/{task_id}."""
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user