feat(admin): add box preview and password bypass for administrators

Introduce an `AdminViewBox` handler and route that allows administrators
to view any box directly. If the box is password-protected, the handler
bypasses the protection by setting an unlock cookie with an unlock token
and logs the bypass event.

Additionally, add CSS and JS foundations for a file context menu and
preview actions in the file browser UI.
This commit is contained in:
2026-05-25 17:05:59 +03:00
parent 26619bacbc
commit bba84d4194
6 changed files with 348 additions and 9 deletions

View File

@@ -77,7 +77,7 @@
{{if .Protected}}<span class="badge">protected</span>{{end}}
</td>
<td class="table-actions">
<a class="button button-outline" href="/d/{{.ID}}">View</a>
<a class="button button-outline" href="/admin/boxes/{{.ID}}/view">View</a>
<form action="/admin/boxes/{{.ID}}/delete" method="post">
<button class="button button-danger" type="submit">Delete</button>
</form>

View File

@@ -44,23 +44,69 @@
<div class="download-list file-browser is-list" data-file-browser>
{{range .Data.Files}}
<article class="download-item file-card" data-kind="{{.PreviewKind}}">
<a class="thumb-link" href="{{.URL}}" aria-label="Preview {{.Name}}">
<article class="download-item file-card" data-kind="{{.PreviewKind}}" data-file-context data-preview-url="{{.URL}}" data-view-url="{{.DownloadURL}}?inline=1" data-download-url="{{.DownloadURL}}" data-file-name="{{.Name}}">
<a class="thumb-link" href="{{.DownloadURL}}?inline=1" aria-label="View {{.Name}}">
<img src="{{.ThumbnailURL}}" alt="" loading="lazy">
</a>
<a class="file-main" href="{{.URL}}">
<a class="file-main" href="{{.DownloadURL}}?inline=1">
<strong>{{.Name}}</strong>
<small>{{.Size}} · {{.ContentType}}</small>
</a>
{{if not $.Data.Locked}}
<a class="button button-outline" href="{{.DownloadURL}}">
<svg viewBox="0 0 24 24" role="img" focusable="false" aria-hidden="true"><path d="M12 3v12m0 0 4-4m-4 4-4-4M5 21h14" /></svg>
Download
</a>
<div class="file-actions">
<a class="button button-outline preview-action" href="{{.DownloadURL}}?inline=1" target="_blank" rel="noopener noreferrer" data-preview-action data-view-label="View" data-copy-label="Copy link">
<svg data-preview-view-icon viewBox="0 0 24 24" role="img" focusable="false" aria-hidden="true"><path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6-10-6-10-6Z" /><circle cx="12" cy="12" r="3" /></svg>
<svg data-preview-copy-icon viewBox="0 0 24 24" role="img" focusable="false" aria-hidden="true" hidden><rect width="14" height="14" x="8" y="8" rx="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg>
<span data-preview-label>View</span>
</a>
<a class="button button-outline" href="{{.DownloadURL}}" download="{{.Name}}">
<svg viewBox="0 0 24 24" role="img" focusable="false" aria-hidden="true"><path d="M12 3v12m0 0 4-4m-4 4-4-4M5 21h14" /></svg>
Download
</a>
</div>
{{end}}
</article>
{{end}}
</div>
{{if not .Data.Locked}}
<div class="context-menu" data-file-context-menu role="menu" aria-label="File actions" hidden>
<div class="context-menu-top">
<small>File actions</small>
<div class="context-menu-icons" aria-label="Quick actions">
<button type="button" role="menuitem" data-context-action="preview" title="Open preview" aria-label="Open preview">
<svg viewBox="0 0 24 24" role="img" focusable="false" aria-hidden="true"><path d="M15 3h6v6" /><path d="M10 14 21 3" /><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></svg>
</button>
<button type="button" role="menuitem" data-context-action="copy-preview" title="Copy preview URL" aria-label="Copy preview URL">
<svg viewBox="0 0 24 24" role="img" focusable="false" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg>
<span data-context-label class="sr-only">Copy</span>
</button>
</div>
</div>
<hr>
<button type="button" role="menuitem" data-context-action="preview">
<svg viewBox="0 0 24 24" role="img" focusable="false" aria-hidden="true"><path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6-10-6-10-6Z" /><circle cx="12" cy="12" r="3" /></svg>
<span data-context-label>Preview</span>
</button>
<button type="button" role="menuitem" data-context-action="view">
<svg viewBox="0 0 24 24" role="img" focusable="false" aria-hidden="true"><path d="M15 3h6v6" /><path d="M10 14 21 3" /><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></svg>
<span data-context-label>View raw file</span>
</button>
<hr>
<button type="button" role="menuitem" data-context-action="copy-preview">
<svg viewBox="0 0 24 24" role="img" focusable="false" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg>
<span data-context-label>Copy Preview</span>
</button>
<button type="button" role="menuitem" data-context-action="copy-download">
<svg viewBox="0 0 24 24" role="img" focusable="false" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" /><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /></svg>
<span data-context-label>Copy Download</span>
</button>
<hr>
<button type="button" role="menuitem" data-context-action="download">
<svg viewBox="0 0 24 24" role="img" focusable="false" aria-hidden="true"><path d="M12 3v12m0 0 4-4m-4 4-4-4M5 21h14" /></svg>
<span data-context-label>Download</span>
</button>
</div>
{{end}}
{{else if not .Data.Locked}}
<p class="download-subtitle">{{.Data.ExpiresLabel}}</p>
{{end}}