Files
sogoms/cmd/sogoms/admin/templates/infra.html
Pierre 0b1977e0c4 SOGOMS v1.0.7 - 2FA obligatoire et Infrastructure Management
Phase 17g - Double Authentification:
- TOTP avec Google Authenticator/Authy
- QR code pour enrôlement
- Codes de backup (10 codes usage unique)
- Page /admin/security pour gestion 2FA
- Page /admin/users avec Reset 2FA (super_admin)
- 2FA obligatoire pour rôles configurés

Phase 21 - Infrastructure Management:
- SQLite pour données infra (/data/infra.db)
- SSH Pool avec reconnexion auto
- Gestion Incus (list, start, stop, restart, sync)
- Gestion Nginx (test, reload, deploy, sync, certbot)
- Interface admin /admin/infra
- Formulaire ajout serveur
- Page détail serveur avec containers et sites

Fichiers créés:
- internal/infra/ (db, models, migrations, repository, ssh, incus, nginx)
- cmd/sogoms/admin/totp.go
- cmd/sogoms/admin/handlers_2fa.go
- cmd/sogoms/admin/handlers_infra.go
- Templates: 2fa_*, security, users, infra, server_*

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-26 21:21:11 +01:00

160 lines
4.6 KiB
HTML

{{define "infra.html"}}
{{template "partials/header.html" .}}
<style>
.server-card {
margin-bottom: 1rem;
}
.server-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.server-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 0.5rem;
margin-top: 0.5rem;
font-size: 0.9rem;
}
.server-info dt {
color: var(--pico-muted-color);
font-size: 0.8rem;
}
.server-info dd {
margin: 0;
font-weight: 500;
}
.badge {
display: inline-block;
padding: 0.2rem 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
font-weight: 500;
}
.badge-online { background: #dcfce7; color: #166534; }
.badge-offline { background: #fef2f2; color: #dc2626; }
.badge-unknown { background: #f3f4f6; color: #6b7280; }
.badge-incus { background: #dbeafe; color: #1d4ed8; }
.badge-nginx { background: #fef3c7; color: #92400e; }
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 0.5rem;
margin-bottom: 1rem;
}
.stat-card {
background: var(--pico-card-background-color);
border: 1px solid var(--pico-muted-border-color);
border-radius: var(--pico-border-radius);
padding: 1rem;
text-align: center;
}
.stat-value {
font-size: 1.5rem;
font-weight: bold;
color: var(--pico-primary);
}
.stat-label {
font-size: 0.8rem;
color: var(--pico-muted-color);
}
</style>
<h1>Infrastructure</h1>
<p class="user-info">
Gestion des serveurs, containers Incus et configurations Nginx.
</p>
{{if eq .Flash "success"}}
<div style="background:#f0fdf4;border:1px solid #bbf7d0;color:#16a34a;padding:0.75rem 1rem;border-radius:var(--pico-border-radius);margin-bottom:1rem;">
{{.FlashMessage}}
</div>
{{end}}
{{if eq .Flash "error"}}
<div style="background:#fef2f2;border:1px solid #fecaca;color:#dc2626;padding:0.75rem 1rem;border-radius:var(--pico-border-radius);margin-bottom:1rem;">
{{.FlashMessage}}
</div>
{{end}}
<!-- Statistiques -->
<div class="stats-grid" hx-get="/admin/api/infra/status" hx-trigger="every 30s" hx-swap="innerHTML">
{{$online := 0}}
{{$containers := 0}}
{{range .Servers}}
{{if eq .Status "online"}}{{$online = 1}}{{end}}
{{$containers = .ContainerCount}}
{{end}}
<div class="stat-card">
<div class="stat-value">{{len .Servers}}</div>
<div class="stat-label">Serveurs</div>
</div>
<div class="stat-card">
<div class="stat-value">{{range .Servers}}{{.ContainerCount}}{{end}}</div>
<div class="stat-label">Containers</div>
</div>
<div class="stat-card">
<div class="stat-value">{{range .Servers}}{{.NginxCount}}{{end}}</div>
<div class="stat-label">Sites Nginx</div>
</div>
</div>
<!-- Actions -->
<div style="margin-bottom: 1rem;">
<a href="/admin/infra/servers/new" role="button">+ Nouveau Serveur</a>
</div>
<!-- Liste des serveurs -->
{{if .Servers}}
{{range .Servers}}
<article class="server-card">
<header class="server-header">
<div>
<strong>{{.Name}}</strong>
{{if eq .Status "online"}}<span class="badge badge-online">Online</span>{{end}}
{{if eq .Status "offline"}}<span class="badge badge-offline">Offline</span>{{end}}
{{if eq .Status "unknown"}}<span class="badge badge-unknown">?</span>{{end}}
{{if .HasIncus}}<span class="badge badge-incus">Incus</span>{{end}}
{{if .HasNginx}}<span class="badge badge-nginx">Nginx</span>{{end}}
</div>
<a href="/admin/infra/servers/{{.ID}}" role="button" class="outline">Détails</a>
</header>
<dl class="server-info">
<div>
<dt>Host</dt>
<dd>{{.Host}}</dd>
</div>
{{if .VpnIP}}
<div>
<dt>VPN IP</dt>
<dd>{{.VpnIP}}</dd>
</div>
{{end}}
<div>
<dt>SSH</dt>
<dd>{{.SSHUser}}@:{{.SSHPort}}</dd>
</div>
<div>
<dt>Containers</dt>
<dd>{{.ContainerCount}}</dd>
</div>
<div>
<dt>Sites Nginx</dt>
<dd>{{.NginxCount}}</dd>
</div>
</dl>
</article>
{{end}}
{{else}}
<article>
<p style="text-align:center;color:var(--pico-muted-color);">
Aucun serveur configuré. <a href="/admin/infra/servers/new">Ajouter un serveur</a>
</p>
</article>
{{end}}
{{template "partials/footer.html" .}}
{{end}}