SOGOMS v1.0.3 - Admin UI, Cron, Config reload
Phase 13 : sogoms-cron
- Jobs planifiés avec schedule cron standard
- Types: query_email, http, service
- Actions: list, trigger, status
Phase 16 : Réorganisation config/apps/{app}/
- Tous les fichiers d'une app dans un seul dossier
- Migration prokov vers nouvelle structure
Phase 17 : sogoms-admin
- Interface web d'administration (Go templates + htmx)
- Auth sessions cookies signées HMAC-SHA256
- Rôles super_admin / app_admin avec permissions
Phase 19 : Création d'app via Admin UI
- Formulaire création app avec config DB/auth
- Bouton "Scanner la base" : introspection + schema.yaml
- Rechargement automatique sogoway via SIGHUP
Infrastructure :
- sogoctl : socket de contrôle /run/sogoctl.sock
- sogoway : reload config sur SIGHUP sans restart
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
338
GO-HTMX.md
Normal file
338
GO-HTMX.md
Normal file
@@ -0,0 +1,338 @@
|
||||
# Architecture Go + htmx (sogoms-admin)
|
||||
|
||||
Guide pour comprendre et modifier l'interface d'administration SOGOMS.
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
```
|
||||
Browser (htmx) ←→ Go Server ←→ Services (sockets)
|
||||
│ │
|
||||
│ HTTP GET/POST │
|
||||
▼ ▼
|
||||
Templates Handlers
|
||||
(HTML) (Go)
|
||||
```
|
||||
|
||||
**Stack :**
|
||||
- **Backend** : Go net/http (pas de framework)
|
||||
- **Frontend** : Go templates + htmx + Pico.css
|
||||
- **Principe** : Le serveur renvoie des fragments HTML, htmx les injecte dans le DOM
|
||||
|
||||
---
|
||||
|
||||
## Structure des fichiers
|
||||
|
||||
```
|
||||
cmd/sogoms/admin/
|
||||
├── main.go # Point d'entrée, routes, config
|
||||
├── handlers.go # Handlers HTTP (logique métier)
|
||||
├── middleware.go # Auth, CSRF, logging, rate limiting
|
||||
├── session.go # Gestion des sessions
|
||||
├── services.go # Appels vers microservices (sockets)
|
||||
└── templates/
|
||||
├── layout.html # Layout commun (head, nav, footer)
|
||||
├── login.html # Page de connexion
|
||||
├── dashboard.html # Dashboard principal
|
||||
└── partials/ # Fragments htmx
|
||||
├── apps_list.html
|
||||
└── services_status.html
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Flux de données
|
||||
|
||||
### 1. Requête initiale (page complète)
|
||||
|
||||
```
|
||||
GET /admin/
|
||||
│
|
||||
▼
|
||||
main.go:103 ─────────────────────────────────────────────┐
|
||||
│ mux.HandleFunc("GET /admin/", AuthMiddleware(...)) │
|
||||
└───────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
handlers.go:114-138 ─────────────────────────────────────┐
|
||||
│ func HandleDashboard(w, r) { │
|
||||
│ data := map[string]any{ │
|
||||
│ "Title": "Dashboard", │
|
||||
│ "Apps": accessibleApps, │
|
||||
│ ... │
|
||||
│ } │
|
||||
│ s.render(w, "dashboard.html", data) │
|
||||
│ } │
|
||||
└───────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
templates/dashboard.html ────────────────────────────────┐
|
||||
│ {{template "layout" .}} │
|
||||
│ <h1>Dashboard</h1> │
|
||||
│ <div hx-get="/admin/api/services/health" │
|
||||
│ hx-trigger="load"> │
|
||||
│ Chargement... │
|
||||
│ </div> │
|
||||
└───────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2. Requête htmx (fragment)
|
||||
|
||||
```
|
||||
hx-get="/admin/api/services/health"
|
||||
│
|
||||
▼
|
||||
main.go:109 ─────────────────────────────────────────────┐
|
||||
│ mux.HandleFunc("GET /admin/api/services/health", ...) │
|
||||
└───────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
handlers.go:172-193 ─────────────────────────────────────┐
|
||||
│ func HandleAPIServicesHealth(w, r) { │
|
||||
│ statuses := s.services.HealthCheck() │
|
||||
│ data := map[string]any{"Services": statuses} │
|
||||
│ s.render(w, "partials/services_status.html", data)│
|
||||
│ } │
|
||||
└───────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
templates/partials/services_status.html ─────────────────┐
|
||||
│ <ul> │
|
||||
│ {{range .Services}} │
|
||||
│ <li>{{.Name}} - {{if .Available}}OK{{end}}</li> │
|
||||
│ {{end}} │
|
||||
│ </ul> │
|
||||
└───────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
htmx remplace le contenu du <div> avec ce fragment
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Attributs htmx essentiels
|
||||
|
||||
| Attribut | Description | Exemple |
|
||||
|----------|-------------|---------|
|
||||
| `hx-get` | GET AJAX vers URL | `hx-get="/admin/api/apps"` |
|
||||
| `hx-post` | POST AJAX vers URL | `hx-post="/admin/api/apps/create"` |
|
||||
| `hx-trigger` | Quand déclencher | `load`, `click`, `every 30s`, `submit` |
|
||||
| `hx-target` | Où injecter la réponse | `hx-target="#result"` (défaut: élément courant) |
|
||||
| `hx-swap` | Comment injecter | `innerHTML` (défaut), `outerHTML`, `beforeend` |
|
||||
| `hx-indicator` | Spinner pendant chargement | `hx-indicator=".loading"` |
|
||||
| `hx-confirm` | Confirmation avant action | `hx-confirm="Supprimer ?"` |
|
||||
|
||||
### Exemples
|
||||
|
||||
```html
|
||||
<!-- Charger au chargement de la page -->
|
||||
<div hx-get="/admin/api/apps" hx-trigger="load">
|
||||
Chargement...
|
||||
</div>
|
||||
|
||||
<!-- Rafraîchir toutes les 30 secondes -->
|
||||
<div hx-get="/admin/api/services/health"
|
||||
hx-trigger="load, every 30s">
|
||||
</div>
|
||||
|
||||
<!-- Soumettre un formulaire en AJAX -->
|
||||
<form hx-post="/admin/api/apps/create"
|
||||
hx-target="#apps-list"
|
||||
hx-swap="beforeend">
|
||||
<input name="name" required>
|
||||
<button type="submit">Créer</button>
|
||||
</form>
|
||||
|
||||
<!-- Supprimer avec confirmation -->
|
||||
<button hx-delete="/admin/api/apps/123"
|
||||
hx-confirm="Supprimer cette app ?"
|
||||
hx-target="closest li"
|
||||
hx-swap="outerHTML">
|
||||
Supprimer
|
||||
</button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Templates Go
|
||||
|
||||
### Syntaxe de base
|
||||
|
||||
```html
|
||||
<!-- Variable -->
|
||||
{{.Title}}
|
||||
|
||||
<!-- Condition -->
|
||||
{{if .IsSuperAdmin}}
|
||||
Super Admin
|
||||
{{else}}
|
||||
App Admin
|
||||
{{end}}
|
||||
|
||||
<!-- Boucle -->
|
||||
{{range .Apps}}
|
||||
<li>{{.Name}}</li>
|
||||
{{end}}
|
||||
|
||||
<!-- Inclusion de template -->
|
||||
{{template "layout" .}}
|
||||
|
||||
<!-- Bloc définissable -->
|
||||
{{define "content"}}
|
||||
Contenu ici
|
||||
{{end}}
|
||||
```
|
||||
|
||||
### Structure layout.html
|
||||
|
||||
```html
|
||||
{{define "layout"}}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{.Title}} - SOGOMS Admin</title>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
||||
<link rel="stylesheet" href="https://unpkg.com/@picocss/pico@1/css/pico.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<nav>...</nav>
|
||||
<main class="container">
|
||||
{{template "content" .}}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
```
|
||||
|
||||
### Structure page (dashboard.html)
|
||||
|
||||
```html
|
||||
{{define "dashboard.html"}}
|
||||
{{template "layout" .}}
|
||||
{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<h1>Dashboard</h1>
|
||||
<!-- contenu de la page -->
|
||||
{{end}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ajouter une nouvelle fonctionnalité
|
||||
|
||||
### Exemple : Liste des jobs cron
|
||||
|
||||
**1. Créer le partial** `templates/partials/cron_jobs.html`
|
||||
|
||||
```html
|
||||
{{define "partials/cron_jobs.html"}}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Job</th>
|
||||
<th>Schedule</th>
|
||||
<th>Prochain run</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Jobs}}
|
||||
<tr>
|
||||
<td>{{.Name}}</td>
|
||||
<td><code>{{.Schedule}}</code></td>
|
||||
<td>{{.NextRun}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{end}}
|
||||
```
|
||||
|
||||
**2. Ajouter le handler** dans `handlers.go`
|
||||
|
||||
```go
|
||||
func (s *AdminServer) HandleAPICronJobs(w http.ResponseWriter, r *http.Request) {
|
||||
user := GetUserFromContext(r.Context())
|
||||
if user == nil {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
jobs, err := s.services.GetCronJobs()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
data := map[string]any{
|
||||
"Jobs": jobs,
|
||||
}
|
||||
|
||||
s.render(w, "partials/cron_jobs.html", data)
|
||||
}
|
||||
```
|
||||
|
||||
**3. Ajouter la route** dans `main.go`
|
||||
|
||||
```go
|
||||
mux.HandleFunc("GET /admin/api/cron/jobs",
|
||||
AuthMiddleware(sessions, adminCfg, server.HandleAPICronJobs))
|
||||
```
|
||||
|
||||
**4. Utiliser dans le dashboard** `templates/dashboard.html`
|
||||
|
||||
```html
|
||||
<article>
|
||||
<header><strong>Jobs Cron</strong></header>
|
||||
<div hx-get="/admin/api/cron/jobs" hx-trigger="load">
|
||||
Chargement...
|
||||
</div>
|
||||
</article>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mode développement
|
||||
|
||||
Le flag `-dev` recharge les templates à chaque requête :
|
||||
|
||||
```yaml
|
||||
# config/sogoctl.yaml
|
||||
sogoms-admin:
|
||||
args:
|
||||
- "-templates"
|
||||
- "/config/admin/templates"
|
||||
- "-dev" # ← rechargement auto
|
||||
```
|
||||
|
||||
**Workflow dev :**
|
||||
|
||||
```bash
|
||||
# 1. Modifier un template localement
|
||||
vim cmd/sogoms/admin/templates/dashboard.html
|
||||
|
||||
# 2. Déployer les templates (sans recompilation)
|
||||
./deploy-admin.sh
|
||||
|
||||
# 3. Rafraîchir le navigateur → changements visibles
|
||||
```
|
||||
|
||||
**Pour la prod**, retirer `-dev` (templates chargés une fois au démarrage).
|
||||
|
||||
---
|
||||
|
||||
## Fichiers de configuration
|
||||
|
||||
| Fichier | Rôle |
|
||||
|---------|------|
|
||||
| `/secrets/admin_users.yaml` | Users, passwords, rôles |
|
||||
| `/secrets/admin_session_secret` | Clé HMAC pour cookies |
|
||||
| `/config/sogoctl.yaml` | Args de sogoms-admin |
|
||||
| `/config/admin/templates/` | Templates HTML (si -templates) |
|
||||
|
||||
---
|
||||
|
||||
## Ressources
|
||||
|
||||
- **htmx** : https://htmx.org/docs/
|
||||
- **Pico.css** : https://picocss.com/docs/
|
||||
- **Go templates** : https://pkg.go.dev/html/template
|
||||
Reference in New Issue
Block a user