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 ( nutzungListenLaenge = 15 nutzungKeysLaenge = 5 ) // usageData sind die Daten der Nutzungsseite. type usageData struct { Summary store.UsageSummary WindowDays int Untagged []store.EntryRef 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 // Suchen 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() alle, err := ws.Store.UsageSummaries(ctx, project.ID, time.Now()) if err != nil { s.serverError(w, "Nutzung konnte nicht geladen werden", err) return } ohneSchlagwort, err := ws.Store.UntaggedEntries(ctx, project.ID, nutzungListenLaenge) if err != nil { s.serverError(w, "Einträge ohne Schlagwort konnten nicht geladen werden", err) return } insLeere, 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, nutzungKeysLaenge) if err != nil { s.serverError(w, "Zugänge konnten nicht geladen werden", err) return } zurPruefung, err := ws.Store.CuratedOverrides(ctx, project.ID, nutzungListenLaenge) if err != nil { s.serverError(w, "Geänderte kuratierte Einträge konnten nicht geladen werden", err) return } suchen, err := ws.Store.SearchStatsFor(ctx, project.ID, time.Now()) if err != nil { s.serverError(w, "Suchprotokoll konnte nicht geladen werden", err) return } ohneTreffer, err := ws.Store.EmptySearches(ctx, project.ID, time.Now(), nutzungListenLaenge) if err != nil { s.serverError(w, "Suchen ohne Treffer konnten nicht geladen werden", err) return } daten := usageData{ // Ein Projekt ohne Einträge und Schreibvorgänge fehlt in der map — // dann liefert alle[project.ID] den Nullwert, also überall 0. Summary: alle[project.ID], WindowDays: store.UsageWindowDays, Untagged: ohneSchlagwort, Missing: insLeere, Keys: keys, CuratedOverrides: zurPruefung, Searches: suchen, EmptySearches: ohneTreffer, } // Die Verweise ins Leere kommen vollständig (die Gesamtzahl ist selbst // eine Aussage), angezeigt werden nur die meistgenannten. if len(daten.Missing) > nutzungListenLaenge { daten.MissingMore = len(daten.Missing) - nutzungListenLaenge daten.Missing = daten.Missing[:nutzungListenLaenge] } s.render(w, r, "nutzung.html", http.StatusOK, pageData{ Title: "Nutzung – " + project.Name, Bereich: "nutzung", Data: daten, }) }