feat(admin): redesign storage backend management UI
All checks were successful
Build and Publish Docker Image / deploy (push) Successful in 1m31s

Implement a new card-based UI for managing storage backends in the admin panel. This update improves the visual presentation and usability of the storage configuration page.

Key changes:
- Added comprehensive CSS styles for storage cards, including status indicators, metadata layouts, and action buttons.
- Updated the storage admin template to render storage configurations as cards with type-specific details (Local, S3, SFTP, SMB, WebDAV).
- Integrated inline actions for testing, editing, disabling, and deleting storage backends.
- Enhanced sidebar link alignment with flexbox.
This commit is contained in:
2026-05-31 04:54:27 +03:00
parent 3423c141be
commit 0503fad9af
11 changed files with 637 additions and 181 deletions

View File

@@ -4,14 +4,14 @@
<section class="app-shell" aria-labelledby="account-title">
<aside class="app-sidebar">
<nav class="sidebar-nav">
<a class="sidebar-link" href="/app">My Files</a>
<a class="sidebar-link is-active" href="/account/settings">Account</a>
{{if eq .Data.Role "admin"}}<a class="sidebar-link" href="/admin">Admin panel</a>{{end}}
<a class="sidebar-link" href="/app">{{template "icon-home-simple" .}}<span>My Files</span></a>
<a class="sidebar-link is-active" href="/account/settings">{{template "icon-user-circle" .}}<span>Account</span></a>
{{if eq .Data.Role "admin"}}<a class="sidebar-link" href="/admin">{{template "icon-dashboard" .}}<span>Admin panel</span></a>{{end}}
</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>
<button class="button button-outline" type="submit">{{template "icon-log-out" .}}<span>Sign out</span></button>
</form>
</aside>

View File

@@ -4,20 +4,20 @@
<section class="app-shell admin-shell" aria-labelledby="admin-title">
<aside class="app-sidebar">
<nav class="sidebar-nav">
<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="/admin/storage">Storage</a>
<a class="sidebar-link {{if eq .Data.Section "overview"}}is-active{{end}}" href="/admin">{{template "icon-dashboard" .}}<span>Overview</span></a>
<a class="sidebar-link {{if eq .Data.Section "files"}}is-active{{end}}" href="/admin/files">{{template "icon-folder" .}}<span>Files</span></a>
<a class="sidebar-link" href="/admin/users">{{template "icon-user-circle" .}}<span>Users</span></a>
<a class="sidebar-link" href="/admin/settings">{{template "icon-settings" .}}<span>Settings</span></a>
<a class="sidebar-link" href="/admin/storage">{{template "icon-database" .}}<span>Storage</span></a>
</nav>
<hr class="sidebar-sep">
<nav class="sidebar-nav">
<a class="sidebar-link" href="/app">My Files</a>
<a class="sidebar-link" href="/app">{{template "icon-home-simple" .}}<span>My Files</span></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>
<button class="button button-outline" type="submit">{{template "icon-log-out" .}}<span>Sign out</span></button>
</form>
</aside>

View File

@@ -4,20 +4,20 @@
<section class="app-shell admin-shell" aria-labelledby="admin-settings-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 is-active" href="/admin/settings">Settings</a>
<a class="sidebar-link" href="/admin/storage">Storage</a>
<a class="sidebar-link" href="/admin">{{template "icon-dashboard" .}}<span>Overview</span></a>
<a class="sidebar-link" href="/admin/files">{{template "icon-folder" .}}<span>Files</span></a>
<a class="sidebar-link" href="/admin/users">{{template "icon-user-circle" .}}<span>Users</span></a>
<a class="sidebar-link is-active" href="/admin/settings">{{template "icon-settings" .}}<span>Settings</span></a>
<a class="sidebar-link" href="/admin/storage">{{template "icon-database" .}}<span>Storage</span></a>
</nav>
<hr class="sidebar-sep">
<nav class="sidebar-nav">
<a class="sidebar-link" href="/app">My Files</a>
<a class="sidebar-link" href="/app">{{template "icon-home-simple" .}}<span>My Files</span></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>
<button class="button button-outline" type="submit">{{template "icon-log-out" .}}<span>Sign out</span></button>
</form>
</aside>

View File

@@ -4,18 +4,20 @@
<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>
<a class="sidebar-link" href="/admin">{{template "icon-dashboard" .}}<span>Overview</span></a>
<a class="sidebar-link" href="/admin/files">{{template "icon-folder" .}}<span>Files</span></a>
<a class="sidebar-link" href="/admin/users">{{template "icon-user-circle" .}}<span>Users</span></a>
<a class="sidebar-link" href="/admin/settings">{{template "icon-settings" .}}<span>Settings</span></a>
<a class="sidebar-link is-active" href="/admin/storage">{{template "icon-database" .}}<span>Storage</span></a>
</nav>
<hr class="sidebar-sep">
<nav class="sidebar-nav"><a class="sidebar-link" href="/app">My Files</a></nav>
<nav class="sidebar-nav">
<a class="sidebar-link" href="/app">{{template "icon-home-simple" .}}<span>My Files</span></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>
<button class="button button-outline" type="submit">{{template "icon-log-out" .}}<span>Sign out</span></button>
</form>
</aside>
@@ -24,126 +26,222 @@
<div>
<p class="kicker">Operator console</p>
<h1 id="admin-storage-title">{{.Data.PageTitle}}</h1>
<p class="muted-copy">Local storage is always active. Remote backends are proxied through Warpbox.</p>
</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. Remote 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 "sftp"}}SFTP{{else if eq .Config.Type "smb"}}Samba{{else if eq .Config.Type "webdav"}}WebDAV{{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 if eq .Config.Type "sftp"}}{{.Config.Username}}@{{.Config.Host}}:{{.Config.RemotePath}}{{else if eq .Config.Type "smb"}}{{.Config.Domain}}\{{.Config.Username}}@{{.Config.Host}}/{{.Config.Share}}:{{.Config.RemotePath}}{{else if eq .Config.Type "webdav"}}{{.Config.Endpoint}}/{{.Config.RemotePath}}{{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 or (eq .Config.Provider "s3") (eq .Config.Provider "")}}selected{{end}}>S3 bucket</option>
<option value="contabo" {{if eq .Config.Provider "contabo"}}selected{{end}}>Contabo Object Storage</option>
<option value="sftp" {{if eq .Config.Provider "sftp"}}selected{{end}}>SFTP</option>
<option value="smb" {{if eq .Config.Provider "smb"}}selected{{end}}>Samba</option>
<option value="webdav" {{if eq .Config.Provider "webdav"}}selected{{end}}>WebDAV</option>
</select></label>
<label><span>Name</span><input name="name" value="{{.Config.Name}}" required></label>
<label data-provider-fields="s3 contabo"><span>Endpoint</span><input name="endpoint" value="{{.Config.Endpoint}}" required></label>
<label data-provider-fields="s3 contabo"><span>Region</span><input name="region" value="{{.Config.Region}}"></label>
<label data-provider-fields="s3 contabo"><span>Bucket / object storage name</span><input name="bucket" value="{{.Config.Bucket}}" required></label>
<label data-provider-fields="s3 contabo"><span>Access key</span><input name="access_key" value="{{.Config.AccessKey}}" required></label>
<label data-provider-fields="s3 contabo"><span>Secret key</span><input name="secret_key" type="password" placeholder="Leave unchanged"></label>
<label class="checkbox-field" data-provider-fields="s3 contabo"><input type="checkbox" name="use_ssl" {{if .Config.UseSSL}}checked{{end}}><span>Use TLS</span></label>
<label class="checkbox-field" data-provider-fields="s3 contabo"><input type="checkbox" name="path_style" {{if .Config.PathStyle}}checked{{end}}><span>Path-style lookup</span></label>
<label data-provider-fields="sftp smb"><span>Host</span><input name="host" value="{{.Config.Host}}" required></label>
<label data-provider-fields="sftp smb"><span>Port</span><input type="number" name="port" min="1" value="{{.Config.Port}}"></label>
<label data-provider-fields="smb"><span>Share</span><input name="share" value="{{.Config.Share}}" required></label>
<label data-provider-fields="smb"><span>Domain</span><input name="domain" value="{{.Config.Domain}}" placeholder="Optional"></label>
<label data-provider-fields="sftp smb webdav"><span>Username</span><input name="username" value="{{.Config.Username}}" required></label>
<label data-provider-fields="sftp smb webdav"><span>Password</span><input name="password" type="password" placeholder="Leave unchanged"></label>
<label data-provider-fields="sftp"><span>Private key</span><textarea name="private_key" rows="4" placeholder="Leave unchanged"></textarea></label>
<label data-provider-fields="sftp"><span>SSH host public key</span><textarea name="host_key" rows="3" placeholder="Optional, leave unchanged">{{.Config.HostKey}}</textarea></label>
<label data-provider-fields="webdav"><span>WebDAV URL</span><input name="endpoint" value="{{.Config.Endpoint}}" placeholder="https://files.example.com/remote.php/dav/files/user"></label>
<label data-provider-fields="sftp smb webdav"><span>Remote path</span><input name="remote_path" value="{{.Config.RemotePath}}" placeholder="/srv/warpbox"></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="storage-stack">
<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. SFTP is useful for a server or NAS you control.</p>
{{range .Data.Storage}}
<div class="storage-card {{if eq .Config.ID "local"}}is-local{{end}}" data-storage-id="{{.Config.ID}}">
<div class="storage-card-header">
<div class="storage-card-identity">
<div class="storage-card-icon">
{{if eq .Config.Type "local"}}{{template "icon-hard-drive" $}}
{{else if eq .Config.Type "sftp"}}{{template "icon-database" $}}
{{else if eq .Config.Type "smb"}}{{template "icon-folder" $}}
{{else if eq .Config.Type "webdav"}}{{template "icon-cloud-sync" $}}
{{else}}{{template "icon-cloud-upload" $}}{{end}}
</div>
<div>
<strong class="storage-card-name">{{.Config.Name}}</strong>
<div class="storage-card-meta">
<span class="badge">{{if eq .Config.Provider "contabo"}}Contabo{{else if eq .Config.Type "sftp"}}SFTP{{else if eq .Config.Type "smb"}}Samba{{else if eq .Config.Type "webdav"}}WebDAV{{else if eq .Config.Type "s3"}}S3{{else if eq .Config.Type "local"}}Local files{{else}}{{.Config.Type}}{{end}}</span>
{{if eq .Config.ID "local"}}<span class="badge">Required</span>
{{else if .Config.Enabled}}<span class="badge badge-active">Enabled</span>
{{else}}<span class="badge badge-disabled">Disabled</span>{{end}}
{{if .UsageLabel}}<span class="storage-card-usage">{{.UsageLabel}}</span>{{end}}
</div>
</div>
</div>
<div class="storage-card-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"}}
<button class="button button-outline button-sm storage-edit-trigger" type="button">Edit</button>
{{if .Config.Enabled}}
<form action="/admin/storage/{{.Config.ID}}/disable" method="post">
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
<button class="button button-outline button-sm" type="submit" {{if .InUse}}disabled title="Backend is in use"{{end}}>Disable</button>
</form>
{{end}}
<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 title="Backend is in use"{{end}}>Delete</button>
</form>
{{end}}
</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>
<option value="sftp">SFTP</option>
<option value="smb">Samba</option>
<option value="webdav">WebDAV</option>
</select></label>
<label><span>Name</span><input name="name" placeholder="Bob's bucket" required></label>
<label data-provider-fields="s3 contabo"><span>Endpoint</span><input name="endpoint" placeholder="s3.example.com" required></label>
<label data-provider-fields="s3 contabo"><span>Region</span><input name="region" placeholder="us-east-1"></label>
<label data-provider-fields="s3 contabo"><span>Bucket / object storage name</span><input name="bucket" placeholder="My Main Bucket" required></label>
<label data-provider-fields="s3 contabo"><span>Access key</span><input name="access_key" required></label>
<label data-provider-fields="s3 contabo"><span>Secret key</span><input name="secret_key" type="password" required></label>
<label class="checkbox-field" data-provider-fields="s3 contabo"><input type="checkbox" name="use_ssl" checked><span>Use TLS</span></label>
<label class="checkbox-field" data-provider-fields="s3 contabo"><input type="checkbox" name="path_style"><span>Use path-style bucket lookup</span></label>
<label data-provider-fields="sftp smb"><span>Host</span><input name="host" placeholder="files.example.com" required></label>
<label data-provider-fields="sftp smb"><span>Port</span><input type="number" name="port" min="1"></label>
<label data-provider-fields="smb"><span>Share</span><input name="share" placeholder="uploads" required></label>
<label data-provider-fields="smb"><span>Domain</span><input name="domain" placeholder="Optional"></label>
<label data-provider-fields="sftp smb webdav"><span>Username</span><input name="username" required></label>
<label data-provider-fields="sftp smb webdav"><span>Password</span><input name="password" type="password"></label>
<label data-provider-fields="sftp"><span>Private key</span><textarea name="private_key" rows="4" placeholder="Optional private key"></textarea></label>
<label data-provider-fields="sftp"><span>SSH host public key</span><textarea name="host_key" rows="3" placeholder="Optional pinned host key"></textarea></label>
<label data-provider-fields="webdav"><span>WebDAV URL</span><input name="endpoint" placeholder="https://files.example.com/webdav"></label>
<label data-provider-fields="sftp smb webdav"><span>Remote path</span><input name="remote_path" placeholder="/srv/warpbox"></label>
{{/* View-mode summary */}}
<div class="storage-card-summary">
{{if eq .Config.Type "local"}}
<div class="storage-detail"><span>Path</span><code>{{.Config.LocalPath}}</code></div>
{{else if or (eq .Config.Type "s3") (eq .Config.Provider "contabo")}}
{{if .Config.Endpoint}}<div class="storage-detail"><span>Endpoint</span><span>{{.Config.Endpoint}}</span></div>{{end}}
{{if .Config.Bucket}}<div class="storage-detail"><span>Bucket</span><span>{{.Config.Bucket}}</span></div>{{end}}
{{if .Config.Region}}<div class="storage-detail"><span>Region</span><span>{{.Config.Region}}</span></div>{{end}}
{{if .Config.AccessKey}}<div class="storage-detail"><span>Access key</span><span>{{.Config.AccessKey}}</span></div>{{end}}
{{else if eq .Config.Type "sftp"}}
{{if .Config.Host}}<div class="storage-detail"><span>Host</span><span>{{.Config.Host}}{{if .Config.Port}}:{{.Config.Port}}{{end}}</span></div>{{end}}
{{if .Config.Username}}<div class="storage-detail"><span>Username</span><span>{{.Config.Username}}</span></div>{{end}}
{{if .Config.RemotePath}}<div class="storage-detail"><span>Remote path</span><span>{{.Config.RemotePath}}</span></div>{{end}}
{{else if eq .Config.Type "smb"}}
{{if .Config.Host}}<div class="storage-detail"><span>Host</span><span>{{if .Config.Domain}}{{.Config.Domain}}\{{end}}{{.Config.Username}}@{{.Config.Host}}/{{.Config.Share}}</span></div>{{end}}
{{if .Config.RemotePath}}<div class="storage-detail"><span>Remote path</span><span>{{.Config.RemotePath}}</span></div>{{end}}
{{else if eq .Config.Type "webdav"}}
{{if .Config.Endpoint}}<div class="storage-detail"><span>URL</span><span>{{.Config.Endpoint}}</span></div>{{end}}
{{if .Config.Username}}<div class="storage-detail"><span>Username</span><span>{{.Config.Username}}</span></div>{{end}}
{{if .Config.RemotePath}}<div class="storage-detail"><span>Remote path</span><span>{{.Config.RemotePath}}</span></div>{{end}}
{{end}}
{{if not (.Config.LastTestedAt.IsZero)}}
<div class="storage-detail storage-detail-test {{if .Config.LastTestSuccess}}is-ok{{else}}is-err{{end}}">
<span>Last test</span>
<span>{{.Config.LastTestedAt.Format "Jan 2, 15:04"}} · {{if .Config.LastTestSuccess}}Passed{{else}}{{if .Config.LastTestError}}{{.Config.LastTestError}}{{else}}Failed{{end}}{{end}}</span>
</div>
<button class="button button-primary" type="submit">Add storage</button>
</form>
{{end}}
</div>
{{/* Edit-mode form — hidden via CSS until .is-editing */}}
{{if ne .Config.ID "local"}}
<div class="storage-card-body">
<form action="/admin/storage/{{.Config.ID}}/edit" method="post" class="storage-card-fields">
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
<label><span>Storage kind</span>
<select name="provider" data-storage-provider>
<option value="s3" {{if or (eq .Config.Provider "s3") (eq .Config.Provider "")}}selected{{end}}>S3 bucket</option>
<option value="contabo" {{if eq .Config.Provider "contabo"}}selected{{end}}>Contabo Object Storage</option>
<option value="sftp" {{if eq .Config.Provider "sftp"}}selected{{end}}>SFTP</option>
<option value="smb" {{if eq .Config.Provider "smb"}}selected{{end}}>Samba</option>
<option value="webdav" {{if eq .Config.Provider "webdav"}}selected{{end}}>WebDAV</option>
</select>
</label>
<label><span>Name</span><input name="name" value="{{.Config.Name}}" required></label>
<label data-provider-fields="s3 contabo"><span>Endpoint</span><input name="endpoint" value="{{.Config.Endpoint}}" required></label>
<label data-provider-fields="s3 contabo"><span>Region</span><input name="region" value="{{.Config.Region}}"></label>
<label data-provider-fields="s3 contabo"><span>Bucket</span><input name="bucket" value="{{.Config.Bucket}}" required></label>
<label data-provider-fields="s3 contabo"><span>Access key</span><input name="access_key" value="{{.Config.AccessKey}}" required></label>
<label data-provider-fields="s3 contabo"><span>Secret key</span><input name="secret_key" type="password" placeholder="Leave unchanged"></label>
<label class="checkbox-field" data-provider-fields="s3 contabo"><input type="checkbox" name="use_ssl" {{if .Config.UseSSL}}checked{{end}}><span>Use TLS</span></label>
<label class="checkbox-field" data-provider-fields="s3 contabo"><input type="checkbox" name="path_style" {{if .Config.PathStyle}}checked{{end}}><span>Path-style lookup</span></label>
<label data-provider-fields="sftp smb"><span>Host</span><input name="host" value="{{.Config.Host}}" required></label>
<label data-provider-fields="sftp smb"><span>Port</span><input type="number" name="port" min="1" value="{{.Config.Port}}"></label>
<label data-provider-fields="smb"><span>Share</span><input name="share" value="{{.Config.Share}}" required></label>
<label data-provider-fields="smb"><span>Domain</span><input name="domain" value="{{.Config.Domain}}" placeholder="Optional"></label>
<label data-provider-fields="sftp smb webdav"><span>Username</span><input name="username" value="{{.Config.Username}}" required></label>
<label data-provider-fields="sftp smb webdav"><span>Password</span><input name="password" type="password" placeholder="Leave unchanged"></label>
<label data-provider-fields="sftp"><span>Private key</span><textarea name="private_key" rows="4" placeholder="Leave unchanged"></textarea></label>
<label data-provider-fields="sftp"><span>SSH host key</span><textarea name="host_key" rows="3" placeholder="Optional">{{.Config.HostKey}}</textarea></label>
<label data-provider-fields="webdav"><span>WebDAV URL</span><input name="endpoint" value="{{.Config.Endpoint}}" placeholder="https://files.example.com/webdav"></label>
<label data-provider-fields="sftp smb webdav"><span>Remote path</span><input name="remote_path" value="{{.Config.RemotePath}}" placeholder="/srv/warpbox"></label>
<div class="storage-card-edit-bar">
<button class="button button-primary button-sm" type="submit">Save changes</button>
<button class="button button-outline button-sm storage-cancel-trigger" type="button">Cancel</button>
</div>
</form>
</div>
{{end}}
</div>
{{end}}
{{/* Add storage section */}}
<div class="storage-add-section">
<div class="storage-add-controls">
<button class="button button-outline storage-add-trigger" type="button">
{{template "icon-plus-circle" .}}
<span>Add storage</span>
</button>
</div>
<div class="storage-type-picker" hidden>
<p class="muted-copy" style="margin:0 0 0.75rem">Choose a backend type</p>
<div class="storage-type-grid">
<button class="storage-type-option" type="button" data-provider="s3">
{{template "icon-cloud-upload" .}}
<strong>S3 Bucket</strong>
<span>Generic S3-compatible object storage</span>
</button>
<button class="storage-type-option" type="button" data-provider="contabo">
{{template "icon-cloud-upload" .}}
<strong>Contabo Object Storage</strong>
<span>Optimized settings for Contabo COS</span>
</button>
<button class="storage-type-option" type="button" data-provider="sftp">
{{template "icon-database" .}}
<strong>SFTP</strong>
<span>SSH file transfer to a server or NAS</span>
</button>
<button class="storage-type-option" type="button" data-provider="smb">
{{template "icon-folder" .}}
<strong>Samba / SMB</strong>
<span>Windows share or network attached storage</span>
</button>
<button class="storage-type-option" type="button" data-provider="webdav">
{{template "icon-cloud-sync" .}}
<strong>WebDAV</strong>
<span>Nextcloud, ownCloud, or any WebDAV server</span>
</button>
</div>
</div>
<div class="storage-new-card storage-card is-editing" hidden>
<div class="storage-card-header">
<div class="storage-card-identity">
<div class="storage-card-icon storage-new-icon">{{template "icon-cloud-upload" .}}</div>
<div>
<strong class="storage-card-name storage-new-label">New storage backend</strong>
<div class="storage-card-meta">
<span class="badge storage-new-type-badge">S3 bucket</span>
</div>
</div>
</div>
</div>
<div class="storage-card-body">
<form action="/admin/storage/s3" method="post" class="storage-card-fields">
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
<label><span>Storage kind</span>
<select name="provider" data-storage-provider>
<option value="s3">S3 bucket</option>
<option value="contabo">Contabo Object Storage</option>
<option value="sftp">SFTP</option>
<option value="smb">Samba</option>
<option value="webdav">WebDAV</option>
</select>
</label>
<label><span>Name</span><input name="name" placeholder="My storage" required></label>
<label data-provider-fields="s3 contabo"><span>Endpoint</span><input name="endpoint" placeholder="s3.example.com" required></label>
<label data-provider-fields="s3 contabo"><span>Region</span><input name="region" placeholder="us-east-1"></label>
<label data-provider-fields="s3 contabo"><span>Bucket</span><input name="bucket" placeholder="my-bucket" required></label>
<label data-provider-fields="s3 contabo"><span>Access key</span><input name="access_key" required></label>
<label data-provider-fields="s3 contabo"><span>Secret key</span><input name="secret_key" type="password" required></label>
<label class="checkbox-field" data-provider-fields="s3 contabo"><input type="checkbox" name="use_ssl" checked><span>Use TLS</span></label>
<label class="checkbox-field" data-provider-fields="s3 contabo"><input type="checkbox" name="path_style"><span>Path-style lookup</span></label>
<label data-provider-fields="sftp smb"><span>Host</span><input name="host" placeholder="files.example.com" required></label>
<label data-provider-fields="sftp smb"><span>Port</span><input type="number" name="port" min="1"></label>
<label data-provider-fields="smb"><span>Share</span><input name="share" placeholder="uploads" required></label>
<label data-provider-fields="smb"><span>Domain</span><input name="domain" placeholder="Optional"></label>
<label data-provider-fields="sftp smb webdav"><span>Username</span><input name="username" required></label>
<label data-provider-fields="sftp smb webdav"><span>Password</span><input name="password" type="password"></label>
<label data-provider-fields="sftp"><span>Private key</span><textarea name="private_key" rows="4" placeholder="Optional private key"></textarea></label>
<label data-provider-fields="sftp"><span>SSH host key</span><textarea name="host_key" rows="3" placeholder="Optional pinned host key"></textarea></label>
<label data-provider-fields="webdav"><span>WebDAV URL</span><input name="endpoint" placeholder="https://files.example.com/webdav"></label>
<label data-provider-fields="sftp smb webdav"><span>Remote path</span><input name="remote_path" placeholder="/srv/warpbox"></label>
<div class="storage-card-edit-bar">
<button class="button button-primary button-sm" type="submit">Add storage</button>
<button class="button button-outline button-sm storage-new-cancel" type="button">Cancel</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</section>

View File

@@ -4,18 +4,18 @@
<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>
<a class="sidebar-link" href="/admin">{{template "icon-dashboard" .}}<span>Overview</span></a>
<a class="sidebar-link" href="/admin/files">{{template "icon-folder" .}}<span>Files</span></a>
<a class="sidebar-link is-active" href="/admin/users">{{template "icon-user-circle" .}}<span>Users</span></a>
<a class="sidebar-link" href="/admin/settings">{{template "icon-settings" .}}<span>Settings</span></a>
<a class="sidebar-link" href="/admin/storage">{{template "icon-database" .}}<span>Storage</span></a>
</nav>
<hr class="sidebar-sep">
<nav class="sidebar-nav"><a class="sidebar-link" href="/app">My Files</a></nav>
<nav class="sidebar-nav"><a class="sidebar-link" href="/app">{{template "icon-home-simple" .}}<span>My Files</span></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>
<button class="button button-outline" type="submit">{{template "icon-log-out" .}}<span>Sign out</span></button>
</form>
</aside>

View File

@@ -4,20 +4,20 @@
<section class="app-shell admin-shell" aria-labelledby="admin-users-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>
<a class="sidebar-link" href="/admin">{{template "icon-dashboard" .}}<span>Overview</span></a>
<a class="sidebar-link" href="/admin/files">{{template "icon-folder" .}}<span>Files</span></a>
<a class="sidebar-link is-active" href="/admin/users">{{template "icon-user-circle" .}}<span>Users</span></a>
<a class="sidebar-link" href="/admin/settings">{{template "icon-settings" .}}<span>Settings</span></a>
<a class="sidebar-link" href="/admin/storage">{{template "icon-database" .}}<span>Storage</span></a>
</nav>
<hr class="sidebar-sep">
<nav class="sidebar-nav">
<a class="sidebar-link" href="/app">My Files</a>
<a class="sidebar-link" href="/app">{{template "icon-home-simple" .}}<span>My Files</span></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>
<button class="button button-outline" type="submit">{{template "icon-log-out" .}}<span>Sign out</span></button>
</form>
</aside>

View File

@@ -4,14 +4,14 @@
<section class="app-shell" aria-labelledby="dashboard-title">
<aside class="app-sidebar">
<nav class="sidebar-nav">
<a class="sidebar-link is-active" href="/app">My Files</a>
<a class="sidebar-link" href="/account/settings">Account</a>
{{if eq .Data.User.Role "admin"}}<a class="sidebar-link" href="/admin">Admin panel</a>{{end}}
<a class="sidebar-link is-active" href="/app">{{template "icon-home-simple" .}}<span>My Files</span></a>
<a class="sidebar-link" href="/account/settings">{{template "icon-user-circle" .}}<span>Account</span></a>
{{if eq .Data.User.Role "admin"}}<a class="sidebar-link" href="/admin">{{template "icon-dashboard" .}}<span>Admin panel</span></a>{{end}}
</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>
<button class="button button-outline" type="submit">{{template "icon-log-out" .}}<span>Sign out</span></button>
</form>
</aside>

View File

@@ -0,0 +1,21 @@
{{define "icon-dashboard"}}<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M15 15.8C15 14.0327 12 11 12 11C12 11 9 14.0327 9 15.8C9 17.5673 10.3431 19 12 19C13.6569 19 15 17.5673 15 15.8Z" stroke="currentColor" stroke-width="1.5"/><path d="M12 4L12 8" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M3.5 7.5L6.5 10.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M17.5 10.5L20.5 7.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 17H6" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M18 17H22" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/></svg>{{end}}
{{define "icon-folder"}}<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M2 11V4.6C2 4.26863 2.26863 4 2.6 4H8.77805C8.92127 4 9.05977 4.05124 9.16852 4.14445L12.3315 6.85555C12.4402 6.94876 12.5787 7 12.722 7H21.4C21.7314 7 22 7.26863 22 7.6V11M2 11V19.4C2 19.7314 2.26863 20 2.6 20H21.4C21.7314 20 22 19.7314 22 19.4V11M2 11H22" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/></svg>{{end}}
{{define "icon-user-circle"}}<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M7 18V17C7 14.2386 9.23858 12 12 12V12C14.7614 12 17 14.2386 17 17V18" stroke="currentColor" stroke-linecap="round"/><path d="M12 12C13.6569 12 15 10.6569 15 9C15 7.34315 13.6569 6 12 6C10.3431 6 9 7.34315 9 9C9 10.6569 10.3431 12 12 12Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="1.5"/></svg>{{end}}
{{define "icon-settings"}}<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M19.6224 10.3954L18.5247 7.7448L20 6L18 4L16.2647 5.48295L13.5578 4.36974L12.9353 2H10.981L10.3491 4.40113L7.70441 5.51596L6 4L4 6L5.45337 7.78885L4.3725 10.4463L2 11V13L4.40111 13.6555L5.51575 16.2997L4 18L6 20L7.79116 18.5403L10.397 19.6123L11 22H13L13.6045 19.6132L16.2551 18.5155C16.6969 18.8313 18 20 18 20L20 18L18.5159 16.2494L19.6139 13.598L21.9999 12.9772L22 11L19.6224 10.3954Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/></svg>{{end}}
{{define "icon-database"}}<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M5 12V18C5 18 5 21 12 21C19 21 19 18 19 18V12" stroke="currentColor" stroke-width="1.5"/><path d="M5 6V12C5 12 5 15 12 15C19 15 19 12 19 12V6" stroke="currentColor" stroke-width="1.5"/><path d="M12 3C19 3 19 6 19 6C19 6 19 9 12 9C5 9 5 6 5 6C5 6 5 3 12 3Z" stroke="currentColor" stroke-width="1.5"/></svg>{{end}}
{{define "icon-home-simple"}}<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M17 21H7C4.79086 21 3 19.2091 3 17V10.7076C3 9.30887 3.73061 8.01175 4.92679 7.28679L9.92679 4.25649C11.2011 3.48421 12.7989 3.48421 14.0732 4.25649L19.0732 7.28679C20.2694 8.01175 21 9.30887 21 10.7076V17C21 19.2091 19.2091 21 17 21Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M9 17H15" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/></svg>{{end}}
{{define "icon-log-out"}}<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M12 12H19M19 12L16 15M19 12L16 9" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M19 6V5C19 3.89543 18.1046 3 17 3H7C5.89543 3 5 3.89543 5 5V19C5 20.1046 5.89543 21 7 21H17C18.1046 21 19 20.1046 19 19V18" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/></svg>{{end}}
{{define "icon-hard-drive"}}<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M10 17.01L10.01 16.9989" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M6 17.01L6.01 16.9989" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M2 13V20.4C2 20.7314 2.26863 21 2.6 21H21.4C21.7314 21 22 20.7314 22 20.4V13M2 13H22M2 13L4.87172 3.42759C4.94786 3.1738 5.18145 3 5.44642 3H18.5536C18.8185 3 19.0521 3.1738 19.1283 3.42759L22 13" stroke="currentColor" stroke-width="1.5"/></svg>{{end}}
{{define "icon-cloud-upload"}}<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M12 22V13M12 13L15.5 16.5M12 13L8.5 16.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M20 17.6073C21.4937 17.0221 23 15.6889 23 13C23 9 19.6667 8 18 8C18 6 18 2 12 2C6 2 6 6 6 8C4.33333 8 1 9 1 13C1 15.6889 2.50628 17.0221 4 17.6073" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/></svg>{{end}}
{{define "icon-cloud-sync"}}<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M20 17.6073C21.4937 17.0221 23 15.6889 23 13C23 9 19.6667 8 18 8C18 6 18 2 12 2C6 2 6 6 6 8C4.33333 8 1 9 1 13C1 15.6889 2.50628 17.0221 4 17.6073" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M7.58059 19.4874L9.34836 21.2552C10.9105 22.8173 13.4431 22.8173 15.0052 21.2552L15.3588 20.9016" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M7.93413 21.9623L7.58058 19.4874L10.0554 19.841L7.93413 21.9623Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M16.2981 16.9016L14.5303 15.1339C12.9682 13.5718 10.4355 13.5718 8.87345 15.1339L8.51989 15.4874" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M15.9445 14.4268L16.2981 16.9017L13.8232 16.5481L15.9445 14.4268Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/></svg>{{end}}
{{define "icon-plus-circle"}}<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M8 12H12M16 12H12M12 12V8M12 12V16" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/><path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/></svg>{{end}}