Add files page
This commit is contained in:
+59
-24
@@ -10,16 +10,24 @@ import (
|
||||
"uncensored-send/internal/config"
|
||||
)
|
||||
|
||||
// adminPage is the one view that shows every object, regardless of who
|
||||
// uploaded it. It exists because "admin" otherwise only means "may delete
|
||||
// anyone's file", with no way to see whose files those are.
|
||||
type adminPage struct {
|
||||
// filesPage lists uploads. One page serves two readers: an admin sees every
|
||||
// object plus what the server as a whole is holding, and a token holder sees
|
||||
// the files uploaded under their own token, which is otherwise information
|
||||
// they have no way to get back.
|
||||
type filesPage struct {
|
||||
page
|
||||
Objects []adminObject
|
||||
Tokens []adminToken
|
||||
Sort string
|
||||
Count int
|
||||
Listed string // bytes held by the files actually listed
|
||||
|
||||
Count int
|
||||
// NeedsToken replaces the listing with an invitation to log in. Uploads
|
||||
// are recorded against the token that made them, so there is nothing to
|
||||
// show someone who has not presented one.
|
||||
NeedsToken bool
|
||||
|
||||
// The rest is the server's own state, and only an admin sees it.
|
||||
Tokens []adminToken
|
||||
Total string
|
||||
Quota string // empty when there is no quota
|
||||
QuotaPct int
|
||||
@@ -79,17 +87,19 @@ func compareExpiry(a, b *time.Time) int {
|
||||
return a.Compare(*b)
|
||||
}
|
||||
|
||||
func (s *Server) handleAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Server) handleFiles(w http.ResponseWriter, r *http.Request) {
|
||||
lim, err := s.limitsFor(r, credential(r))
|
||||
switch {
|
||||
case err != nil:
|
||||
if err != nil {
|
||||
s.fail(w, r, http.StatusUnauthorized, "Unrecognised token.")
|
||||
return
|
||||
case lim.Anonymous():
|
||||
s.fail(w, r, http.StatusUnauthorized, "This page needs an admin token.")
|
||||
return
|
||||
case !lim.Admin:
|
||||
s.fail(w, r, http.StatusForbidden, "That token is not an admin token.")
|
||||
}
|
||||
if lim.Anonymous() {
|
||||
// Not an error: the page exists, it just has nothing to say without a
|
||||
// token. An anonymous upload is not recorded against anyone.
|
||||
s.render(w, http.StatusOK, "files.html", filesPage{
|
||||
page: s.page(r, "Files", false),
|
||||
NeedsToken: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -100,16 +110,23 @@ func (s *Server) handleAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
now := s.now()
|
||||
objects := make([]adminObject, 0, s.store.Count())
|
||||
anonymous := 0
|
||||
anonymous, listed := 0, int64(0)
|
||||
for _, m := range s.store.List() {
|
||||
// Expired objects are logically gone even if the sweeper has not yet
|
||||
// reached them, so they are not listed as though they were still here.
|
||||
if m.Expired(now) {
|
||||
continue
|
||||
}
|
||||
// A token holder sees their own uploads and nothing else. Anonymous
|
||||
// files belong to no token, so they stay with the admins, which is
|
||||
// exactly who may delete them.
|
||||
if !lim.Admin && m.Owner != lim.Name {
|
||||
continue
|
||||
}
|
||||
if m.Owner == "" {
|
||||
anonymous++
|
||||
}
|
||||
listed += m.Size
|
||||
objects = append(objects, adminObject{
|
||||
ID: m.ID,
|
||||
Filename: m.Filename,
|
||||
@@ -127,15 +144,33 @@ func (s *Server) handleAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
slices.SortStableFunc(objects, adminSorts[sortBy])
|
||||
|
||||
data := adminPage{
|
||||
page: s.widePage(r, "Administration"),
|
||||
Objects: objects,
|
||||
Tokens: s.adminTokens(),
|
||||
Sort: sortBy,
|
||||
Count: len(objects),
|
||||
Total: config.FormatBytes(s.store.Total()),
|
||||
Anonymous: anonymous,
|
||||
title := "Your files"
|
||||
if lim.Admin {
|
||||
title = "All files"
|
||||
}
|
||||
// s.page resolves the session cookie, which is the right thing for the
|
||||
// header but not for this body: the page has to describe the credential it
|
||||
// was actually read with, or an admin presenting a bearer token is shown a
|
||||
// plain user's view of a listing that was built for an admin.
|
||||
head := s.widePage(r, title)
|
||||
head.User, head.Admin = lim.Name, lim.Admin
|
||||
|
||||
data := filesPage{
|
||||
page: head,
|
||||
Objects: objects,
|
||||
Sort: sortBy,
|
||||
Count: len(objects),
|
||||
Listed: config.FormatBytes(listed),
|
||||
}
|
||||
if !lim.Admin {
|
||||
s.render(w, http.StatusOK, "files.html", data)
|
||||
return
|
||||
}
|
||||
|
||||
// Everything below is the server's own state rather than anyone's files.
|
||||
data.Tokens = s.adminTokens()
|
||||
data.Total = config.FormatBytes(s.store.Total())
|
||||
data.Anonymous = anonymous
|
||||
if s.cfg.MaxTotalBytes != config.Unlimited {
|
||||
data.Quota = config.FormatSize(s.cfg.MaxTotalBytes)
|
||||
data.QuotaPct = int(min(100, s.store.Total()*100/max(1, s.cfg.MaxTotalBytes)))
|
||||
@@ -143,7 +178,7 @@ func (s *Server) handleAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
if free, ok := freeBytes(s.store.DataDir()); ok {
|
||||
data.FreeDisk = config.FormatBytes(free)
|
||||
}
|
||||
s.render(w, http.StatusOK, "admin.html", data)
|
||||
s.render(w, http.StatusOK, "files.html", data)
|
||||
}
|
||||
|
||||
// adminTokens describes the configured credentials. Only names and limits are
|
||||
|
||||
Reference in New Issue
Block a user