feat(storage): add S3 backend support and advanced upload limits
- Introduce S3-compatible storage backend support using minio-go. - Add configuration options for local storage limits, box limits, and rate limiting. - Implement storage backend selection (local vs S3) for anonymous and registered users. - Add an `/admin/storage` management interface. - Update documentation and environment examples with the new configuration variables.
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
</nav>
|
||||
<hr class="sidebar-sep">
|
||||
<form class="sidebar-logout" action="/logout" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
<button class="button button-outline" type="submit">Sign out</button>
|
||||
</form>
|
||||
</aside>
|
||||
@@ -33,6 +34,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<form class="settings-form settings-form-narrow" action="/account/password" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
<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>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<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="/admin/storage">Storage</a>
|
||||
</nav>
|
||||
<hr class="sidebar-sep">
|
||||
<nav class="sidebar-nav">
|
||||
@@ -15,6 +16,7 @@
|
||||
</nav>
|
||||
<hr class="sidebar-sep">
|
||||
<form class="sidebar-logout" action="/admin/logout" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
<button class="button button-outline" type="submit">Sign out</button>
|
||||
</form>
|
||||
</aside>
|
||||
@@ -96,6 +98,7 @@
|
||||
<td class="table-actions">
|
||||
<a class="button button-outline" href="/admin/boxes/{{.ID}}/view">View</a>
|
||||
<form action="/admin/boxes/{{.ID}}/delete" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||
<button class="button button-danger" type="submit">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<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="/admin/storage">Storage</a>
|
||||
</nav>
|
||||
<hr class="sidebar-sep">
|
||||
<nav class="sidebar-nav">
|
||||
@@ -15,6 +16,7 @@
|
||||
</nav>
|
||||
<hr class="sidebar-sep">
|
||||
<form class="sidebar-logout" action="/admin/logout" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
<button class="button button-outline" type="submit">Sign out</button>
|
||||
</form>
|
||||
</aside>
|
||||
@@ -37,6 +39,7 @@
|
||||
</div>
|
||||
|
||||
<form class="settings-form" action="/admin/settings" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
<div class="settings-section">
|
||||
<h3 class="settings-section-title">Anonymous uploads</h3>
|
||||
<label class="checkbox-field">
|
||||
@@ -51,6 +54,26 @@
|
||||
<span>Daily cap per IP (MB)</span>
|
||||
<input name="anonymous_daily_upload_mb" value="{{.Data.Settings.AnonymousDailyUploadMB}}" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>Daily boxes per IP</span>
|
||||
<input type="number" name="anonymous_daily_boxes" min="1" value="{{.Data.Settings.AnonymousDailyBoxes}}" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>Active boxes per IP</span>
|
||||
<input type="number" name="anonymous_active_boxes" min="1" value="{{.Data.Settings.AnonymousActiveBoxes}}" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>Max expiration (days)</span>
|
||||
<input type="number" name="anonymous_max_days" min="1" value="{{.Data.Settings.AnonymousMaxDays}}" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>Anonymous storage backend</span>
|
||||
<select name="anonymous_storage_backend" required>
|
||||
{{range .Data.Storage}}
|
||||
{{if or .Config.Enabled (eq $.Data.Settings.AnonymousStorageBackend .Config.ID)}}<option value="{{.Config.ID}}" {{if eq $.Data.Settings.AnonymousStorageBackend .Config.ID}}selected{{end}}>{{.Config.Name}} ({{.Config.ID}})</option>{{end}}
|
||||
{{end}}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
@@ -67,6 +90,38 @@
|
||||
<span>Usage retention (days)</span>
|
||||
<input type="number" name="usage_retention_days" min="1" value="{{.Data.Settings.UsageRetentionDays}}" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>Daily boxes</span>
|
||||
<input type="number" name="user_daily_boxes" min="1" value="{{.Data.Settings.UserDailyBoxes}}" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>Active boxes</span>
|
||||
<input type="number" name="user_active_boxes" min="1" value="{{.Data.Settings.UserActiveBoxes}}" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>Max expiration (days)</span>
|
||||
<input type="number" name="user_max_days" min="1" value="{{.Data.Settings.UserMaxDays}}" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>User storage backend</span>
|
||||
<select name="user_storage_backend" required>
|
||||
{{range .Data.Storage}}
|
||||
{{if or .Config.Enabled (eq $.Data.Settings.UserStorageBackend .Config.ID)}}<option value="{{.Config.ID}}" {{if eq $.Data.Settings.UserStorageBackend .Config.ID}}selected{{end}}>{{.Config.Name}} ({{.Config.ID}})</option>{{end}}
|
||||
{{end}}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>Local storage max (GB)</span>
|
||||
<input name="local_storage_max_gb" value="{{.Data.Settings.LocalStorageMaxGB}}" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>Short-window requests</span>
|
||||
<input type="number" name="short_window_requests" min="1" value="{{.Data.Settings.ShortWindowRequests}}" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>Short-window seconds</span>
|
||||
<input type="number" name="short_window_seconds" min="1" value="{{.Data.Settings.ShortWindowSeconds}}" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button class="button button-primary" type="submit">Save settings</button>
|
||||
|
||||
124
backend/templates/pages/admin_storage.html
Normal file
124
backend/templates/pages/admin_storage.html
Normal file
@@ -0,0 +1,124 @@
|
||||
{{define "admin_storage.html"}}{{template "base" .}}{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<section class="app-shell admin-shell" aria-labelledby="admin-storage-title">
|
||||
<aside class="app-sidebar">
|
||||
<nav class="sidebar-nav">
|
||||
<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" href="/admin/settings">Settings</a>
|
||||
<a class="sidebar-link is-active" href="/admin/storage">Storage</a>
|
||||
</nav>
|
||||
<hr class="sidebar-sep">
|
||||
<nav class="sidebar-nav"><a class="sidebar-link" href="/app">My Files</a></nav>
|
||||
<hr class="sidebar-sep">
|
||||
<form class="sidebar-logout" action="/admin/logout" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
<button class="button button-outline" type="submit">Sign out</button>
|
||||
</form>
|
||||
</aside>
|
||||
|
||||
<div class="app-main">
|
||||
<div class="admin-header">
|
||||
<div>
|
||||
<p class="kicker">Operator console</p>
|
||||
<h1 id="admin-storage-title">{{.Data.PageTitle}}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if .Data.Error}}<p class="form-error">{{.Data.Error}}</p>{{end}}
|
||||
|
||||
<div class="card admin-table-card">
|
||||
<div class="card-content">
|
||||
<div class="table-header">
|
||||
<div>
|
||||
<h2>Storage backends</h2>
|
||||
<p>Local storage is always available. S3-compatible backends stay private behind Warpbox routes.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admin-table-wrap">
|
||||
<table class="admin-table">
|
||||
<thead><tr><th>Name</th><th>Type</th><th>Status</th><th>Usage</th><th>Details</th><th>Actions</th></tr></thead>
|
||||
<tbody>
|
||||
{{range .Data.Storage}}
|
||||
<tr>
|
||||
<td>{{.Config.Name}}</td>
|
||||
<td>{{if eq .Config.Provider "contabo"}}Contabo Object Storage{{else if eq .Config.Type "s3"}}S3 bucket{{else}}{{.Config.Type}}{{end}}</td>
|
||||
<td>{{if .Config.Enabled}}Enabled{{else}}Disabled{{end}}</td>
|
||||
<td>{{.UsageLabel}}</td>
|
||||
<td>{{if eq .Config.Type "local"}}{{.Config.LocalPath}}{{else}}{{.Config.Bucket}} @ {{.Config.Endpoint}}{{end}}</td>
|
||||
<td class="table-actions">
|
||||
<form action="/admin/storage/{{.Config.ID}}/test" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||
<button class="button button-outline button-sm" type="submit">Test</button>
|
||||
</form>
|
||||
{{if ne .Config.ID "local"}}
|
||||
<details class="row-edit">
|
||||
<summary class="button button-outline button-sm">Edit</summary>
|
||||
<form action="/admin/storage/{{.Config.ID}}/edit" method="post" class="row-edit-form storage-edit-form">
|
||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||
<label><span>Storage kind</span><select name="provider" data-storage-provider>
|
||||
<option value="s3" {{if ne .Config.Provider "contabo"}}selected{{end}}>S3 bucket</option>
|
||||
<option value="contabo" {{if eq .Config.Provider "contabo"}}selected{{end}}>Contabo Object Storage</option>
|
||||
</select></label>
|
||||
<label><span>Name</span><input name="name" value="{{.Config.Name}}" required></label>
|
||||
<label><span>Endpoint</span><input name="endpoint" value="{{.Config.Endpoint}}" required></label>
|
||||
<label><span>Region</span><input name="region" value="{{.Config.Region}}"></label>
|
||||
<label><span>Bucket / object storage name</span><input name="bucket" value="{{.Config.Bucket}}" required></label>
|
||||
<label><span>Access key</span><input name="access_key" value="{{.Config.AccessKey}}" required></label>
|
||||
<label><span>Secret key</span><input name="secret_key" type="password" placeholder="Leave unchanged"></label>
|
||||
<label class="checkbox-field"><input type="checkbox" name="use_ssl" {{if .Config.UseSSL}}checked{{end}}><span>Use TLS</span></label>
|
||||
<label class="checkbox-field"><input type="checkbox" name="path_style" {{if .Config.PathStyle}}checked{{end}}><span>Path-style lookup</span></label>
|
||||
<button class="button button-primary button-sm" type="submit">Save</button>
|
||||
</form>
|
||||
</details>
|
||||
<form action="/admin/storage/{{.Config.ID}}/disable" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||
<button class="button button-danger button-sm" type="submit" {{if .InUse}}disabled{{end}}>Disable</button>
|
||||
</form>
|
||||
<form action="/admin/storage/{{.Config.ID}}/delete" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||
<button class="button button-danger button-sm" type="submit" {{if .InUse}}disabled{{end}}>Delete</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card admin-table-card">
|
||||
<div class="card-content">
|
||||
<div class="table-header">
|
||||
<div>
|
||||
<h2>Add storage</h2>
|
||||
<p>Choose a provider kind first. Contabo uses S3-compatible access with path-style requests.</p>
|
||||
</div>
|
||||
</div>
|
||||
<form class="settings-form" action="/admin/storage/s3" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
<div class="settings-section">
|
||||
<label><span>Storage kind</span><select name="provider" data-storage-provider>
|
||||
<option value="s3">S3 bucket</option>
|
||||
<option value="contabo">Contabo Object Storage</option>
|
||||
</select></label>
|
||||
<label><span>Name</span><input name="name" placeholder="Bob's bucket" required></label>
|
||||
<label><span>Endpoint</span><input name="endpoint" placeholder="s3.example.com" required></label>
|
||||
<label><span>Region</span><input name="region" placeholder="us-east-1"></label>
|
||||
<label><span>Bucket / object storage name</span><input name="bucket" placeholder="My Main Bucket" required></label>
|
||||
<label><span>Access key</span><input name="access_key" required></label>
|
||||
<label><span>Secret key</span><input name="secret_key" type="password" required></label>
|
||||
<label class="checkbox-field"><input type="checkbox" name="use_ssl" checked><span>Use TLS</span></label>
|
||||
<label class="checkbox-field"><input type="checkbox" name="path_style"><span>Use path-style bucket lookup</span></label>
|
||||
</div>
|
||||
<button class="button button-primary" type="submit">Add bucket</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
117
backend/templates/pages/admin_user_edit.html
Normal file
117
backend/templates/pages/admin_user_edit.html
Normal file
@@ -0,0 +1,117 @@
|
||||
{{define "admin_user_edit.html"}}{{template "base" .}}{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<section class="app-shell admin-shell" aria-labelledby="admin-user-edit-title">
|
||||
<aside class="app-sidebar">
|
||||
<nav class="sidebar-nav">
|
||||
<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="/admin/storage">Storage</a>
|
||||
</nav>
|
||||
<hr class="sidebar-sep">
|
||||
<nav class="sidebar-nav"><a class="sidebar-link" href="/app">My Files</a></nav>
|
||||
<hr class="sidebar-sep">
|
||||
<form class="sidebar-logout" action="/admin/logout" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
<button class="button button-outline" type="submit">Sign out</button>
|
||||
</form>
|
||||
</aside>
|
||||
|
||||
<div class="app-main">
|
||||
<div class="admin-header">
|
||||
<div>
|
||||
<p class="kicker">Operator console</p>
|
||||
<h1 id="admin-user-edit-title">{{.Data.PageTitle}}</h1>
|
||||
<p class="muted-copy">{{.Data.UserEdit.Email}} · {{.Data.UserEdit.Role}}</p>
|
||||
</div>
|
||||
<a class="button button-outline" href="/admin/users">Back to users</a>
|
||||
</div>
|
||||
|
||||
{{if .Data.Error}}<p class="form-error">{{.Data.Error}}</p>{{end}}
|
||||
{{if .Data.LastInviteURL}}
|
||||
<div class="copy-field">
|
||||
<input type="text" value="{{.Data.LastInviteURL}}" readonly id="reset-url-field" aria-label="Reset link">
|
||||
<button class="button button-outline button-sm" type="button"
|
||||
onclick="navigator.clipboard.writeText(document.getElementById('reset-url-field').value).then(()=>{this.textContent='Copied!';setTimeout(()=>this.textContent='Copy',2000)})">Copy reset link</button>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="metric-grid">
|
||||
<article class="metric-card"><span>Storage used</span><strong>{{.Data.UserEdit.StorageUsed}}</strong></article>
|
||||
<article class="metric-card"><span>Uploaded today</span><strong>{{.Data.UserEdit.DailyUsed}}</strong></article>
|
||||
<article class="metric-card"><span>Effective quota</span><strong>{{.Data.UserEdit.EffectiveStorage}}</strong></article>
|
||||
<article class="metric-card"><span>Effective backend</span><strong>{{.Data.UserEdit.EffectiveBackend}}</strong></article>
|
||||
</div>
|
||||
|
||||
<div class="card admin-table-card">
|
||||
<div class="card-content">
|
||||
<div class="table-header">
|
||||
<div>
|
||||
<h2>Identity and limits</h2>
|
||||
<p>Blank limit fields inherit the global user defaults. Storage quota set to 0 means unlimited.</p>
|
||||
</div>
|
||||
</div>
|
||||
<form class="settings-form" action="/admin/users/{{.Data.UserEdit.ID}}/edit" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
<div class="settings-section">
|
||||
<h3 class="settings-section-title">Account</h3>
|
||||
<label><span>Username</span><input name="username" value="{{.Data.UserEdit.Username}}" required></label>
|
||||
<label><span>Email</span><input type="email" name="email" value="{{.Data.UserEdit.Email}}" required></label>
|
||||
<label><span>Role</span><select name="role">
|
||||
<option value="user" {{if eq .Data.UserEdit.Role "user"}}selected{{end}}>User</option>
|
||||
<option value="admin" {{if eq .Data.UserEdit.Role "admin"}}selected{{end}}>Admin</option>
|
||||
</select></label>
|
||||
<label><span>Status</span><select name="status">
|
||||
<option value="active" {{if eq .Data.UserEdit.Status "active"}}selected{{end}}>Active</option>
|
||||
<option value="disabled" {{if eq .Data.UserEdit.Status "disabled"}}selected{{end}}>Disabled</option>
|
||||
</select></label>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h3 class="settings-section-title">Storage</h3>
|
||||
<label>
|
||||
<span>Storage backend</span>
|
||||
<select name="storage_backend_id">
|
||||
<option value="">Inherit global user backend ({{.Data.UserEdit.EffectiveBackend}})</option>
|
||||
{{range .Data.Storage}}
|
||||
{{if or .Config.Enabled (eq $.Data.UserEdit.StorageBackendID .Config.ID)}}<option value="{{.Config.ID}}" {{if eq $.Data.UserEdit.StorageBackendID .Config.ID}}selected{{end}}>{{.Config.Name}} ({{.Config.ID}})</option>{{end}}
|
||||
{{end}}
|
||||
</select>
|
||||
</label>
|
||||
<label><span>Storage quota override (MB)</span><input name="storage_quota_mb" value="{{.Data.UserEdit.StorageQuotaMB}}" placeholder="inherit"></label>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h3 class="settings-section-title">Upload limits</h3>
|
||||
<label><span>Max upload size (MB)</span><input name="max_upload_mb" value="{{.Data.UserEdit.MaxUploadMB}}" placeholder="inherit"></label>
|
||||
<label><span>Daily upload cap (MB)</span><input name="daily_upload_mb" value="{{.Data.UserEdit.DailyUploadMB}}" placeholder="inherit"></label>
|
||||
<label><span>Max expiration (days)</span><input type="number" min="1" name="max_days" value="{{.Data.UserEdit.MaxDays}}" placeholder="inherit"></label>
|
||||
<label><span>Daily boxes</span><input type="number" min="1" name="daily_boxes" value="{{.Data.UserEdit.DailyBoxes}}" placeholder="inherit"></label>
|
||||
<label><span>Active boxes</span><input type="number" min="1" name="active_boxes" value="{{.Data.UserEdit.ActiveBoxes}}" placeholder="inherit"></label>
|
||||
<label><span>Short-window requests</span><input type="number" min="1" name="short_window_requests" value="{{.Data.UserEdit.ShortWindowRequests}}" placeholder="inherit"></label>
|
||||
</div>
|
||||
|
||||
<button class="button button-primary" type="submit">Save user</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card admin-table-card">
|
||||
<div class="card-content">
|
||||
<div class="table-header">
|
||||
<div>
|
||||
<h2>Password reset</h2>
|
||||
<p>Create a copyable reset link for this user.</p>
|
||||
</div>
|
||||
</div>
|
||||
<form action="/admin/users/{{.Data.UserEdit.ID}}/reset?next=edit" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
<button class="button button-outline" type="submit">Generate reset link</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
@@ -8,6 +8,7 @@
|
||||
<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="/admin/storage">Storage</a>
|
||||
</nav>
|
||||
<hr class="sidebar-sep">
|
||||
<nav class="sidebar-nav">
|
||||
@@ -15,6 +16,7 @@
|
||||
</nav>
|
||||
<hr class="sidebar-sep">
|
||||
<form class="sidebar-logout" action="/admin/logout" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
<button class="button button-outline" type="submit">Sign out</button>
|
||||
</form>
|
||||
</aside>
|
||||
@@ -43,6 +45,7 @@
|
||||
</div>
|
||||
{{end}}
|
||||
<form class="inline-controls" action="/admin/invites" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
<label><span>Email</span><input type="email" name="email" required></label>
|
||||
<label><span>Role</span><select name="role"><option value="user">User</option><option value="admin">Admin</option></select></label>
|
||||
<button class="button button-primary" type="submit">Create invite</button>
|
||||
@@ -66,6 +69,7 @@
|
||||
<th>Status</th>
|
||||
<th>Storage</th>
|
||||
<th>Today</th>
|
||||
<th>Storage backend</th>
|
||||
<th>Joined</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
@@ -79,28 +83,29 @@
|
||||
<td><span class="badge {{if eq .Status "active"}}badge-active{{else}}badge-disabled{{end}}">{{.Status}}</span></td>
|
||||
<td>{{.StorageUsed}} / {{.StorageQuota}}</td>
|
||||
<td>{{.DailyUsed}}</td>
|
||||
<td>{{.StorageBackend}}</td>
|
||||
<td>{{.CreatedAt}}</td>
|
||||
<td class="table-actions">
|
||||
{{if eq .Status "disabled"}}
|
||||
<form action="/admin/users/{{.ID}}/disable?disabled=false" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||
<button class="button button-outline button-sm" type="submit">Reactivate</button>
|
||||
</form>
|
||||
{{else}}
|
||||
<form action="/admin/users/{{.ID}}/disable" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||
<button class="button button-danger button-sm" type="submit">Disable</button>
|
||||
</form>
|
||||
{{end}}
|
||||
<form action="/admin/users/{{.ID}}/reset" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||
<button class="button button-outline button-sm" type="submit">Reset link</button>
|
||||
</form>
|
||||
<form class="quota-form" action="/admin/users/{{.ID}}/quota" method="post">
|
||||
<input name="storage_quota_mb" placeholder="Quota MB" title="Override storage quota in MB (leave blank to clear override)">
|
||||
<button class="button button-outline button-sm" type="submit">Set</button>
|
||||
</form>
|
||||
<a class="button button-outline button-sm" href="/admin/users/{{.ID}}/edit">Edit</a>
|
||||
</td>
|
||||
</tr>
|
||||
{{else}}
|
||||
<tr><td colspan="8" class="muted-copy">No users yet.</td></tr>
|
||||
<tr><td colspan="9" class="muted-copy">No users yet.</td></tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<h1 id="auth-title">Create the admin account</h1>
|
||||
<p class="muted-copy">The first user becomes the instance admin. Registration closes after this account is created.</p>
|
||||
<form class="stack-form" action="/register" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
{{if .Data.Error}}<p class="form-error">{{.Data.Error}}</p>{{end}}
|
||||
<label><span>Username</span><input name="username" autocomplete="username" required></label>
|
||||
<label><span>Email</span><input type="email" name="email" autocomplete="email" required></label>
|
||||
@@ -20,6 +21,7 @@
|
||||
<h1 id="auth-title">{{if .Data.IsReset}}Choose a new password{{else}}Create your account{{end}}</h1>
|
||||
{{if .Data.Email}}<p class="muted-copy">{{.Data.Email}}</p>{{end}}
|
||||
<form class="stack-form" action="/invite/{{.Data.Token}}" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
{{if .Data.Error}}<p class="form-error">{{.Data.Error}}</p>{{end}}
|
||||
{{if not .Data.IsReset}}<label><span>Username</span><input name="username" autocomplete="username" required></label>{{end}}
|
||||
<label><span>Password</span><input type="password" name="password" autocomplete="new-password" minlength="8" required></label>
|
||||
@@ -29,6 +31,7 @@
|
||||
<p class="kicker">Account</p>
|
||||
<h1 id="auth-title">Sign in</h1>
|
||||
<form class="stack-form" action="/login" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
{{if .Data.Error}}<p class="form-error">{{.Data.Error}}</p>{{end}}
|
||||
<input type="hidden" name="next" value="{{.Data.ReturnPath}}">
|
||||
<label><span>Email</span><input type="email" name="email" autocomplete="email" required></label>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
</nav>
|
||||
<hr class="sidebar-sep">
|
||||
<form class="sidebar-logout" action="/logout" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
<button class="button button-outline" type="submit">Sign out</button>
|
||||
</form>
|
||||
</aside>
|
||||
@@ -35,6 +36,7 @@
|
||||
<summary class="button button-outline button-sm">+ Collection</summary>
|
||||
<div class="new-collection-body">
|
||||
<form action="/app/collections" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||
<label>
|
||||
<span>Name</span>
|
||||
<input name="name" placeholder="e.g. Projects" required>
|
||||
@@ -73,6 +75,7 @@
|
||||
<details class="row-edit">
|
||||
<summary>Rename</summary>
|
||||
<form action="/app/boxes/{{.ID}}/rename" method="post" class="row-edit-form">
|
||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||
<input name="title" placeholder="New title">
|
||||
<button class="button button-outline button-sm" type="submit">Save</button>
|
||||
</form>
|
||||
@@ -83,6 +86,7 @@
|
||||
<details class="row-edit">
|
||||
<summary>Move</summary>
|
||||
<form action="/app/boxes/{{.ID}}/move" method="post" class="row-edit-form">
|
||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||
<select name="collection_id">
|
||||
<option value="">Unsorted</option>
|
||||
{{range $.Data.Collections}}<option value="{{.ID}}">{{.Name}}</option>{{end}}
|
||||
@@ -97,6 +101,7 @@
|
||||
<td class="table-actions">
|
||||
<a class="button button-outline button-sm" href="{{.URL}}" target="_blank" rel="noopener noreferrer">Open</a>
|
||||
<form action="/app/boxes/{{.ID}}/delete" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||
<button class="button button-danger button-sm" type="submit">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
|
||||
Reference in New Issue
Block a user