package main
import (
"bytes"
"html/template"
"net/http"
"git.neuromancer.ovh/bastien-mrq/gitfed/internal/i18n"
"git.neuromancer.ovh/bastien-mrq/gitfed/internal/store"
)
var adminIndexTpl = newTpl("admin-index", `
<div class="gf-page-head"><h1>{{t .Lang "admin.title"}}</h1></div>
<div class="gf-admin-grid">
<div class="gf-admin-card">
<div class="top"><span class="l">{{t .Lang "admin.card_users"}}</span></div>
<div class="n">{{.UserCount}}</div>
<p>{{t .Lang "admin.card_users_sub" .AdminCount .RegularCount}}</p>
<a class="go" href="/admin/users">{{t .Lang "admin.card_users_link"}} →</a>
</div>
<div class="gf-admin-card">
<div class="top">
<span class="l">{{t .Lang "admin.card_trust"}}</span>
{{if .PendingCount}}<span class="badge pending">{{.PendingCount}} {{t .Lang "admin.card_trust_pending"}}</span>{{end}}
</div>
<div class="n">{{.TrustCount}}</div>
<p>{{t .Lang "admin.card_trust_sub"}}</p>
<a class="go" href="/admin/trust">{{t .Lang "admin.card_trust_link"}} →</a>
</div>
<div class="gf-admin-card">
<div class="top"><span class="l">{{t .Lang "admin.card_audit"}}</span></div>
<div class="n">{{.AuditCount}}</div>
<p>{{t .Lang "admin.card_audit_sub"}}</p>
<a class="go" href="/admin/audit">{{t .Lang "admin.card_audit_link"}} →</a>
</div>
<div class="gf-admin-card">
<div class="top"><span class="l">{{t .Lang "admin.card_repos"}}</span></div>
<div class="n">{{humanBytes .RepoBytes}}</div>
<p>{{t .Lang "admin.card_repos_sub" .RepoCount}}</p>
<a class="go" href="/admin/repos">{{t .Lang "admin.card_repos_link"}} →</a>
</div>
</div>
{{if .RecentAudit}}
<h3 style="font-size:0.95rem; margin-bottom:0.6rem;">{{t .Lang "admin.recent_activity"}}</h3>
<div class="gf-card gf-audit-mini">
{{range .RecentAudit}}
<div class="gf-audit-row">
<span class="t">{{.Time.Local.Format "15:04:05"}}</span>
<span class="who">{{.Principal}} · {{.Action}}{{if .Repo}} · {{.Repo}}{{end}}</span>
{{if .Allowed}}<span class="badge trusted">{{t $.Lang "admin.audit_allow"}}</span>{{else}}<span class="badge pending">{{t $.Lang "admin.audit_deny"}}</span>{{end}}
</div>
{{end}}
</div>
{{end}}
`)
func (s *server) handleAdminIndex(w http.ResponseWriter, r *http.Request) {
users, err := s.ops.ListUsers()
if err != nil {
s.serverError(w, r, err)
return
}
adminCount := 0
for _, u := range users {
if u.IsAdmin {
adminCount++
}
}
trust, err := s.ops.ListTrustedCAs()
if err != nil {
s.serverError(w, r, err)
return
}
pendingCount := 0
for _, t := range trust {
if t.Status == store.TrustPending {
pendingCount++
}
}
audit, err := s.ops.ListAudit(200)
if err != nil {
s.serverError(w, r, err)
return
}
recent := audit
if len(recent) > 5 {
recent = recent[:5]
}
repos, err := s.ops.ListRepos()
if err != nil {
s.serverError(w, r, err)
return
}
var repoBytes int64
for _, repo := range repos {
// Best-effort, same as the repos list page — a vanished repo
// directory shouldn't take down the admin dashboard.
if size, err := s.ops.RepoDiskUsage(repo.Name); err == nil {
repoBytes += size
}
}
lang := s.lang(r)
var buf bytes.Buffer
_ = adminIndexTpl.Execute(&buf, struct {
UserCount, AdminCount, RegularCount int
TrustCount, PendingCount int
AuditCount int
RepoCount int
RepoBytes int64
Lang string
RecentAudit []store.AuditEvent
}{len(users), adminCount, len(users) - adminCount, len(trust), pendingCount, len(audit), len(repos), repoBytes, string(lang), recent})
s.render(w, r, i18n.T(lang, "admin.title"), "admin", template.HTML(buf.String()))
}