Files
sogoms/GO-HTMX.md
Pierre 65da4efdad 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>
2025-12-19 20:30:56 +01:00

9.6 KiB

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

<!-- 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

<!-- 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

{{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)

{{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

{{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

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

mux.HandleFunc("GET /admin/api/cron/jobs",
    AuthMiddleware(sessions, adminCfg, server.HandleAPICronJobs))

4. Utiliser dans le dashboard templates/dashboard.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 :

# config/sogoctl.yaml
sogoms-admin:
  args:
    - "-templates"
    - "/config/admin/templates"
    - "-dev"  # ← rechargement auto

Workflow dev :

# 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