Add gallery admin and video media support.
This updates gallery handling to support video playback with generated poster thumbnails, adds authenticated admin upload/delete flows, and improves dev/runtime behavior including reliable thumbnail generation and media-safe response handling. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
156
app/templates/admin.templ
Normal file
156
app/templates/admin.templ
Normal file
@@ -0,0 +1,156 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"technical.kiwi/website/internal/gallery"
|
||||
)
|
||||
|
||||
templ AdminLogin(errMsg string) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>Admin login — Technical Kiwi</title>
|
||||
<link rel="stylesheet" href="/static/style.css"/>
|
||||
</head>
|
||||
<body class="admin-body">
|
||||
<main class="admin-login">
|
||||
<h1>Gallery admin</h1>
|
||||
if errMsg != "" {
|
||||
<div class="alert alert-error" role="alert"><p>{ errMsg }</p></div>
|
||||
}
|
||||
<form method="post" action="/admin/login" class="contact-form">
|
||||
<div class="form-row">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" id="username" name="username" required autocomplete="username"/>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="password" required autocomplete="current-password"/>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Sign in</button>
|
||||
</form>
|
||||
<p class="admin-back"><a href="/">← Back to site</a></p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
templ AdminDashboard(images []gallery.Image, albums []string, flash string) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>Gallery admin — Technical Kiwi</title>
|
||||
<link rel="stylesheet" href="/static/style.css"/>
|
||||
<script src="/static/htmx.min.js" defer></script>
|
||||
</head>
|
||||
<body class="admin-body" hx-boost="false">
|
||||
<header class="admin-header">
|
||||
<h1>Gallery admin</h1>
|
||||
<form method="post" action="/admin/logout">
|
||||
<button type="submit" class="btn btn-ghost">Sign out</button>
|
||||
</form>
|
||||
</header>
|
||||
<main class="admin-main">
|
||||
<div id="admin-flash">
|
||||
if flash != "" {
|
||||
@AdminFlash(flash)
|
||||
}
|
||||
</div>
|
||||
<section class="admin-panel">
|
||||
<h2>Upload image</h2>
|
||||
<form
|
||||
class="contact-form"
|
||||
method="post"
|
||||
action="/admin/upload"
|
||||
enctype="multipart/form-data"
|
||||
hx-post="/admin/upload"
|
||||
hx-target="#admin-images"
|
||||
hx-swap="innerHTML"
|
||||
hx-encoding="multipart/form-data"
|
||||
>
|
||||
<div class="form-row">
|
||||
<label for="album">Album</label>
|
||||
<select id="album" name="album" required>
|
||||
for _, a := range albums {
|
||||
<option value={ a }>{ gallery.AlbumLabel(a) }</option>
|
||||
}
|
||||
<option value="new">+ New album</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="album_new">New album name</label>
|
||||
<input type="text" id="album_new" name="album_new" placeholder="e.g. my-project" pattern="[a-z0-9][a-z0-9_-]{0,63}"/>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="image">JPEG image</label>
|
||||
<input type="file" id="image" name="image" accept="image/jpeg,.jpg,.jpeg" required/>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Upload</button>
|
||||
</form>
|
||||
</section>
|
||||
<section class="admin-panel">
|
||||
<h2>Images ({ fmt.Sprintf("%d", len(images)) })</h2>
|
||||
<div id="admin-images">
|
||||
@AdminImageTable(images)
|
||||
</div>
|
||||
</section>
|
||||
<p class="admin-back"><a href="/">← View public site</a></p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
templ AdminFlash(msg string) {
|
||||
if msg != "" {
|
||||
<div class="alert alert-error" role="alert"><p>{ msg }</p></div>
|
||||
}
|
||||
}
|
||||
|
||||
templ AdminImageTable(images []gallery.Image) {
|
||||
if len(images) == 0 {
|
||||
<p class="gallery-empty">No images yet.</p>
|
||||
} else {
|
||||
<div class="admin-table-wrap">
|
||||
<table class="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Preview</th>
|
||||
<th>Path</th>
|
||||
<th>Album</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
for _, img := range images {
|
||||
<tr>
|
||||
<td class="admin-thumb">
|
||||
if img.ThumbURL != "" {
|
||||
<img src={ img.ThumbURL } alt="" loading="lazy"/>
|
||||
}
|
||||
</td>
|
||||
<td><code>{ img.RelPath }</code></td>
|
||||
<td>{ gallery.AlbumLabel(img.Album) }</td>
|
||||
<td>
|
||||
<form
|
||||
method="post"
|
||||
action="/admin/delete"
|
||||
hx-post="/admin/delete"
|
||||
hx-target="#admin-images"
|
||||
hx-swap="innerHTML"
|
||||
hx-confirm="Delete this image and its thumbnails?"
|
||||
>
|
||||
<input type="hidden" name="path" value={ img.RelPath }/>
|
||||
<button type="submit" class="btn btn-ghost admin-delete">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user