feat: add admin console, cleanup, and thumbnail workers

- Implement a token-authenticated admin console at `/admin` with overview metrics and file management.
- Add a background worker to periodically clean up expired boxes based on `WARPBOX_CLEANUP_EVERY`.
- Add a background worker to generate image and video thumbnails based on `WARPBOX_THUMBNAIL_EVERY`.
- Update file storage paths to use `@each@` and `@thumb@` prefixes to separate original files from thumbnails.
- Add severity fields to startup logs and update configuration templates.
This commit is contained in:
2026-05-25 16:52:57 +03:00
parent e12878887c
commit 26619bacbc
28 changed files with 1576 additions and 178 deletions

View File

@@ -0,0 +1,70 @@
{{define "download.html"}}{{template "base" .}}{{end}}
{{define "content"}}
<section class="download-view download-view-wide" aria-labelledby="download-title">
<div class="card download-card">
<div class="card-content">
<div class="file-emblem" aria-hidden="true">
<svg viewBox="0 0 24 24" role="img" focusable="false"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z" /><path d="M14 2v6h6" /></svg>
</div>
<h1 id="download-title">{{if .Data.Locked}}Protected box{{else}}Download files{{end}}</h1>
<p class="download-subtitle">Bucket id: {{.Data.Box.ID}}</p>
{{if .Data.Locked}}
<form class="unlock-form" action="/d/{{.Data.Box.ID}}/unlock" method="post">
<label>
<span>Password</span>
<input type="password" name="password" autocomplete="current-password" required>
</label>
<button class="button button-primary" type="submit">Unlock box</button>
</form>
{{if .Data.Obfuscated}}
<p class="download-subtitle">File names, counts, and previews are hidden until the password is entered.</p>
{{end}}
{{end}}
{{if .Data.Files}}
<div class="badge-row">
<span class="badge">Expires {{.Data.ExpiresLabel}}</span>
{{if .Data.MaxDownloads}}<span class="badge">{{.Data.DownloadCount}} / {{.Data.MaxDownloads}} downloads</span>{{end}}
</div>
{{if not .Data.Locked}}
<a class="button button-primary button-wide" href="{{.Data.ZipURL}}">
<svg viewBox="0 0 24 24" role="img" focusable="false" aria-hidden="true"><path d="M12 3v12m0 0 4-4m-4 4-4-4M5 21h14" /></svg>
Download zip
</a>
{{end}}
<div class="view-toolbar" aria-label="File view options">
<button class="button button-outline is-active" type="button" data-view-button="list">List</button>
<button class="button button-outline" type="button" data-view-button="thumbs">Thumbnails</button>
<button class="button button-outline" type="button" data-preview-images>Preview images only</button>
</div>
<div class="download-list file-browser is-list" data-file-browser>
{{range .Data.Files}}
<article class="download-item file-card" data-kind="{{.PreviewKind}}">
<a class="thumb-link" href="{{.URL}}" aria-label="Preview {{.Name}}">
<img src="{{.ThumbnailURL}}" alt="" loading="lazy">
</a>
<a class="file-main" href="{{.URL}}">
<strong>{{.Name}}</strong>
<small>{{.Size}} · {{.ContentType}}</small>
</a>
{{if not $.Data.Locked}}
<a class="button button-outline" href="{{.DownloadURL}}">
<svg viewBox="0 0 24 24" role="img" focusable="false" aria-hidden="true"><path d="M12 3v12m0 0 4-4m-4 4-4-4M5 21h14" /></svg>
Download
</a>
{{end}}
</article>
{{end}}
</div>
{{else if not .Data.Locked}}
<p class="download-subtitle">{{.Data.ExpiresLabel}}</p>
{{end}}
</div>
</div>
</section>
{{end}}