Remove Docker configuration and implementation documentation files, keeping only core application code and essential documentation
This commit is contained in:
parent
3559552eca
commit
5a512d39b5
@ -681,9 +681,23 @@ class TaskLogService:
|
|||||||
"""Retourne le chemin du répertoire pour une date donnée (YYYY/MM/JJ)"""
|
"""Retourne le chemin du répertoire pour une date donnée (YYYY/MM/JJ)"""
|
||||||
if dt is None:
|
if dt is None:
|
||||||
dt = datetime.now(timezone.utc)
|
dt = datetime.now(timezone.utc)
|
||||||
year = dt.strftime("%Y")
|
|
||||||
month = dt.strftime("%m")
|
# IMPORTANT : on utilise le fuseau horaire local (America/Montreal)
|
||||||
day = dt.strftime("%d")
|
# pour déterminer la date du dossier de log, afin que les tâches
|
||||||
|
# exécutées en soirée ne basculent pas au jour suivant à cause de l'UTC.
|
||||||
|
import pytz
|
||||||
|
local_tz = pytz.timezone("America/Montreal")
|
||||||
|
|
||||||
|
if dt.tzinfo is None:
|
||||||
|
# Si la datetime est naïve, on considère qu'elle est déjà en heure locale
|
||||||
|
dt_local = local_tz.localize(dt)
|
||||||
|
else:
|
||||||
|
# Sinon on la convertit dans le fuseau local
|
||||||
|
dt_local = dt.astimezone(local_tz)
|
||||||
|
|
||||||
|
year = dt_local.strftime("%Y")
|
||||||
|
month = dt_local.strftime("%m")
|
||||||
|
day = dt_local.strftime("%d")
|
||||||
return self.base_dir / year / month / day
|
return self.base_dir / year / month / day
|
||||||
|
|
||||||
def _generate_task_id(self) -> str:
|
def _generate_task_id(self) -> str:
|
||||||
|
|||||||
2096
app/index.html
2096
app/index.html
File diff suppressed because it is too large
Load Diff
109
app/index.html_dashboard_temp_rewrite
Normal file
109
app/index.html_dashboard_temp_rewrite
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
|
<!-- Schedules Widget (Moved to top) -->
|
||||||
|
<div class="lg:col-span-2">
|
||||||
|
<div class="glass-card p-6 fade-in">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<h3 class="text-lg font-semibold">
|
||||||
|
<i class="fas fa-calendar-alt text-purple-400 mr-2"></i>Planificateur
|
||||||
|
</h3>
|
||||||
|
<a href="#" data-page="schedules" class="nav-link text-purple-400 text-sm hover:underline">Voir tout →</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stats mini -->
|
||||||
|
<div class="grid grid-cols-3 gap-2 mb-4">
|
||||||
|
<div class="text-center p-2 bg-gray-800/50 rounded">
|
||||||
|
<div class="text-lg font-bold text-green-400" id="dashboard-schedules-active">0</div>
|
||||||
|
<div class="text-xs text-gray-500">Actifs</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center p-2 bg-gray-800/50 rounded">
|
||||||
|
<div class="text-lg font-bold text-blue-400" id="dashboard-schedules-next">--</div>
|
||||||
|
<div class="text-xs text-gray-500">Prochaine</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center p-2 bg-gray-800/50 rounded">
|
||||||
|
<div class="text-lg font-bold text-red-400" id="dashboard-schedules-failures">0</div>
|
||||||
|
<div class="text-xs text-gray-500">Échecs 24h</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Next executions -->
|
||||||
|
<div id="dashboard-upcoming-schedules" class="space-y-2 text-sm">
|
||||||
|
<p class="text-gray-500 text-center py-4">Chargement...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button onclick="showCreateScheduleModal()" class="w-full mt-4 p-2 bg-purple-600 hover:bg-purple-500 rounded-lg text-sm transition-colors">
|
||||||
|
<i class="fas fa-plus mr-2"></i>Nouveau schedule
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Quick Actions -->
|
||||||
|
<div class="glass-card p-6 fade-in">
|
||||||
|
<h3 class="text-xl font-semibold mb-6">Actions Rapides</h3>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<button class="w-full p-4 bg-gradient-to-r from-green-600 to-green-700 rounded-lg text-left hover:from-green-500 hover:to-green-600 transition-all" onclick="executeTask('upgrade-all')">
|
||||||
|
<i class="fas fa-arrow-up mr-3"></i>
|
||||||
|
Mettre à jour tous les hôtes
|
||||||
|
</button>
|
||||||
|
<button class="w-full p-4 bg-gradient-to-r from-blue-600 to-blue-700 rounded-lg text-left hover:from-blue-500 hover:to-blue-600 transition-all" onclick="executeTask('reboot-all')">
|
||||||
|
<i class="fas fa-redo mr-3"></i>
|
||||||
|
Redémarrer les hôtes
|
||||||
|
</button>
|
||||||
|
<button class="w-full p-4 bg-gradient-to-r from-purple-600 to-purple-700 rounded-lg text-left hover:from-purple-500 hover:to-purple-600 transition-all" onclick="executeTask('health-check')">
|
||||||
|
<i class="fas fa-heartbeat mr-3"></i>
|
||||||
|
Vérifier la santé
|
||||||
|
</button>
|
||||||
|
<button class="w-full p-4 bg-gradient-to-r from-orange-600 to-orange-700 rounded-lg text-left hover:from-orange-500 hover:to-orange-600 transition-all" onclick="executeTask('backup')">
|
||||||
|
<i class="fas fa-save mr-3"></i>
|
||||||
|
Sauvegarder la configuration
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Hosts Management (Moved down) -->
|
||||||
|
<div class="lg:col-span-2">
|
||||||
|
<div class="glass-card p-6 fade-in">
|
||||||
|
<div class="flex items-center justify-between mb-6">
|
||||||
|
<h3 class="text-xl font-semibold">Gestion des Hosts</h3>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<button class="btn-primary" onclick="dashboard.addHost()">
|
||||||
|
<i class="fas fa-plus mr-2"></i>
|
||||||
|
Ajouter Host
|
||||||
|
</button>
|
||||||
|
<div class="relative group">
|
||||||
|
<button class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors flex items-center">
|
||||||
|
<i class="fas fa-layer-group mr-2"></i>
|
||||||
|
Groupes
|
||||||
|
<i class="fas fa-chevron-down ml-2 text-xs"></i>
|
||||||
|
</button>
|
||||||
|
<div class="absolute right-0 mt-2 w-56 bg-gray-800 border border-gray-700 rounded-lg shadow-xl opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50">
|
||||||
|
<div class="py-2">
|
||||||
|
<div class="px-4 py-2 text-xs text-gray-400 uppercase tracking-wider border-b border-gray-700">Environnements</div>
|
||||||
|
<button onclick="dashboard.showAddGroupModal('env')" class="w-full px-4 py-2 text-left hover:bg-gray-700 flex items-center">
|
||||||
|
<i class="fas fa-plus text-green-400 mr-3 w-4"></i>
|
||||||
|
Ajouter environnement
|
||||||
|
</button>
|
||||||
|
<button onclick="dashboard.showManageGroupsModal('env')" class="w-full px-4 py-2 text-left hover:bg-gray-700 flex items-center">
|
||||||
|
<i class="fas fa-edit text-blue-400 mr-3 w-4"></i>
|
||||||
|
Gérer environnements
|
||||||
|
</button>
|
||||||
|
<div class="px-4 py-2 text-xs text-gray-400 uppercase tracking-wider border-b border-t border-gray-700 mt-2">Rôles</div>
|
||||||
|
<button onclick="dashboard.showAddGroupModal('role')" class="w-full px-4 py-2 text-left hover:bg-gray-700 flex items-center">
|
||||||
|
<i class="fas fa-plus text-green-400 mr-3 w-4"></i>
|
||||||
|
Ajouter rôle
|
||||||
|
</button>
|
||||||
|
<button onclick="dashboard.showManageGroupsModal('role')" class="w-full px-4 py-2 text-left hover:bg-gray-700 flex items-center">
|
||||||
|
<i class="fas fa-edit text-blue-400 mr-3 w-4"></i>
|
||||||
|
Gérer rôles
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="hosts-list" class="space-y-4">
|
||||||
|
<!-- Hosts will be populated by JavaScript -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Binary file not shown.
Binary file not shown.
81
docker/README.md
Normal file
81
docker/README.md
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# Docker Configuration for Homelab Automation
|
||||||
|
|
||||||
|
Ce dossier contient tous les fichiers nécessaires pour conteneuriser et déployer l'application Homelab Automation API.
|
||||||
|
|
||||||
|
## Structure du dossier
|
||||||
|
|
||||||
|
- **Dockerfile** : Définition de l'image Docker.
|
||||||
|
- **docker-compose.yml** : Définition de la stack (Container API + Volumes).
|
||||||
|
- **.env.example** : Modèle de configuration des variables d'environnement.
|
||||||
|
- **init.sh** : Script d'initialisation de l'environnement local.
|
||||||
|
- **build-img.ps1** : Script PowerShell pour construire l'image (optimisé pour WSL).
|
||||||
|
- **deploy-img.sh / .ps1** : Scripts pour pousser l'image vers un registre Docker privé.
|
||||||
|
- **maj.sh** : Script pour mettre à jour la stack depuis le registre.
|
||||||
|
|
||||||
|
## Prérequis
|
||||||
|
|
||||||
|
- Docker Engine & Docker Compose
|
||||||
|
- Pour Windows : WSL 2 activé (recommandé pour les scripts .ps1)
|
||||||
|
|
||||||
|
## Installation et Démarrage
|
||||||
|
|
||||||
|
### 1. Initialisation
|
||||||
|
|
||||||
|
Lancez le script d'initialisation pour préparer le fichier `.env` et les répertoires de données :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Dans le dossier docker/
|
||||||
|
bash init.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Ensuite, éditez le fichier `.env` généré pour configurer :
|
||||||
|
- `API_KEY` : Votre clé secrète.
|
||||||
|
- `SSH_USER` : L'utilisateur pour les connexions Ansible.
|
||||||
|
- `NTFY_*` : La configuration des notifications (optionnel).
|
||||||
|
|
||||||
|
### 2. Démarrage (Local)
|
||||||
|
|
||||||
|
Pour lancer l'application localement avec Docker Compose :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
L'API sera accessible sur `http://localhost:8008`.
|
||||||
|
|
||||||
|
## Construction et Déploiement (Avancé)
|
||||||
|
|
||||||
|
Si vous utilisez un registre Docker privé (ex: Nexus, Harbor, Registry local), vous pouvez utiliser les scripts de build et de déploiement.
|
||||||
|
|
||||||
|
### Configuration du Registre
|
||||||
|
|
||||||
|
Modifiez les variables dans `.env` (ou laissez les valeurs par défaut si elles conviennent) :
|
||||||
|
- `REGISTRY_HOST`
|
||||||
|
- `REGISTRY_PORT`
|
||||||
|
- `IMAGE_NAME`
|
||||||
|
|
||||||
|
### Construction
|
||||||
|
|
||||||
|
Sous Windows (via PowerShell) :
|
||||||
|
```powershell
|
||||||
|
.\build-img.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Sous Linux :
|
||||||
|
```bash
|
||||||
|
docker build -f Dockerfile -t homelab-automation-api:latest ..
|
||||||
|
```
|
||||||
|
|
||||||
|
### Déploiement
|
||||||
|
|
||||||
|
Pousse l'image vers le registre configuré :
|
||||||
|
```powershell
|
||||||
|
.\deploy-img.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mise à jour
|
||||||
|
|
||||||
|
Sur le serveur de destination, pour tirer la dernière image et redémarrer :
|
||||||
|
```bash
|
||||||
|
./maj.sh
|
||||||
|
```
|
||||||
119
docker/build-img.ps1
Normal file
119
docker/build-img.ps1
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Construit l'image Docker avec toutes les dépendances requises via PowerShell et WSL (Debian).
|
||||||
|
.DESCRIPTION
|
||||||
|
Ce script prépare les dépendances et construit l'image Docker sous WSL (Debian) avec BuildKit activé pour des builds plus rapides.
|
||||||
|
.PARAMETRES
|
||||||
|
-full : Si présent, effectue une construction complète de l'image Docker (équivalent à --no-cache).
|
||||||
|
.EXEMPLE
|
||||||
|
.\build-img.ps1 # Construction rapide (avec cache)
|
||||||
|
.\build-img.ps1 -full # Construction complète sans cache
|
||||||
|
.RETOURNE
|
||||||
|
Affiche le succès ou les erreurs pendant la construction de l'image Docker.
|
||||||
|
#>
|
||||||
|
|
||||||
|
param(
|
||||||
|
[switch]$full
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Determine paths
|
||||||
|
$scriptDir = $PSScriptRoot
|
||||||
|
$projectRoot = (Get-Item (Join-Path $scriptDir '..')).FullName
|
||||||
|
|
||||||
|
# Convert Windows path to WSL path (manual, no wslpath)
|
||||||
|
$wslProjectRoot = $projectRoot -replace '^([A-Za-z]):\\','/mnt/$1/' -replace '\\','/'
|
||||||
|
$wslProjectRoot = $wslProjectRoot.ToLower()
|
||||||
|
|
||||||
|
# Check if Docker is available in WSL Debian
|
||||||
|
Write-Host "Vérification de Docker dans WSL Debian..." -ForegroundColor Cyan
|
||||||
|
$dockerCheck = wsl -d Debian bash -c "docker --version" 2>&1
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "Docker n'est pas disponible dans WSL Debian : $dockerCheck"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Write-Host "Docker détecté : $dockerCheck" -ForegroundColor Green
|
||||||
|
|
||||||
|
# Check if the Dockerfile exists
|
||||||
|
$dockerfilePath = Join-Path $scriptDir "Dockerfile"
|
||||||
|
if (-not (Test-Path $dockerfilePath)) {
|
||||||
|
Write-Error "Le fichier Dockerfile n'existe pas dans $scriptDir"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if .dockerignore exists, warn if not
|
||||||
|
$dockerignorePath = Join-Path $projectRoot ".dockerignore"
|
||||||
|
if (-not (Test-Path $dockerignorePath)) {
|
||||||
|
Write-Warning "Aucun fichier .dockerignore trouvé. Créez-en un pour optimiser le build."
|
||||||
|
Write-Host "Exemple de contenu :" -ForegroundColor Yellow
|
||||||
|
Write-Host @"
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.git
|
||||||
|
.vscode
|
||||||
|
*.log
|
||||||
|
.env
|
||||||
|
"@ -ForegroundColor DarkGray
|
||||||
|
}
|
||||||
|
|
||||||
|
# Compose build command with BuildKit enabled
|
||||||
|
$noCache = if ($full) { '--no-cache' } else { '' }
|
||||||
|
|
||||||
|
# Escape spaces in path for bash
|
||||||
|
$escapedPath = $wslProjectRoot -replace ' ','\\ '
|
||||||
|
|
||||||
|
# Build command parts
|
||||||
|
$cdCmd = "cd `"$wslProjectRoot`""
|
||||||
|
$dockerCmd = "docker build $noCache --progress=plain -t homelab-automation-api:latest -f docker/Dockerfile ."
|
||||||
|
|
||||||
|
# Complete command with BuildKit
|
||||||
|
$innerCmd = "export DOCKER_BUILDKIT=1 && $cdCmd && $dockerCmd"
|
||||||
|
|
||||||
|
# Run build inside WSL Debian to use Linux Docker daemon
|
||||||
|
Write-Host "Construction de l'image Docker homelab-automation-api:latest..." -ForegroundColor Cyan
|
||||||
|
Write-Host "BuildKit activé pour un build optimisé avec cache" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
$buildResult = wsl -d Debian bash -lc $innerCmd 2>&1
|
||||||
|
|
||||||
|
# Display build output
|
||||||
|
$buildResult | ForEach-Object { Write-Host $_ }
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "Erreur lors de la construction de l'image Docker (code: $LASTEXITCODE)"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verify the image was built successfully
|
||||||
|
$verifyCmd = "docker image inspect homelab-automation-api:latest --format='{{.Size}}'"
|
||||||
|
Write-Host "`nVérification de l'image construite..." -ForegroundColor Cyan
|
||||||
|
$imageSize = wsl -d Debian bash -lc $verifyCmd 2>&1
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Error "L'image Docker n'a pas été construite correctement : $imageSize"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Convert size to MB
|
||||||
|
$sizeMB = [math]::Round([long]$imageSize / 1MB, 2)
|
||||||
|
|
||||||
|
Write-Host "`n================================================" -ForegroundColor Green
|
||||||
|
Write-Host "✓ Image Docker construite avec succès" -ForegroundColor Green
|
||||||
|
Write-Host " Nom: homelab-automation-api:latest" -ForegroundColor Green
|
||||||
|
Write-Host " Taille: $sizeMB MB" -ForegroundColor Green
|
||||||
|
Write-Host "================================================" -ForegroundColor Green
|
||||||
|
|
||||||
|
# Display tips for faster rebuilds
|
||||||
|
if (-not $full) {
|
||||||
|
Write-Host "`nConseils pour les prochains builds :" -ForegroundColor Cyan
|
||||||
|
Write-Host " • Les dépendances Python sont mises en cache" -ForegroundColor Gray
|
||||||
|
Write-Host " • Seuls les fichiers modifiés déclencheront un rebuild" -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Error "Erreur lors de la construction de l'image Docker : $_"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
46
docker/deploy-img.ps1
Normal file
46
docker/deploy-img.ps1
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Exécute le script deploy-img.sh dans WSL Debian.
|
||||||
|
.DESCRIPTION
|
||||||
|
Ce script PowerShell lance le script Bash deploy-img.sh dans le répertoire courant sous la distribution WSL Debian.
|
||||||
|
Le script déploie l'image Docker locale vers le registre distant et gère les versions.
|
||||||
|
.PARAMETRES
|
||||||
|
Aucun paramètre requis actuellement.
|
||||||
|
.EXEMPLE
|
||||||
|
.\deploy-img.ps1
|
||||||
|
.RETOURNE
|
||||||
|
Affiche le succès ou les erreurs lors du déploiement de l'image Docker.
|
||||||
|
#>
|
||||||
|
|
||||||
|
param()
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
try {
|
||||||
|
Write-Verbose "[Étape 1] Détermination du répertoire du script."
|
||||||
|
$scriptDir = $PSScriptRoot
|
||||||
|
|
||||||
|
Write-Verbose "[Étape 2] Conversion du chemin Windows vers WSL (manuel, sans wslpath)."
|
||||||
|
# Remplacement manuel du préfixe du chemin Windows par le préfixe WSL
|
||||||
|
$wslScriptDir = $scriptDir -replace '^([A-Za-z]):\\', '/mnt/$1/' -replace '\\','/'
|
||||||
|
$wslScriptDir = $wslScriptDir.ToLower()
|
||||||
|
|
||||||
|
Write-Verbose "[Étape 3] Vérification des permissions du script bash."
|
||||||
|
wsl -d Debian -- chmod +x "$wslScriptDir/deploy-img.sh"
|
||||||
|
|
||||||
|
Write-Verbose "[Étape 4] Construction de la commande Bash à exécuter."
|
||||||
|
$innerCmd = "cd '$wslScriptDir' && ./deploy-img.sh"
|
||||||
|
|
||||||
|
Write-Verbose "[Étape 5] Exécution du script deploy-img.sh dans WSL Debian."
|
||||||
|
wsl -d Debian bash -lc $innerCmd
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "Le script de déploiement a échoué avec le code d'erreur: $LASTEXITCODE"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Déploiement de l'image Docker homelab-automation-api:latest terminé avec succès." -ForegroundColor Green
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Error "Erreur lors de l'exécution du déploiement de l'image Docker : $_"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
247
docker/deploy-img.sh
Normal file
247
docker/deploy-img.sh
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Script: deploy-img.sh
|
||||||
|
# Description: Pousse l'image Docker locale vers un registre HTTP (insecure), gère le versioning semver et la rétention
|
||||||
|
# Date: 2025-04-18
|
||||||
|
# Mise à jour: 2025-09-19 - Vérification stricte du daemon (insecure-registries), compatibilité Windows/Linux
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
#####################################
|
||||||
|
# CONFIG UTILISATEUR #
|
||||||
|
#####################################
|
||||||
|
|
||||||
|
# Charger les variables depuis .env s'il existe
|
||||||
|
if [[ -f ".env" ]]; then
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source .env
|
||||||
|
fi
|
||||||
|
|
||||||
|
IMAGE_NAME="${IMAGE_NAME:-homelab-automation-api}"
|
||||||
|
REGISTRY_HOST="${REGISTRY_HOST:-docker-registry.dev.home}"
|
||||||
|
REGISTRY_PORT="${REGISTRY_PORT:-5000}"
|
||||||
|
MAX_VERSIONS="${MAX_VERSIONS:-5}" # nombre de versions (semver) à conserver
|
||||||
|
LOCAL_TAG="${LOCAL_TAG:-latest}" # tag local source
|
||||||
|
PUSH_LATEST="${PUSH_LATEST:-yes}" # yes/no : pousser aussi :latest
|
||||||
|
|
||||||
|
#####################################
|
||||||
|
# DÉDUCTIONS & CONSTANTES #
|
||||||
|
#####################################
|
||||||
|
REGISTRY="${REGISTRY_HOST}:${REGISTRY_PORT}"
|
||||||
|
REMOTE_REPO="${REGISTRY}/${IMAGE_NAME}"
|
||||||
|
LOCAL_IMAGE="${IMAGE_NAME}:${LOCAL_TAG}"
|
||||||
|
TEMP_DIR="/tmp/docker-deploy-$(date +%s)"
|
||||||
|
CURL="curl -fsSL"
|
||||||
|
JQ_BIN="${JQ_BIN:-jq}" # possibilité de surcharger via env
|
||||||
|
|
||||||
|
# Headers pour obtenir le digest (manifest v2)
|
||||||
|
ACCEPT_MANIFEST='application/vnd.docker.distribution.manifest.v2+json'
|
||||||
|
|
||||||
|
#####################################
|
||||||
|
# UTILITAIRES #
|
||||||
|
#####################################
|
||||||
|
info() { echo -e "\033[1;34m[INFO]\033[0m $*"; }
|
||||||
|
warn() { echo -e "\033[1;33m[AVERT]\033[0m $*"; }
|
||||||
|
error() { echo -e "\033[1;31m[ERREUR]\033[0m $*" >&2; }
|
||||||
|
success() { echo -e "\033[1;32m[SUCCÈS]\033[0m $*"; }
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
set +e
|
||||||
|
[[ -d "$TEMP_DIR" ]] && rm -rf "$TEMP_DIR"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
is_windows_shell() {
|
||||||
|
# Git Bash / MSYS / CYGWIN ou variable OS=Windows_NT
|
||||||
|
if [[ "${OS:-}" == "Windows_NT" ]] || uname -s | grep -qiE 'mingw|msys|cygwin'; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
require_cmd() {
|
||||||
|
command -v "$1" >/dev/null 2>&1 || { error "Commande requise manquante: $1"; exit 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#####################################
|
||||||
|
# PRÉ-VÉRIFICATIONS #
|
||||||
|
#####################################
|
||||||
|
require_cmd docker
|
||||||
|
require_cmd curl
|
||||||
|
require_cmd sed
|
||||||
|
require_cmd grep
|
||||||
|
require_cmd awk
|
||||||
|
require_cmd sort
|
||||||
|
|
||||||
|
# Vérifier l'image locale
|
||||||
|
info "Vérification de l'image locale '${LOCAL_IMAGE}'…"
|
||||||
|
if ! docker image inspect "${LOCAL_IMAGE}" >/dev/null 2>&1; then
|
||||||
|
error "L'image locale ${LOCAL_IMAGE} n'existe pas. Construis-la d'abord (docker build …)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Vérifier config daemon: insecure-registries
|
||||||
|
info "Vérification de la config du daemon Docker (insecure-registries)…"
|
||||||
|
if ! docker info >/dev/null 2>&1; then
|
||||||
|
error "Impossible de contacter le daemon Docker. Est-il démarré ?"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! docker info 2>/dev/null \
|
||||||
|
| tr -d '\r' \
|
||||||
|
| awk '/Insecure Registries:/,/^$/ {print}' \
|
||||||
|
| grep -q -E "(^|[[:space:]])${REGISTRY_HOST}:${REGISTRY_PORT}([[:space:]]|$)"; then
|
||||||
|
error "Le daemon Docker n'a PAS '${REGISTRY}' dans insecure-registries."
|
||||||
|
if is_windows_shell; then
|
||||||
|
info "Sous Windows (Docker Desktop) : Settings → Docker Engine, ajoute dans le JSON :"
|
||||||
|
printf '%s\n' ' "insecure-registries": ["'"${REGISTRY}"'"]'
|
||||||
|
info "Puis clique 'Apply & Restart' et relance ce script."
|
||||||
|
else
|
||||||
|
info "Sous Linux : édite /etc/docker/daemon.json et ajoute par ex. :"
|
||||||
|
printf '%s\n' '{ "insecure-registries": ["'"${REGISTRY}"'"] }'
|
||||||
|
info "Puis : sudo systemctl restart docker"
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ping HTTP direct du registre (API v2)
|
||||||
|
info "Vérification HTTP directe du registre (http://${REGISTRY}/v2/)…"
|
||||||
|
if ! ${CURL} "http://${REGISTRY}/v2/" >/dev/null 2>&1; then
|
||||||
|
error "Impossible de joindre http://${REGISTRY}/v2/. Le registre écoute-t-il bien en HTTP sur :${REGISTRY_PORT} ?"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Auth facultative via env (DOCKER_USERNAME / DOCKER_PASSWORD)
|
||||||
|
if [[ -n "${DOCKER_USERNAME:-}" && -n "${DOCKER_PASSWORD:-}" ]]; then
|
||||||
|
info "Authentification au registre (docker login)…"
|
||||||
|
# NB: docker login respecte le mode HTTP si daemon configuré en insecure-registries
|
||||||
|
echo "${DOCKER_PASSWORD}" | docker login "${REGISTRY}" --username "${DOCKER_USERNAME}" --password-stdin >/dev/null
|
||||||
|
else
|
||||||
|
info "Aucun identifiant Docker fourni (DOCKER_USERNAME/DOCKER_PASSWORD). Tentative sans authentification."
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "${TEMP_DIR}"
|
||||||
|
|
||||||
|
#####################################
|
||||||
|
# RÉCUPÉRATION DES TAGS #
|
||||||
|
#####################################
|
||||||
|
get_all_tags() {
|
||||||
|
# Renvoie la liste des tags (un par ligne) ou rien si vide/erreur
|
||||||
|
# Utilise jq si dispo, sinon un parseur simple.
|
||||||
|
local tags_json
|
||||||
|
if ! tags_json="$(${CURL} "http://${REGISTRY}/v2/${IMAGE_NAME}/tags/list" 2>/dev/null)"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v "${JQ_BIN}" >/dev/null 2>&1; then
|
||||||
|
echo "${tags_json}" | ${JQ_BIN} -r '.tags[]?' 2>/dev/null || true
|
||||||
|
else
|
||||||
|
# fallback très simple (non robuste à tous les cas, mais suffisant ici)
|
||||||
|
echo "${tags_json}" \
|
||||||
|
| tr -d '\n' \
|
||||||
|
| sed -n 's/.*"tags":[[]\([^]]*\)[]].*/\1/p' \
|
||||||
|
| tr -d '"' \
|
||||||
|
| tr ',' '\n' \
|
||||||
|
| sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
filter_semver() {
|
||||||
|
# Garde uniquement X.Y.Z (numériques)
|
||||||
|
grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' || true
|
||||||
|
}
|
||||||
|
|
||||||
|
increment_patch() {
|
||||||
|
# Lit un tag semver X.Y.Z et augmente Z
|
||||||
|
local tag="$1"
|
||||||
|
local major minor patch
|
||||||
|
IFS='.' read -r major minor patch <<< "${tag}"
|
||||||
|
echo "${major}.${minor}.$((patch+1))"
|
||||||
|
}
|
||||||
|
|
||||||
|
info "Récupération des tags existants…"
|
||||||
|
ALL_TAGS="$(get_all_tags || true)"
|
||||||
|
SEMVER_TAGS="$(printf '%s\n' "${ALL_TAGS}" | filter_semver | sort -V || true)"
|
||||||
|
|
||||||
|
if [[ -z "${SEMVER_TAGS}" ]]; then
|
||||||
|
NEW_TAG="1.0.0"
|
||||||
|
else
|
||||||
|
LATEST_TAG="$(printf '%s\n' "${SEMVER_TAGS}" | tail -n1)"
|
||||||
|
NEW_TAG="$(increment_patch "${LATEST_TAG}")"
|
||||||
|
fi
|
||||||
|
info "Tag retenu pour cette release : ${NEW_TAG}"
|
||||||
|
|
||||||
|
#####################################
|
||||||
|
# TAG + PUSH (HTTP via daemon)
|
||||||
|
#####################################
|
||||||
|
info "Taggage local → ${REMOTE_REPO}:${NEW_TAG}"
|
||||||
|
docker tag "${LOCAL_IMAGE}" "${REMOTE_REPO}:${NEW_TAG}"
|
||||||
|
|
||||||
|
if [[ "${PUSH_LATEST}" == "yes" ]]; then
|
||||||
|
info "Taggage local → ${REMOTE_REPO}:latest"
|
||||||
|
docker tag "${LOCAL_IMAGE}" "${REMOTE_REPO}:latest"
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Push de ${REMOTE_REPO}:${NEW_TAG} (HTTP via insecure-registries)…"
|
||||||
|
docker push --disable-content-trust "${REMOTE_REPO}:${NEW_TAG}"
|
||||||
|
|
||||||
|
if [[ "${PUSH_LATEST}" == "yes" ]]; then
|
||||||
|
info "Push de ${REMOTE_REPO}:latest…"
|
||||||
|
docker push --disable-content-trust "${REMOTE_REPO}:latest"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#####################################
|
||||||
|
# RÉTENTION: SUPPRIMER ANCIENS #
|
||||||
|
#####################################
|
||||||
|
delete_by_tag() {
|
||||||
|
# Supprime un manifest par son tag en récupérant le digest via HEAD
|
||||||
|
local tag="$1"
|
||||||
|
local digest
|
||||||
|
# On demande le digest via HEAD + Accept manifest v2
|
||||||
|
digest="$(curl -fsSI -H "Accept: ${ACCEPT_MANIFEST}" "http://${REGISTRY}/v2/${IMAGE_NAME}/manifests/${tag}" \
|
||||||
|
| tr -d '\r' \
|
||||||
|
| awk -F': ' 'tolower($1)=="docker-content-digest"{print $2}' \
|
||||||
|
| tail -n1)"
|
||||||
|
if [[ -z "${digest}" ]]; then
|
||||||
|
warn "Digest introuvable pour ${tag} (manifest v2 absent ?). Skip."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Suppression manifest ${tag} (digest: ${digest})…"
|
||||||
|
# La delete API ne renvoie rien en cas de succès (204)
|
||||||
|
if ! curl -fsS -X DELETE "http://${REGISTRY}/v2/${IMAGE_NAME}/manifests/${digest}" >/dev/null; then
|
||||||
|
warn "Échec suppression manifest pour ${tag} (digest: ${digest})."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mettre à jour la liste après push
|
||||||
|
ALL_TAGS="$(get_all_tags || true)"
|
||||||
|
SEMVER_TAGS="$(printf '%s\n' "${ALL_TAGS}" | filter_semver | sort -V || true)"
|
||||||
|
|
||||||
|
if [[ -n "${SEMVER_TAGS}" ]]; then
|
||||||
|
COUNT="$(printf '%s\n' "${SEMVER_TAGS}" | wc -l | awk '{print $1}')"
|
||||||
|
if (( COUNT > MAX_VERSIONS )); then
|
||||||
|
TO_DELETE_COUNT=$(( COUNT - MAX_VERSIONS ))
|
||||||
|
# On supprime les plus anciennes
|
||||||
|
OLDEST="$(printf '%s\n' "${SEMVER_TAGS}" | head -n "${TO_DELETE_COUNT}")"
|
||||||
|
while IFS= read -r old_tag; do
|
||||||
|
[[ -z "${old_tag}" ]] && continue
|
||||||
|
# Ne jamais supprimer le tag que l'on vient de pousser (sécurité)
|
||||||
|
if [[ "${old_tag}" == "${NEW_TAG}" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
delete_by_tag "${old_tag}"
|
||||||
|
done <<< "${OLDEST}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
#####################################
|
||||||
|
# NETTOYAGE LOCAL OPTIONNEL #
|
||||||
|
#####################################
|
||||||
|
info "Nettoyage des tags locaux temporaires…"
|
||||||
|
set +e
|
||||||
|
docker rmi "${REMOTE_REPO}:${NEW_TAG}" >/dev/null 2>&1
|
||||||
|
[[ "${PUSH_LATEST}" == "yes" ]] && docker rmi "${REMOTE_REPO}:latest" >/dev/null 2>&1
|
||||||
|
set -e
|
||||||
|
|
||||||
|
success "Déploiement terminé. Version publiée : ${NEW_TAG}"
|
||||||
|
exit 0
|
||||||
@ -2,13 +2,15 @@
|
|||||||
services:
|
services:
|
||||||
homelab-dashboard:
|
homelab-dashboard:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: ..
|
||||||
dockerfile: Dockerfile
|
dockerfile: docker/Dockerfile
|
||||||
container_name: homelab-automation-dashboard
|
container_name: homelab-automation-dashboard
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "8008:8000"
|
- "8008:8000"
|
||||||
environment:
|
environment:
|
||||||
|
# zone horaire
|
||||||
|
- TZ=${TZ:-America/Montreal}
|
||||||
# Clé API pour l'authentification
|
# Clé API pour l'authentification
|
||||||
- API_KEY=${API_KEY:-dev-key-12345}
|
- API_KEY=${API_KEY:-dev-key-12345}
|
||||||
# Utilisateur SSH pour Ansible
|
# Utilisateur SSH pour Ansible
|
||||||
@ -45,17 +47,17 @@ services:
|
|||||||
# Volume Docker natif pour la base de données SQLite (évite les problèmes I/O sur Windows)
|
# Volume Docker natif pour la base de données SQLite (évite les problèmes I/O sur Windows)
|
||||||
- homelab_data:/app/data
|
- homelab_data:/app/data
|
||||||
# Monter l'inventaire Ansible (permet de modifier sans rebuild)
|
# Monter l'inventaire Ansible (permet de modifier sans rebuild)
|
||||||
- ${ANSIBLE_INVENTORY:-./ansible/inventory}:/ansible/inventory
|
- ${ANSIBLE_INVENTORY:-../ansible/inventory}:/ansible/inventory
|
||||||
# Monter les playbooks (permet de modifier sans rebuild)
|
# Monter les playbooks (permet de modifier sans rebuild)
|
||||||
- ${ANSIBLE_PLAYBOOKS:-./ansible/playbooks}:/ansible/playbooks
|
- ${ANSIBLE_PLAYBOOKS:-../ansible/playbooks}:/ansible/playbooks
|
||||||
# Monter les variables de groupe
|
# Monter les variables de groupe
|
||||||
- ${ANSIBLE_GROUP_VARS:-./ansible/inventory/group_vars}:/ansible/inventory/group_vars
|
- ${ANSIBLE_GROUP_VARS:-../ansible/inventory/group_vars}:/ansible/inventory/group_vars
|
||||||
# Monter les clés SSH depuis le host
|
# Monter les clés SSH depuis le host
|
||||||
- ${SSH_KEY_DIR:-~/.ssh}:/app/ssh_keys:ro
|
- ${SSH_KEY_DIR:-~/.ssh}:/app/ssh_keys:ro
|
||||||
# Volume pour les logs (optionnel)
|
# Volume pour les logs (optionnel)
|
||||||
- homelab_logs:/app/logs
|
- homelab_logs:/app/logs
|
||||||
# Monter le répertoire des logs de tâches depuis le host
|
# Monter le répertoire des logs de tâches depuis le host
|
||||||
- ${DIR_LOGS_TASKS:-./tasks_logs}:/app/tasks_logs
|
- ${DIR_LOGS_TASKS:-../tasks_logs}:/app/tasks_logs
|
||||||
networks:
|
networks:
|
||||||
- homelab-network
|
- homelab-network
|
||||||
healthcheck:
|
healthcheck:
|
||||||
51
docker/init.sh
Normal file
51
docker/init.sh
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# init.sh - Script d'initialisation de l'environnement pour Homelab Automation
|
||||||
|
# Ce script prépare le fichier .env et les répertoires nécessaires.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Se placer dans le dossier du script
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
echo "=== Initialisation de l'environnement Docker ==="
|
||||||
|
|
||||||
|
# 1. Configuration du fichier .env
|
||||||
|
if [ ! -f .env ]; then
|
||||||
|
echo "Creation du fichier .env a partir de .env.example..."
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# Génération d'une clé API aléatoire
|
||||||
|
API_KEY=$(openssl rand -hex 16 2>/dev/null || echo "change-me-please-123")
|
||||||
|
|
||||||
|
# Remplacement de la clé API par défaut (si sed disponible)
|
||||||
|
if command -v sed >/dev/null 2>&1; then
|
||||||
|
# Compatible Linux/Mac
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
sed -i '' "s/API_KEY=dev-key-12345/API_KEY=${API_KEY}/" .env
|
||||||
|
else
|
||||||
|
sed -i "s/API_KEY=dev-key-12345/API_KEY=${API_KEY}/" .env
|
||||||
|
fi
|
||||||
|
echo "Clé API générée aléatoirement."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "--> Fichier .env créé. Pensez à l'éditer pour configurer vos accès SSH et notifications."
|
||||||
|
else
|
||||||
|
echo "--> Le fichier .env existe déjà."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Création des répertoires de données
|
||||||
|
echo "Vérification des répertoires de données..."
|
||||||
|
mkdir -p ../data
|
||||||
|
mkdir -p ../tasks_logs
|
||||||
|
|
||||||
|
# 3. Vérification des permissions
|
||||||
|
echo "Ajustement des permissions..."
|
||||||
|
chmod +x deploy-img.sh maj.sh init.sh 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "=== Initialisation terminée ==="
|
||||||
|
echo "Pour démarrer l'application :"
|
||||||
|
echo " docker compose up -d --build"
|
||||||
|
echo ""
|
||||||
|
echo "Pour construire l'image :"
|
||||||
|
echo " ./build-img.ps1 (Windows/WSL)"
|
||||||
|
echo " docker build -f Dockerfile -t homelab-automation-api:latest .. (Linux)"
|
||||||
27
docker/maj.sh
Normal file
27
docker/maj.sh
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Always execute relative to this script directory
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
# Charger les variables depuis .env s'il existe
|
||||||
|
if [[ -f ".env" ]]; then
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source .env
|
||||||
|
fi
|
||||||
|
|
||||||
|
REGISTRY_HOST="${REGISTRY_HOST:-docker-registry.dev.home}"
|
||||||
|
REGISTRY_PORT="${REGISTRY_PORT:-5000}"
|
||||||
|
IMAGE_NAME="${IMAGE_NAME:-homelab-automation-api}"
|
||||||
|
|
||||||
|
REGISTRY="${REGISTRY_HOST}:${REGISTRY_PORT}"
|
||||||
|
IMAGE="${IMAGE_NAME}:latest"
|
||||||
|
|
||||||
|
# Pull latest image from private registry
|
||||||
|
docker image pull "${REGISTRY}/${IMAGE}"
|
||||||
|
|
||||||
|
# Restart stack with the current compose file
|
||||||
|
docker compose down
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
echo "Homelab Automation API updated and running."
|
||||||
270
documentation/MOBILE_RESPONSIVE_GUIDE.md
Normal file
270
documentation/MOBILE_RESPONSIVE_GUIDE.md
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
# Guide de Responsive Mobile - Homelab Automation Dashboard
|
||||||
|
|
||||||
|
## Résumé des Modifications
|
||||||
|
|
||||||
|
Ce document détaille les modifications apportées pour rendre l'application Homelab Automation Dashboard entièrement responsive et optimisée pour les appareils mobiles.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Navigation Mobile
|
||||||
|
|
||||||
|
### Menu Hamburger
|
||||||
|
- **Breakpoint**: `md:` (768px)
|
||||||
|
- **Composants ajoutés**:
|
||||||
|
- `#mobile-nav-overlay` - Overlay semi-transparent avec blur
|
||||||
|
- `#mobile-nav-sidebar` - Sidebar glissante depuis la gauche
|
||||||
|
- `#mobile-menu-btn` - Bouton hamburger animé
|
||||||
|
|
||||||
|
### Fonctionnalités
|
||||||
|
- Animation du bouton hamburger (transformation en X)
|
||||||
|
- Fermeture par swipe vers la gauche
|
||||||
|
- Fermeture par touche Escape
|
||||||
|
- Fermeture automatique au redimensionnement vers desktop
|
||||||
|
- Liens de navigation avec icônes
|
||||||
|
- Toggle de thème intégré
|
||||||
|
|
||||||
|
### Classes CSS
|
||||||
|
```css
|
||||||
|
.mobile-menu-btn
|
||||||
|
.mobile-nav-overlay
|
||||||
|
.mobile-nav-sidebar
|
||||||
|
.mobile-nav-links
|
||||||
|
.desktop-nav-links
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Breakpoints Utilisés
|
||||||
|
|
||||||
|
| Breakpoint | Taille | Usage |
|
||||||
|
|------------|--------|-------|
|
||||||
|
| Base | < 640px | Mobile (téléphones) |
|
||||||
|
| `sm:` | ≥ 640px | Petits écrans (grands téléphones) |
|
||||||
|
| `md:` | ≥ 768px | Tablettes |
|
||||||
|
| `lg:` | ≥ 1024px | Desktop |
|
||||||
|
| `xl:` | ≥ 1280px | Grands écrans |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Touch Targets
|
||||||
|
|
||||||
|
Tous les éléments interactifs respectent la taille minimale de **44x44px** :
|
||||||
|
|
||||||
|
```css
|
||||||
|
.touch-target {
|
||||||
|
min-width: 44px;
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Éléments concernés
|
||||||
|
- Boutons de navigation
|
||||||
|
- Filtres de tâches/schedules
|
||||||
|
- Boutons d'action (kebab menu)
|
||||||
|
- Éléments de dropdown
|
||||||
|
- Boutons de formulaire
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Adaptations par Section
|
||||||
|
|
||||||
|
### Dashboard
|
||||||
|
- **Hero**: Titre réduit de `text-5xl` à `text-3xl` sur mobile
|
||||||
|
- **Métriques**: Grille 2x2 au lieu de 4 colonnes
|
||||||
|
- **Actions Rapides**: Grille 2x2 avec labels courts
|
||||||
|
- **Boutons**: Empilés verticalement sur mobile
|
||||||
|
|
||||||
|
### Hosts
|
||||||
|
- **Header**: Layout en colonne sur mobile
|
||||||
|
- **Boutons**: Labels condensés ("Ajouter" au lieu de "Ajouter Host")
|
||||||
|
- **Dropdown groupes**: Padding augmenté pour touch
|
||||||
|
|
||||||
|
### Playbooks
|
||||||
|
- **Recherche**: Pleine largeur sur mobile
|
||||||
|
- **Filtres catégories**: Scroll horizontal avec masquage scrollbar
|
||||||
|
- **Bouton nouveau**: Texte condensé sur mobile
|
||||||
|
|
||||||
|
### Tasks
|
||||||
|
- **Filtres status**: Scroll horizontal
|
||||||
|
- **Calendrier date**: Adapté à la largeur de l'écran
|
||||||
|
- **Cartes tâches**: Layout empilé
|
||||||
|
|
||||||
|
### Schedules
|
||||||
|
- **Stats**: Grille 2x2 sur mobile
|
||||||
|
- **Filtres**: Empilés avec recherche pleine largeur
|
||||||
|
- **Calendrier**: Cellules réduites
|
||||||
|
|
||||||
|
### Logs
|
||||||
|
- **Boutons**: Icônes seules sur mobile
|
||||||
|
- **Container**: Hauteur max réduite (400px vs 600px)
|
||||||
|
|
||||||
|
### Aide
|
||||||
|
- **Table des matières**: Masquée sur mobile/tablette
|
||||||
|
- **Contenu**: Pleine largeur
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Modales Mobile
|
||||||
|
|
||||||
|
### Comportement
|
||||||
|
- Plein écran sur mobile (`< 640px`)
|
||||||
|
- Scroll interne avec prevention du scroll body
|
||||||
|
- Bouton de fermeture fixe en haut à droite
|
||||||
|
|
||||||
|
```css
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
#modal .glass-card {
|
||||||
|
min-height: 100vh;
|
||||||
|
max-height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Animations Optimisées
|
||||||
|
|
||||||
|
### Désactivation sur Mobile
|
||||||
|
- Animation `float` désactivée
|
||||||
|
- Hover transforms supprimés
|
||||||
|
- Transitions réduites à `opacity` seulement
|
||||||
|
|
||||||
|
### Respect de `prefers-reduced-motion`
|
||||||
|
```css
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
* {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Interactions Tactiles
|
||||||
|
|
||||||
|
### Swipe Gestures
|
||||||
|
- **Swipe droite** (bord gauche): Ouvre la navigation
|
||||||
|
- **Swipe gauche** (nav ouverte): Ferme la navigation
|
||||||
|
- Seuil: 80px minimum
|
||||||
|
|
||||||
|
### Kebab Menu
|
||||||
|
- Visible uniquement sur mobile (`< 640px`)
|
||||||
|
- Remplace les boutons d'action inline
|
||||||
|
- Fermeture au clic extérieur
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function toggleKebabMenu(element, event) {
|
||||||
|
// Toggle menu dropdown
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Utilitaires CSS Ajoutés
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Masquer scrollbar */
|
||||||
|
.scrollbar-hide {
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Safe area pour iPhone X+ */
|
||||||
|
.safe-area-bottom {
|
||||||
|
padding-bottom: max(16px, env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Touch target minimum */
|
||||||
|
.touch-target {
|
||||||
|
min-width: 44px;
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Support Orientation Paysage
|
||||||
|
|
||||||
|
```css
|
||||||
|
@media (max-height: 500px) and (orientation: landscape) {
|
||||||
|
.hero-section {
|
||||||
|
padding-top: 60px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Thème Clair Mobile
|
||||||
|
|
||||||
|
Tous les composants mobiles supportent le thème clair :
|
||||||
|
- Navigation sidebar
|
||||||
|
- Action bar
|
||||||
|
- Kebab menus
|
||||||
|
- Dropdowns
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Guide d'Implémentation Future
|
||||||
|
|
||||||
|
### Pour ajouter un nouveau composant responsive :
|
||||||
|
|
||||||
|
1. **Commencer par le mobile** (mobile-first)
|
||||||
|
2. **Utiliser les classes Tailwind** `sm:`, `md:`, `lg:`
|
||||||
|
3. **Respecter les touch targets** (min 44x44px)
|
||||||
|
4. **Tester sur plusieurs appareils** :
|
||||||
|
- iPhone SE (375px)
|
||||||
|
- iPhone 14 Pro (393px)
|
||||||
|
- iPad (768px)
|
||||||
|
- Desktop (1024px+)
|
||||||
|
|
||||||
|
### Pattern de bouton responsive :
|
||||||
|
```html
|
||||||
|
<button class="btn-primary flex-1 sm:flex-none text-sm touch-target">
|
||||||
|
<i class="fas fa-icon mr-1 sm:mr-2"></i>
|
||||||
|
<span class="hidden sm:inline">Texte complet</span>
|
||||||
|
<span class="sm:hidden">Court</span>
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern de grille responsive :
|
||||||
|
```html
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-2 sm:gap-4">
|
||||||
|
<!-- Items -->
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern de layout empilé :
|
||||||
|
```html
|
||||||
|
<div class="flex flex-col sm:flex-row gap-3 sm:gap-4">
|
||||||
|
<!-- Items -->
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tests Recommandés
|
||||||
|
|
||||||
|
1. **Chrome DevTools** - Device Mode
|
||||||
|
2. **Safari** - Responsive Design Mode
|
||||||
|
3. **BrowserStack** - Appareils réels
|
||||||
|
4. **Lighthouse** - Performance mobile
|
||||||
|
|
||||||
|
### Checklist de validation :
|
||||||
|
- [ ] Navigation mobile fonctionne
|
||||||
|
- [ ] Touch targets ≥ 44px
|
||||||
|
- [ ] Pas de scroll horizontal non voulu
|
||||||
|
- [ ] Textes lisibles (min 14px)
|
||||||
|
- [ ] Boutons accessibles
|
||||||
|
- [ ] Modales plein écran
|
||||||
|
- [ ] Thème clair/sombre fonctionne
|
||||||
|
- [ ] Animations fluides
|
||||||
|
- [ ] Safe area respectée (notch)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Dernière mise à jour: Décembre 2025*
|
||||||
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user