feat: add upload policies, daily limits, and storage quotas
All checks were successful
Build and Publish Docker Image / deploy (push) Successful in 1m8s
All checks were successful
Build and Publish Docker Image / deploy (push) Successful in 1m8s
- Add environment variables to configure anonymous uploads, daily upload caps, and default user storage limits. - Update config loader to parse and validate the new settings. - Implement backend logic to track daily usage and active storage per user. - Update README and `.env.example` to document the new settings and admin panels.
This commit is contained in:
@@ -28,14 +28,12 @@
|
||||
</a>
|
||||
<div class="nav-links">
|
||||
{{if .CurrentUser}}
|
||||
<a class="button button-ghost" href="/app">My files</a>
|
||||
<a class="button button-ghost" href="/account/settings">Account</a>
|
||||
<form action="/logout" method="post" class="inline-form"><button class="button button-outline" type="submit">Logout</button></form>
|
||||
<a class="button button-ghost" href="/api">API</a>
|
||||
<a class="button button-outline" href="/account/settings">My Account</a>
|
||||
{{else}}
|
||||
<a class="button button-ghost" href="/login">Login</a>
|
||||
{{end}}
|
||||
<a class="button button-ghost" href="/api">API</a>
|
||||
<a class="button button-outline" href="/healthz">Health</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
@@ -46,7 +44,7 @@
|
||||
|
||||
<footer class="site-footer">
|
||||
<span>{{.AppName}} · {{.CurrentYear}} · self-hosted</span>
|
||||
<span class="footer-links"><a href="/">Upload</a>{{if .CurrentUser}}<a href="/app">My files</a>{{end}}<a href="/healthz">Health</a></span>
|
||||
<span class="footer-links">{{if .CurrentUser}}<a href="/api">API</a><a href="/account/settings">My Account</a>{{else}}<a href="/login">Login</a><a href="/api">API</a>{{end}}</span>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,18 +1,42 @@
|
||||
{{define "account.html"}}{{template "base" .}}{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<section class="auth-view" aria-labelledby="account-title">
|
||||
<div class="card auth-card">
|
||||
<div class="card-content">
|
||||
<p class="kicker">Account</p>
|
||||
<h1 id="account-title">Settings</h1>
|
||||
<p class="muted-copy">{{.Data.Email}} · {{.Data.Role}}</p>
|
||||
<form class="stack-form" action="/account/password" method="post">
|
||||
<label><span>Current password</span><input type="password" name="current_password" autocomplete="current-password" required></label>
|
||||
<label><span>New password</span><input type="password" name="new_password" autocomplete="new-password" minlength="8" required></label>
|
||||
<button class="button button-primary" type="submit">Update password</button>
|
||||
</form>
|
||||
<p class="muted-copy">Public forgot-password is deferred until SMTP support is added. Admins can generate reset links.</p>
|
||||
<section class="app-shell" aria-labelledby="account-title">
|
||||
<aside class="app-sidebar">
|
||||
<a class="sidebar-link" href="/app">My files</a>
|
||||
<a class="sidebar-link is-active" href="/account/settings">Account settings</a>
|
||||
{{if eq .Data.Role "admin"}}<a class="sidebar-link" href="/admin">Admin</a>{{end}}
|
||||
<form class="sidebar-logout" action="/logout" method="post">
|
||||
<button class="button button-outline" type="submit">Logout</button>
|
||||
</form>
|
||||
</aside>
|
||||
|
||||
<div class="app-main">
|
||||
<div class="admin-header">
|
||||
<div>
|
||||
<p class="kicker">Account</p>
|
||||
<h1 id="account-title">Settings</h1>
|
||||
<p class="muted-copy">{{.Data.Email}} · {{.Data.Role}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-stack">
|
||||
<div class="card settings-panel">
|
||||
<div class="card-content">
|
||||
<div class="table-header">
|
||||
<div>
|
||||
<h2>Password</h2>
|
||||
<p>Update the password for your account.</p>
|
||||
</div>
|
||||
</div>
|
||||
<form class="settings-form settings-form-narrow" action="/account/password" method="post">
|
||||
<label><span>Current password</span><input type="password" name="current_password" autocomplete="current-password" required></label>
|
||||
<label><span>New password</span><input type="password" name="new_password" autocomplete="new-password" minlength="8" required></label>
|
||||
<button class="button button-primary" type="submit">Update password</button>
|
||||
</form>
|
||||
<p class="muted-copy">Public forgot-password is deferred until SMTP support is added. Admins can generate reset links.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
{{define "admin.html"}}{{template "base" .}}{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<section class="admin-view" aria-labelledby="admin-title">
|
||||
<section class="app-shell admin-shell" aria-labelledby="admin-title">
|
||||
<aside class="app-sidebar">
|
||||
<a class="sidebar-link {{if eq .Data.Section "overview"}}is-active{{end}}" href="/admin">Overview</a>
|
||||
<a class="sidebar-link {{if eq .Data.Section "files"}}is-active{{end}}" href="/admin/files">Files</a>
|
||||
<a class="sidebar-link" href="/admin/users">Users</a>
|
||||
<a class="sidebar-link" href="/admin/settings">Settings</a>
|
||||
<a class="sidebar-link" href="/app">My files</a>
|
||||
<form class="sidebar-logout" action="/admin/logout" method="post">
|
||||
<button class="button button-outline" type="submit">Logout</button>
|
||||
</form>
|
||||
</aside>
|
||||
|
||||
<div class="app-main">
|
||||
<div class="admin-header">
|
||||
<div>
|
||||
<p class="kicker">Operator console</p>
|
||||
<h1 id="admin-title">Admin overview</h1>
|
||||
<h1 id="admin-title">{{.Data.PageTitle}}</h1>
|
||||
</div>
|
||||
<form action="/admin/logout" method="post">
|
||||
<a class="button button-outline" href="/admin/users">Users</a>
|
||||
<button class="button button-outline" type="submit">Logout</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="metric-grid">
|
||||
@@ -94,5 +102,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
64
backend/templates/pages/admin_settings.html
Normal file
64
backend/templates/pages/admin_settings.html
Normal file
@@ -0,0 +1,64 @@
|
||||
{{define "admin_settings.html"}}{{template "base" .}}{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<section class="app-shell admin-shell" aria-labelledby="admin-settings-title">
|
||||
<aside class="app-sidebar">
|
||||
<a class="sidebar-link" href="/admin">Overview</a>
|
||||
<a class="sidebar-link" href="/admin/files">Files</a>
|
||||
<a class="sidebar-link" href="/admin/users">Users</a>
|
||||
<a class="sidebar-link is-active" href="/admin/settings">Settings</a>
|
||||
<a class="sidebar-link" href="/app">My files</a>
|
||||
<form class="sidebar-logout" action="/admin/logout" method="post">
|
||||
<button class="button button-outline" type="submit">Logout</button>
|
||||
</form>
|
||||
</aside>
|
||||
|
||||
<div class="app-main">
|
||||
<div class="admin-header">
|
||||
<div>
|
||||
<p class="kicker">Operator console</p>
|
||||
<h1 id="admin-settings-title">{{.Data.PageTitle}}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card admin-table-card">
|
||||
<div class="card-content">
|
||||
<div class="table-header">
|
||||
<div>
|
||||
<h2>Upload policy</h2>
|
||||
<p>Values are stored in megabytes. Admin users bypass these upload caps.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="settings-form" action="/admin/settings" method="post">
|
||||
<label class="checkbox-field">
|
||||
<input type="checkbox" name="anonymous_uploads_enabled" {{if .Data.Settings.AnonymousUploadsEnabled}}checked{{end}}>
|
||||
<span>Allow anonymous uploads</span>
|
||||
</label>
|
||||
<label>
|
||||
<span>Anonymous max upload MB</span>
|
||||
<input name="anonymous_max_upload_mb" value="{{.Data.Settings.AnonymousMaxUploadMB}}" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>Anonymous daily upload MB per IP</span>
|
||||
<input name="anonymous_daily_upload_mb" value="{{.Data.Settings.AnonymousDailyUploadMB}}" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>User daily upload MB</span>
|
||||
<input name="user_daily_upload_mb" value="{{.Data.Settings.UserDailyUploadMB}}" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>Default user storage MB</span>
|
||||
<input name="default_user_storage_mb" value="{{.Data.Settings.DefaultUserStorageMB}}" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>Usage retention days</span>
|
||||
<input type="number" name="usage_retention_days" min="1" value="{{.Data.Settings.UsageRetentionDays}}" required>
|
||||
</label>
|
||||
<button class="button button-primary" type="submit">Save settings</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
@@ -1,15 +1,23 @@
|
||||
{{define "admin_users.html"}}{{template "base" .}}{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<section class="admin-view" aria-labelledby="admin-users-title">
|
||||
<section class="app-shell admin-shell" aria-labelledby="admin-users-title">
|
||||
<aside class="app-sidebar">
|
||||
<a class="sidebar-link" href="/admin">Overview</a>
|
||||
<a class="sidebar-link" href="/admin/files">Files</a>
|
||||
<a class="sidebar-link is-active" href="/admin/users">Users</a>
|
||||
<a class="sidebar-link" href="/admin/settings">Settings</a>
|
||||
<a class="sidebar-link" href="/app">My files</a>
|
||||
<form class="sidebar-logout" action="/admin/logout" method="post">
|
||||
<button class="button button-outline" type="submit">Logout</button>
|
||||
</form>
|
||||
</aside>
|
||||
|
||||
<div class="app-main">
|
||||
<div class="admin-header">
|
||||
<div>
|
||||
<p class="kicker">Operator console</p>
|
||||
<h1 id="admin-users-title">Users</h1>
|
||||
</div>
|
||||
<div class="result-actions">
|
||||
<a class="button button-outline" href="/admin">Overview</a>
|
||||
<a class="button button-outline" href="/admin/files">Files</a>
|
||||
<h1 id="admin-users-title">{{.Data.PageTitle}}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -37,7 +45,7 @@
|
||||
<div class="table-header"><h2>Users</h2><p>Disable accounts or create reset links.</p></div>
|
||||
<div class="admin-table-wrap">
|
||||
<table class="admin-table">
|
||||
<thead><tr><th>User</th><th>Email</th><th>Role</th><th>Status</th><th>Joined</th><th>Actions</th></tr></thead>
|
||||
<thead><tr><th>User</th><th>Email</th><th>Role</th><th>Status</th><th>Storage</th><th>Today</th><th>Joined</th><th>Actions</th></tr></thead>
|
||||
<tbody>
|
||||
{{range .Data.Users}}
|
||||
<tr>
|
||||
@@ -45,6 +53,8 @@
|
||||
<td>{{.Email}}</td>
|
||||
<td>{{.Role}}</td>
|
||||
<td><span class="badge">{{.Status}}</span></td>
|
||||
<td>{{.StorageUsed}} / {{.StorageQuota}}</td>
|
||||
<td>{{.DailyUsed}}</td>
|
||||
<td>{{.CreatedAt}}</td>
|
||||
<td class="table-actions">
|
||||
{{if eq .Status "disabled"}}
|
||||
@@ -53,15 +63,20 @@
|
||||
<form action="/admin/users/{{.ID}}/disable" method="post"><button class="button button-danger" type="submit">Disable</button></form>
|
||||
{{end}}
|
||||
<form action="/admin/users/{{.ID}}/reset" method="post"><button class="button button-outline" type="submit">Reset link</button></form>
|
||||
<form action="/admin/users/{{.ID}}/quota" method="post">
|
||||
<input class="compact-input" name="storage_quota_mb" placeholder="Quota MB">
|
||||
<button class="button button-outline" type="submit">Quota</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr><td colspan="6">No users yet.</td></tr>
|
||||
<tr><td colspan="8">No users yet.</td></tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
<a class="sidebar-link is-active" href="/app">Dashboard</a>
|
||||
<a class="sidebar-link" href="/account/settings">Settings</a>
|
||||
{{if eq .Data.User.Role "admin"}}<a class="sidebar-link" href="/admin">Admin</a>{{end}}
|
||||
<form class="sidebar-logout" action="/logout" method="post">
|
||||
<button class="button button-outline" type="submit">Logout</button>
|
||||
</form>
|
||||
<form class="collection-create" action="/app/collections" method="post">
|
||||
<label>
|
||||
<span>New collection</span>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</span>
|
||||
<span class="drop-title">Drop files to upload</span>
|
||||
<span class="drop-copy">or click to browse</span>
|
||||
<span class="drop-meta">{{if .Data.IsAdmin}}Admin upload: no file size limit{{else}}Max file size: {{.Data.MaxUploadSize}}{{end}} · Links expire in 7 days</span>
|
||||
<span class="drop-meta">Max file size: {{.Data.MaxUploadSize}} · {{.Data.LimitSummary}}</span>
|
||||
<input id="file-input" name="file" type="file" multiple>
|
||||
</label>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user