feat(admin): implement provider-specific storage configuration pages
Some checks failed
Build and Publish Docker Image / deploy (push) Has been cancelled
Some checks failed
Build and Publish Docker Image / deploy (push) Has been cancelled
Refactor the admin storage backend creation and editing flows to use provider-specific pages (e.g., `/admin/storage/new/sftp`) instead of a single generic form. This ensures only relevant fields are rendered for each storage provider (such as SFTP, S3, or WebDAV). Additionally: - Prevent mutation of the storage provider type during backend edits. - Add comprehensive unit tests for provider-specific rendering, edit validation, and CSRF/admin route protection.
This commit is contained in:
@@ -28,15 +28,36 @@
|
||||
<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>
|
||||
<a class="button button-primary" href="/admin/storage/new">{{template "icon-plus-circle" .}}<span>Add storage</span></a>
|
||||
</div>
|
||||
|
||||
{{if .Data.Notice}}<p class="form-success">{{.Data.Notice}}</p>{{end}}
|
||||
{{if .Data.Error}}<p class="form-error">{{.Data.Error}}</p>{{end}}
|
||||
|
||||
<div class="storage-stack">
|
||||
<div class="storage-ops-grid">
|
||||
<form class="storage-op-card" action="/admin/storage/jobs/cleanup" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
<strong>Run cleanup now</strong>
|
||||
<span>Remove expired boxes and boxes that reached their download limit.</span>
|
||||
<button class="button button-outline button-sm" type="submit">Run cleanup</button>
|
||||
</form>
|
||||
<form class="storage-op-card" action="/admin/storage/jobs/thumbnails" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
<strong>Generate thumbnails now</strong>
|
||||
<span>Scan active boxes and create missing image or video thumbnails.</span>
|
||||
<button class="button button-outline button-sm" type="submit">Generate</button>
|
||||
</form>
|
||||
<form class="storage-op-card" action="/admin/storage/jobs/verify" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
|
||||
<strong>Verify storage now</strong>
|
||||
<span>Test every enabled backend and update its last-test status.</span>
|
||||
<button class="button button-outline button-sm" type="submit">Verify all</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="storage-stack">
|
||||
{{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">
|
||||
@@ -59,12 +80,16 @@
|
||||
</div>
|
||||
|
||||
<div class="storage-card-actions">
|
||||
{{if .CanSpeedTest}}
|
||||
<a class="button button-outline button-sm" href="/admin/storage/{{.Config.ID}}/tests">Testing</a>
|
||||
{{else}}
|
||||
<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>
|
||||
{{end}}
|
||||
{{if ne .Config.ID "local"}}
|
||||
<button class="button button-outline button-sm storage-edit-trigger" type="button">Edit</button>
|
||||
<a class="button button-outline button-sm" href="/admin/storage/{{.Config.ID}}/edit">Edit</a>
|
||||
{{if .Config.Enabled}}
|
||||
<form action="/admin/storage/{{.Config.ID}}/disable" method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{$.CSRFToken}}">
|
||||
@@ -79,7 +104,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{/* 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>
|
||||
@@ -107,141 +131,8 @@
|
||||
</div>
|
||||
{{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>
|
||||
|
||||
Reference in New Issue
Block a user