feat/ajout-de-fonctionnalites (#1)
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s

Reviewed-on: #1
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #1.
This commit is contained in:
2026-04-06 14:23:20 +00:00
committed by Autin
parent f80578c26a
commit 8f585b4be8
52 changed files with 6536 additions and 434 deletions

View File

@@ -0,0 +1,172 @@
# Phase 1 — Gestion des applications et environnements
## Contexte
Central est un outil d'ops pour l'equipe technique Malio. Il gere les applications du SI deployees en Docker sur une meme machine (recette + prod).
Aujourd'hui les applications gerees sont definies en YAML (`config/applications.yaml`) avec des chemins maintenance en variables d'env. Cette phase remplace ce systeme statique par une gestion dynamique en base de donnees avec une UI complete.
## Roadmap globale
- **Phase 1** (ce spec) : Modele de donnees apps/envs + UI CRUD
- **Phase 2** : Deploiement de version, dashboard sante, logs
- **Phase 3** : Historique deploys, rollback, gestion containers, notifications
## Modele de donnees
### Entity `Application`
| Champ | Type | Contraintes |
|-------|------|-------------|
| `id` | int | PK, auto-generated |
| `name` | string (255) | not null |
| `slug` | string (255) | not null, unique |
| `registryImage` | string (255) | not null, ex: `gitea.malio.fr/malio-dev/sirh` |
| `description` | text | nullable |
| `giteaUrl` | string (255) | nullable |
| `createdAt` | DateTimeImmutable | not null, set on construct |
- Relation : `OneToMany` vers `Environment` (cascade persist + remove, orphanRemoval)
### Entity `Environment`
| Champ | Type | Contraintes |
|-------|------|-------------|
| `id` | int | PK, auto-generated |
| `name` | string (255) | not null, ex: "production", "recette" |
| `containerName` | string (255) | not null, ex: "sirh-app" |
| `deployScriptPath` | string (255) | not null |
| `maintenanceFilePath` | string (255) | not null |
| `appUrl` | string (255) | nullable |
| `application` | ManyToOne | not null, vers Application |
- Relation : `OneToMany` vers `LogFile` (cascade persist + remove, orphanRemoval)
### Entity `LogFile`
| Champ | Type | Contraintes |
|-------|------|-------------|
| `id` | int | PK, auto-generated |
| `label` | string (255) | not null, ex: "prod", "cron" |
| `path` | string (255) | not null |
| `environment` | ManyToOne | not null, vers Environment |
## API Endpoints
Tous les endpoints sont proteges par `ROLE_ADMIN`. Le slug est utilise comme identifiant URL pour Application.
### Application
| Route | Methode | Description | Provider/Processor |
|-------|---------|-------------|--------------------|
| `GET /api/applications` | GET | Liste des apps avec envs et logfiles | Doctrine (default) |
| `POST /api/applications` | POST | Creer une app | Doctrine (default) |
| `GET /api/applications/{slug}` | GET | Detail d'une app avec envs | Doctrine (default) |
| `PATCH /api/applications/{slug}` | PATCH | Modifier une app | Doctrine (default) |
| `DELETE /api/applications/{slug}` | DELETE | Supprimer une app (cascade envs) | Doctrine (default) |
Groupes de serialisation :
- Lecture : `app:read` (tous les champs + envs embarques)
- Ecriture : `app:write` (name, slug, registryImage, description, giteaUrl)
### Environment
| Route | Methode | Description | Provider/Processor |
|-------|---------|-------------|--------------------|
| `POST /api/applications/{slug}/environments` | POST | Ajouter un env a une app | Custom processor pour lier a l'app |
| `PATCH /api/environments/{id}` | PATCH | Modifier un env | Doctrine (default) |
| `DELETE /api/environments/{id}` | DELETE | Supprimer un env | Doctrine (default) |
Groupes de serialisation :
- Lecture : `env:read` (tous les champs + logfiles embarques)
- Ecriture : `env:write` (name, containerName, deployScriptPath, maintenanceFilePath, appUrl, logFiles)
Les LogFiles sont geres en embedded dans l'environnement : envoyes dans le body du POST/PATCH de l'env, pas d'endpoint separe.
### Maintenance
| Route | Methode | Description |
|-------|---------|-------------|
| `POST /api/environments/{id}/maintenance` | POST | Toggle maintenance (body: `{ "maintenance": true/false }`) |
Remplace l'ancien endpoint `POST /api/applications/{slug}/maintenance`. Le processor cree ou supprime le fichier maintenance sur le filesystem.
## Frontend
### Page liste — `/applications`
- Grille des applications (cards)
- Chaque card : nom, description (tronquee), nombre d'environnements, lien Gitea (icone externe)
- Bouton "Ajouter une application" en haut
- Clic sur une card → navigation vers `/applications/{slug}`
### Page detail — `/applications/{slug}`
**Section haute : infos application**
- Nom, description, image registry, lien Gitea
- Bouton editer → formulaire inline ou modale avec les champs app:write
- Bouton supprimer (avec confirmation)
**Section basse : environnements**
- Liste des environnements de l'app
- Chaque env affiche : nom, container, URL (lien cliquable), statut maintenance (badge)
- Actions par env : editer, supprimer, toggle maintenance
- Sous chaque env : liste des fichiers de log configures (label + path)
- Bouton "Ajouter un environnement" → formulaire avec champs env + logfiles dynamiques (ajouter/supprimer des lignes de log)
### Sidebar
- Lien "Applications" ajoute dans la sidebar (remplace ou complete le lien Dashboard actuel)
### Services frontend
- `services/applications.ts` : CRUD applications
- `services/environments.ts` : CRUD environnements + toggle maintenance
- `services/dto/application.ts` : types TypeScript
- `services/dto/environment.ts` : types TypeScript (inclut LogFile)
### i18n
- Nouvelles cles dans `fr.json` pour toutes les labels, messages de succes/erreur, confirmations de suppression
## Migration des donnees existantes
### Ce qui est cree
Une DataFixture (ou migration SQL) cree les 3 apps existantes :
**SIRH**
- slug: `sirh`, image: `gitea.malio.fr/malio-dev/sirh`
- Env prod : container `sirh-app`, deploy `/home/m-tristan/workspace/SIRH/deploy/docker/deploy.sh`
- Env recette : container `sirh-test-app` (si existant)
**Lesstime**
- slug: `lesstime`, image: `gitea.malio.fr/malio-dev/lesstime`
- Env prod : container `lesstime-app`
**Inventory**
- slug: `inventory`, image: `gitea.malio.fr/malio-dev/inventory`
- Env prod : container `inventory-app`
- Env recette : container `inventory-test-app`
Les chemins exacts (deploy, maintenance, logs) seront renseignes lors de l'implementation en inspectant chaque projet.
### Ce qui est supprime
- `config/applications.yaml`
- Variables d'env `SIRH_MAINTENANCE_PATH`, `LESSTIME_MAINTENANCE_PATH`, `INVENTORY_MAINTENANCE_PATH`
- `src/ApiResource/ManagedApplication.php`
- `src/State/ManagedApplicationProvider.php`
- `src/State/MaintenanceToggleProcessor.php`
- `services/dto/managed-application.ts` (frontend)
- `services/managed-applications.ts` (frontend)
La page d'accueil actuelle (dashboard avec toggle maintenance) est remplacee par la nouvelle page liste/detail.
## Hors scope
- Deploiement de version (phase 2)
- Logs en temps reel (phase 2)
- Dashboard sante containers (phase 2)
- Historique de deploiements (phase 3)
- Gestion multi-roles (pas de ROLE_USER pour l'instant)

View File

@@ -0,0 +1,155 @@
# Phase 2a — Deploiement de version + Versions disponibles
## Contexte
Central gere les applications du SI Malio. La phase 1 a mis en place le CRUD des applications et environnements en BDD. Cette phase ajoute la capacite de deployer une version sur un environnement et de lister les versions disponibles sur le registry Gitea.
Chaque app a un `deploy.sh` qui prend un tag en argument, pull l'image Docker, lance les migrations, et gere le mode maintenance. Le script tourne sur la machine host. Central tourne en Docker et accede au host via le socket Docker monte.
## Architecture
### Variable d'environnement
`GITEA_API_TOKEN` : token global Gitea avec acces lecture aux packages. Configure dans `.env` (backend Symfony). Non stocke en BDD.
`GITEA_API_URL` : URL de base de l'API Gitea (ex: `https://gitea.malio.fr`). Configure dans `.env`.
### Service GiteaRegistryService
Classe PHP dans `src/Service/GiteaRegistryService.php`.
Responsabilite : appeler l'API Gitea pour lister les tags d'une image container.
API Gitea : `GET {GITEA_API_URL}/api/v1/packages/{owner}/container/{package}` avec header `Authorization: token {GITEA_API_TOKEN}`.
- Input : `registryImage` de l'Application (ex: `gitea.malio.fr/malio-dev/sirh`) — on extrait `owner` (`malio-dev`) et `package` (`sirh`) depuis cette string.
- Output : liste de tags tries par date de creation (plus recent en premier), chaque tag avec son nom et sa date.
- Gestion d'erreur : si l'API est inaccessible ou le token invalide, renvoyer une erreur claire.
### Service DeployService
Classe PHP dans `src/Service/DeployService.php`.
Responsabilite : executer le script de deploy d'un environnement.
- Input : entite `Environment` + tag a deployer
- Execute `deployScriptPath` avec le tag en argument via `Symfony\Component\Process\Process`
- Timeout : 300 secondes (5 min max pour un deploy)
- Capture stdout + stderr
- Output : objet avec `success` (bool), `output` (string), `exitCode` (int)
Le process tourne dans le container Central. Le `deploy.sh` est accessible car les dossiers des apps sont montes dans le container. Le script utilise `docker compose` qui fonctionne grace au socket Docker monte.
### Endpoints API
| Route | Methode | Description | Securite |
|-------|---------|-------------|----------|
| `GET /api/applications/{slug}/tags` | GET | Liste les tags disponibles sur le registry Gitea | ROLE_ADMIN |
| `POST /api/environments/{id}/deploy` | POST | Lance un deploy, body: `{ "tag": "v1.2.3" }` | ROLE_ADMIN |
#### GET /api/applications/{slug}/tags
Provider custom `TagListProvider` dans `src/State/`.
- Charge l'Application par slug
- Appelle `GiteaRegistryService` avec le `registryImage`
- Retourne la liste des tags
Response :
```json
{
"tags": [
{ "name": "v1.2.3", "date": "2026-04-05T10:00:00Z" },
{ "name": "v1.2.2", "date": "2026-04-01T08:00:00Z" },
{ "name": "latest", "date": "2026-04-05T10:00:00Z" }
]
}
```
#### POST /api/environments/{id}/deploy
Processor custom `DeployProcessor` dans `src/State/`.
- Charge l'Environment par id
- Valide que le tag est fourni
- Appelle `DeployService`
- Retourne le resultat
Response succes :
```json
{
"success": true,
"output": "==> Deploying sirh:v1.2.3...\n...\n==> Deployed v1.2.3",
"tag": "v1.2.3"
}
```
Response echec :
```json
{
"success": false,
"output": "Error: ...",
"tag": "v1.2.3"
}
```
### Docker
#### docker-compose.yml (dev)
Ajouter au service `php` :
```yaml
volumes:
- /var/run/docker.sock:/var/run/docker.sock
```
Note : en dev les deploy.sh ne sont pas forcement accessibles depuis le container (chemins host). Le deploy ne fonctionnera pleinement qu'en prod. En dev on peut tester la liste des tags et le mecanisme, mais le deploy lui-meme pourra echouer (script introuvable). C'est acceptable.
#### docker-compose.yml (prod)
Ajouter au service `app` :
```yaml
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/www/sirh/deploy:/var/www/sirh/deploy:ro
- /var/www/lesstime/deploy:/var/www/lesstime/deploy:ro
- /var/www/inventory/deploy:/var/www/inventory/deploy:ro
```
Le socket Docker + les dossiers deploy montes en read-only (le deploy.sh est execute, pas modifie). Le script execute `docker compose` qui passe par le socket.
Installer `docker-cli` dans le Dockerfile prod pour que les commandes `docker compose` fonctionnent depuis le container.
### Frontend
#### Modal de deploy
Sur la page detail (`/applications/{slug}`), chaque environnement affiche un bouton "Deployer".
Clic sur "Deployer" → ouvre une modal `AppModal` avec :
- Un select (ou liste) des tags disponibles, charges depuis `GET /api/applications/{slug}/tags`
- Loading skeleton pendant le chargement des tags
- Bouton "Deployer" qui POST sur `/api/environments/{id}/deploy`
- Pendant le deploy : le bouton passe en loading, on attend la reponse
- A la fin : affichage du resultat dans la modal
- Succes : message vert + output du script dans un bloc `<pre>`
- Echec : message rouge + output du script dans un bloc `<pre>`
#### Service frontend
`frontend/services/deploy.ts` :
- `getAvailableTags(slug: string)` : GET les tags
- `deploy(envId: number, tag: string)` : POST le deploy
`frontend/services/dto/deploy.ts` :
- Type `Tag` : `{ name: string, date: string }`
- Type `DeployResult` : `{ success: boolean, output: string, tag: string }`
#### i18n
Nouvelles cles dans `fr.json` pour la modal de deploy (titre, select, boutons, messages succes/echec).
## Hors scope
- Streaming temps reel du log de deploy
- Historique des deployments
- Rollback
- Version actuellement deployee (affichage)

View File

@@ -0,0 +1,147 @@
# Phase 2b — Dashboard sante
## Contexte
Central gere les applications du SI Malio avec leurs environnements. Le socket Docker est monte dans le container Central (phase 2a). Cette phase ajoute un dashboard de sante qui affiche l'etat des containers Docker de chaque environnement, et enrichit la page detail avec des metriques.
## Architecture
### Service DockerService
Classe PHP dans `src/Service/DockerService.php`.
Utilise `Symfony\Component\Process\Process` pour executer des commandes Docker CLI via le socket monte.
**Methode `getContainerStatus(string $containerName): array`**
Execute `docker inspect --format '{{.State.Status}}||{{.Config.Image}}||{{.State.StartedAt}}' {containerName}`.
Retourne :
```php
[
'status' => 'running', // running | exited | restarting | not_found
'image' => 'gitea.malio.fr/malio-dev/sirh:v1.2.3',
'version' => 'v1.2.3', // tag extrait de l'image
'startedAt' => '2026-04-06T10:00:00Z',
]
```
Si le container n'existe pas ou la commande echoue, retourne `status: not_found` avec des valeurs vides.
Le tag/version est extrait en splitant l'image sur `:` — si pas de tag, retourne `latest`.
**Methode `getContainerStats(string $containerName): array`**
Execute `docker stats --no-stream --format '{{.CPUPerc}}||{{.MemUsage}}||{{.MemPerc}}' {containerName}`.
Retourne :
```php
[
'cpuPercent' => 2.5,
'memoryUsage' => '150MiB',
'memoryLimit' => '2GiB',
'memoryPercent' => 7.3,
]
```
### Endpoints API
| Route | Methode | Description | Securite |
|-------|---------|-------------|----------|
| `GET /api/dashboard` | GET | Toutes les apps avec statut de chaque env | ROLE_ADMIN |
| `GET /api/environments/{id}/health` | GET | Statut + stats detaillees d'un env | ROLE_ADMIN |
#### GET /api/dashboard
Provider custom `DashboardProvider` dans `src/State/`.
Charge toutes les Applications avec leurs Environments via le repository. Pour chaque Environment, appelle `DockerService::getContainerStatus()`.
Response :
```json
{
"applications": [
{
"name": "SIRH",
"slug": "sirh",
"environments": [
{
"id": 1,
"name": "production",
"status": "running",
"version": "v1.2.3"
}
]
}
]
}
```
#### GET /api/environments/{id}/health
Provider custom `EnvironmentHealthProvider` dans `src/State/`.
Charge l'Environment par id, appelle `getContainerStatus()` + `getContainerStats()`.
Response :
```json
{
"status": "running",
"version": "v1.2.3",
"startedAt": "2026-04-06T10:00:00Z",
"cpuPercent": 2.5,
"memoryUsage": "150MiB",
"memoryLimit": "2GiB",
"memoryPercent": 7.3
}
```
### Frontend
#### Page dashboard — `/dashboard`
Nouvelle page. Grille 2 colonnes fixe (`grid-cols-1 lg:grid-cols-2`).
Une card par application (fond `bg-tertiary-500`, style existant).
Chaque card :
- Header : nom de l'app (lien vers `/applications/{slug}`)
- Body : une ligne par environnement avec :
- Nom de l'env
- Badge statut : vert `running`, rouge `exited`, orange `restarting`, gris `not_found`
- Version deployee (tag, en `font-mono`)
Bouton refresh en haut a droite du titre.
Appel API : `GET /api/dashboard` au chargement.
#### Page detail — `/applications/{slug}`
Enrichir chaque bloc d'environnement avec un sous-bloc de metriques :
- Statut container (badge, meme style que dashboard)
- Version deployee
- Uptime (calcule depuis `startedAt` — ex: "2j 5h")
- CPU %
- Memoire : usage / limit (ex: "150MiB / 2GiB — 7.3%")
Appel API : `GET /api/environments/{id}/health` pour chaque env au chargement de la page.
#### Sidebar
Ajouter un lien "Dashboard" (`/dashboard`, icone `mdi:view-dashboard`) au-dessus du lien "Applications" existant.
### Frontend services
- `frontend/services/dashboard.ts` : `getDashboard()` et `getEnvironmentHealth(envId)`
- `frontend/services/dto/dashboard.ts` : types TypeScript
### i18n
Nouvelles cles dans `fr.json` pour le dashboard (titre, statuts, metriques, refresh).
## Hors scope
- Polling automatique
- Alertes / notifications
- Historique de sante
- Restart count, ports, taille des logs