124 lines
3.6 KiB
Go
124 lines
3.6 KiB
Go
package web
|
||
|
||
// Nutzungsübersicht je Projekt (AUSBAUSTUFEN.md 4.6).
|
||
//
|
||
// Für alle mit Zugang zum Projekt, auch mit Leserecht: Die Seite zeigt nur
|
||
// Zahlen und Namen von Zugängen, und die stehen ohnehin schon im Protokoll,
|
||
// das jede Rolle sehen darf.
|
||
|
||
import (
|
||
"net/http"
|
||
"time"
|
||
|
||
"munin/internal/store"
|
||
)
|
||
|
||
// Wie viele Einträge die Listen auf der Seite höchstens zeigen. Die Seite
|
||
// soll einen Überblick geben, keine vollständige Aufstellung — wer mehr
|
||
// braucht, findet sie über die Eintragsliste oder den Graph.
|
||
const (
|
||
usageListLen = 15
|
||
usageKeysLen = 5
|
||
)
|
||
|
||
// usageData sind die Daten der Nutzungsseite.
|
||
type usageData struct {
|
||
Summary store.UsageSummary
|
||
WindowDays int
|
||
Untagged []store.EntryRef
|
||
Stale int // seit über einem Jahr unverändert (store.CountStaleEntries)
|
||
Missing []store.MissingLink
|
||
MissingMore int // wie viele Verweise ins Leere über die Liste hinaus
|
||
Keys []store.APIKey
|
||
|
||
// Kuratierte Einträge, die ein Agent seitdem geändert hat — zur Prüfung.
|
||
CuratedOverrides []store.CuratedOverride
|
||
|
||
// Searches der Agenten über recall (Suchprotokoll, migrations/006).
|
||
Searches store.SearchStats
|
||
EmptySearches []store.EmptySearch
|
||
}
|
||
|
||
// handleProjectUsage zeigt die Nutzungsübersicht eines Projekts.
|
||
func (s *Server) handleProjectUsage(w http.ResponseWriter, r *http.Request) {
|
||
ws, _ := WorkspaceFrom(r)
|
||
project, _ := ProjectFrom(r)
|
||
ctx := r.Context()
|
||
|
||
all, err := ws.Store.UsageSummaries(ctx, project.ID, time.Now())
|
||
if err != nil {
|
||
s.serverError(w, "Nutzung konnte nicht geladen werden", err)
|
||
return
|
||
}
|
||
|
||
untagged, err := ws.Store.UntaggedEntries(ctx, project.ID, usageListLen)
|
||
if err != nil {
|
||
s.serverError(w, "Einträge ohne Schlagwort konnten nicht geladen werden", err)
|
||
return
|
||
}
|
||
|
||
dangling, err := ws.Store.MissingLinks(ctx, project.ID)
|
||
if err != nil {
|
||
s.serverError(w, "Verweise konnten nicht geladen werden", err)
|
||
return
|
||
}
|
||
|
||
keys, err := ws.Store.RecentlyUsedKeys(ctx, project.ID, usageKeysLen)
|
||
if err != nil {
|
||
s.serverError(w, "Zugänge konnten nicht geladen werden", err)
|
||
return
|
||
}
|
||
|
||
forReview, err := ws.Store.CuratedOverrides(ctx, project.ID, usageListLen)
|
||
if err != nil {
|
||
s.serverError(w, "Geänderte kuratierte Einträge konnten nicht geladen werden", err)
|
||
return
|
||
}
|
||
|
||
searches, err := ws.Store.SearchStatsFor(ctx, project.ID, time.Now())
|
||
if err != nil {
|
||
s.serverError(w, "Suchprotokoll konnte nicht geladen werden", err)
|
||
return
|
||
}
|
||
|
||
noHits, err := ws.Store.EmptySearches(ctx, project.ID, time.Now(), usageListLen)
|
||
if err != nil {
|
||
s.serverError(w, "Suchen ohne Treffer konnten nicht geladen werden", err)
|
||
return
|
||
}
|
||
|
||
stale, err := ws.Store.CountStaleEntries(ctx, project.ID, time.Now())
|
||
if err != nil {
|
||
s.serverError(w, "veraltete Einträge konnten nicht gezählt werden", err)
|
||
return
|
||
}
|
||
|
||
data := usageData{
|
||
Stale: stale,
|
||
// Ein Projekt ohne Einträge und Schreibvorgänge fehlt in der map —
|
||
// dann liefert alle[project.ID] den Nullwert, also überall 0.
|
||
Summary: all[project.ID],
|
||
WindowDays: store.UsageWindowDays,
|
||
Untagged: untagged,
|
||
Missing: dangling,
|
||
Keys: keys,
|
||
|
||
CuratedOverrides: forReview,
|
||
|
||
Searches: searches,
|
||
EmptySearches: noHits,
|
||
}
|
||
|
||
// Die Verweise ins Leere kommen vollständig (die Gesamtzahl ist selbst
|
||
// eine Aussage), angezeigt werden nur die meistgenannten.
|
||
if len(data.Missing) > usageListLen {
|
||
data.MissingMore = len(data.Missing) - usageListLen
|
||
data.Missing = data.Missing[:usageListLen]
|
||
}
|
||
|
||
s.render(w, r, "usage.html", http.StatusOK, pageData{
|
||
Title: "Nutzung – " + project.Name,
|
||
Section: "nutzung",
|
||
Data: data,
|
||
})
|
||
}
|