278 lines
8.1 KiB
Plaintext
278 lines
8.1 KiB
Plaintext
package views
|
|
|
|
import (
|
|
"fmt"
|
|
"git.32bit.cafe/32bitcafe/guestbook/internal/forms"
|
|
"git.32bit.cafe/32bitcafe/guestbook/internal/models"
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type AdminStat struct {
|
|
WebsiteCount int64
|
|
UserCount int64
|
|
CommentCount int64
|
|
}
|
|
|
|
templ adminBase(title string, data CommonData) {
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<title>{ title } - webweav.ing</title>
|
|
<meta charset="UTF-8"/>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
<meta name="htmx-config" content={ `{"includeIndicatorStyles":false}` }/>
|
|
<link href="/static/css/style.css" rel="stylesheet"/>
|
|
<link href="/static/fontawesome/css/fontawesome.css" rel="stylesheet"/>
|
|
<link href="/static/fontawesome/css/solid.css" rel="stylesheet"/>
|
|
<script src="/static/js/htmx.min.js"></script>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<a href="/">Back to webweav.ing</a>
|
|
</header>
|
|
<main>
|
|
{ children... }
|
|
</main>
|
|
@commonFooter()
|
|
</body>
|
|
</html>
|
|
}
|
|
|
|
templ adminSidebar() {
|
|
<nav aria-label="Admin panel navigation">
|
|
<div>
|
|
<section>
|
|
<h3>Administration</h3>
|
|
<ul role="list">
|
|
<li><a href="/admin">Dashboard</a></li>
|
|
<li><a href="/admin/users">Users</a></li>
|
|
<li><a href="/admin/websites">Websites</a></li>
|
|
</ul>
|
|
</section>
|
|
</div>
|
|
</nav>
|
|
}
|
|
|
|
templ AdminPanelLandingView(title string, data CommonData, stats AdminStat) {
|
|
@adminBase(title, data) {
|
|
<div id="dashboard">
|
|
@adminSidebar()
|
|
<div>
|
|
<section class="admin-section">
|
|
<h1>{ title }</h1>
|
|
<p>Welcome to the admin panel</p>
|
|
</section>
|
|
<section class="admin-flex">
|
|
<section class="admin-section admin-info">
|
|
<h2>Users</h2>
|
|
<p>{ fmt.Sprintf("%d", stats.UserCount) }</p>
|
|
</section>
|
|
<section class="admin-section admin-info">
|
|
<h2>Websites</h2>
|
|
<p>{ fmt.Sprintf("%d", stats.WebsiteCount) }</p>
|
|
</section>
|
|
<section class="admin-section admin-info">
|
|
<h2>Comments</h2>
|
|
<p>{ fmt.Sprintf("%d", stats.CommentCount) }</p>
|
|
</section>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
templ AdminPanelUsersView(title string, data CommonData, users []models.User, pageNum, pageSize, total int64) {
|
|
@adminBase(title, data) {
|
|
<div id="dashboard">
|
|
@adminSidebar()
|
|
<div>
|
|
<section class="admin-section">
|
|
<table>
|
|
<th>Username</th>
|
|
<th>Joined</th>
|
|
<th>Email</th>
|
|
for _, u := range users {
|
|
<tr>
|
|
{{ url := fmt.Sprintf("/admin/users/%s", shortIdToSlug(u.ShortId)) }}
|
|
<td><a href={ templ.URL(url) }>{ u.Username }</a></td>
|
|
<td>{ u.Created.Format(time.RFC3339) }</td>
|
|
<td>{ u.Email }</td>
|
|
</tr>
|
|
}
|
|
</table>
|
|
</section>
|
|
@pagination("/admin/users", pageNum, total, pageSize)
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
templ AdminPanelUserMgmtDetail(csrfToken string, user models.User) {
|
|
<div id="user-info">
|
|
<form>
|
|
<input type="hidden" name="csrf_token" value={ csrfToken }/>
|
|
<section>
|
|
<h3>User Info</h3>
|
|
<div>
|
|
<h5>Username</h5>
|
|
<p>{ user.Username }</p>
|
|
</div>
|
|
<div>
|
|
<h5>Email</h5>
|
|
<p>{ user.Email }</p>
|
|
</div>
|
|
<div>
|
|
<h5>Joined</h5>
|
|
<p>{ user.Created.Format(time.RFC3339) }</p>
|
|
</div>
|
|
</section>
|
|
<section>
|
|
<h3>Groups</h3>
|
|
<ul>
|
|
for _, g := range user.Groups {
|
|
<li>{ fmt.Sprintf("%s", getGroupName(g)) }</li>
|
|
}
|
|
</ul>
|
|
</section>
|
|
<section>
|
|
<h3>Actions</h3>
|
|
{{ getFormUrl := fmt.Sprintf("/admin/users/%s/edit", shortIdToSlug(user.ShortId)) }}
|
|
{{ putBanUrl := fmt.Sprintf("/admin/users/%s/ban", shortIdToSlug(user.ShortId)) }}
|
|
{{ putUnbanUrl := fmt.Sprintf("/admin/users/%s/unban", shortIdToSlug(user.ShortId)) }}
|
|
{{ deleteUrl := fmt.Sprintf("/admin/users/%s", shortIdToSlug(user.ShortId)) }}
|
|
<button type="button" hx-get={ getFormUrl } hx-target="#user-info">Edit</button>
|
|
if user.ID != 1 {
|
|
if user.Banned.IsZero() {
|
|
<button type="button" hx-put={ putBanUrl } hx-confirm="Are you sure you want to ban this user?" hx-target="#user-info" class="danger">Ban</button>
|
|
} else {
|
|
<button type="button" hx-put={ putUnbanUrl } hx-confirm="Are you sure you want to unban this user?" hx-target="#user-info">Unban</button>
|
|
}
|
|
<button type="button" hx-delete={ deleteUrl } hx-confirm="Are you sure you want to delete this user? This is irreversible" class="danger">Delete</button>
|
|
}
|
|
</section>
|
|
</form>
|
|
</div>
|
|
}
|
|
|
|
templ AdminPanelUserMgmtView(title string, data CommonData, user models.User) {
|
|
@adminBase(title, data) {
|
|
<div id="dashboard">
|
|
@adminSidebar()
|
|
@AdminPanelUserMgmtDetail(data.CSRFToken, user)
|
|
</div>
|
|
}
|
|
}
|
|
|
|
templ AdminPanelUserMgmtEditForm(csrfToken string, form forms.AdminUserMgmtForm, user models.User, groups []models.UserGroupId) {
|
|
<div id="user-info">
|
|
<form>
|
|
<input type="hidden" name="csrf_token" value={ csrfToken }/>
|
|
<fieldset>
|
|
<h3>User Info</h3>
|
|
<div>
|
|
<h5>Username</h5>
|
|
<input type="text" name="admin_username" id="username" value={ form.Username } required/>
|
|
</div>
|
|
<div>
|
|
<h5>Email</h5>
|
|
<input type="text" name="admin_useremail" id="useremail" value={ form.Email } required/>
|
|
</div>
|
|
<div>
|
|
<h5>Joined</h5>
|
|
<p>{ user.Created.Format(time.RFC3339) }</p>
|
|
</div>
|
|
</fieldset>
|
|
<section>
|
|
<fieldset>
|
|
<h3>Groups</h3>
|
|
{{ isAdmin := slices.Contains(user.Groups, models.AdminGroup) }}
|
|
<input type="checkbox" name="admin_usergroup_admin" id="usergroup_admin" checked?={ isAdmin } disabled?={ user.ID == 1 }/>
|
|
<label for="usergroup_admin">Administrator</label>
|
|
<input type="checkbox" name="admin_usergroup_user" id="usergroup_user" checked disabled/>
|
|
<label for="usergroup_user">User</label>
|
|
</fieldset>
|
|
</section>
|
|
<section>
|
|
<h3>Actions</h3>
|
|
{{ putFormUrl := fmt.Sprintf("/admin/users/%s/edit", shortIdToSlug(user.ShortId)) }}
|
|
{{ getDetailUrl := fmt.Sprintf("/admin/users/%s/detail", shortIdToSlug(user.ShortId)) }}
|
|
<button type="button" hx-put={ putFormUrl } hx-target="#user-info">Save</button>
|
|
<button type="reset" hx-get={ getDetailUrl } hx-target="#user-info">Cancel</button>
|
|
</section>
|
|
</form>
|
|
</div>
|
|
}
|
|
|
|
templ AdminPanelAllWebsitesView(title string, data CommonData, websites []models.Website, pageNum, pageCount, total int64) {
|
|
@adminBase(title, data) {
|
|
<div id="dashboard">
|
|
@adminSidebar()
|
|
<div>
|
|
<section class="admin-section">
|
|
<table>
|
|
<th>Site Name</th>
|
|
<th>Owner</th>
|
|
<th>URL</th>
|
|
<th>Created</th>
|
|
<th>Guestbook</th>
|
|
for _, w := range websites {
|
|
<tr>
|
|
{{ detailUrl := fmt.Sprintf("/admin/websites/%s", shortIdToSlug(w.ShortId)) }}
|
|
<td><a href={ templ.SafeURL(detailUrl) }>{ w.Name }</a></td>
|
|
<td>{ w.AuthorName }</td>
|
|
<td><a href={ templ.SafeURL(w.Url.String()) }>{ w.Url.String() }</a></td>
|
|
<td>{ w.Created.Format(time.RFC1123) }</td>
|
|
{{ gbUrl := fmt.Sprintf("/websites/%s/guestbook", shortIdToSlug(w.ShortId)) }}
|
|
<td><a href={ templ.SafeURL(gbUrl) }>View</a></td>
|
|
</tr>
|
|
}
|
|
</table>
|
|
</section>
|
|
@pagination("/admin/websites", pageNum, total, pageCount)
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
func truncateComment(s string, n int) string {
|
|
words := strings.Fields(s)
|
|
if len(words) < n {
|
|
return s
|
|
}
|
|
truncated := words[:n]
|
|
truncated = append(truncated, "...")
|
|
return strings.Join(truncated, " ")
|
|
}
|
|
|
|
templ AdminPanelWebsiteDetailView(title string, data CommonData, website models.Website, comments []models.GuestbookComment, pageNum, pageAmount, total int64) {
|
|
@adminBase(title, data) {
|
|
<div id="dashboard">
|
|
@adminSidebar()
|
|
<div>
|
|
<section class="admin-section">
|
|
<table>
|
|
<th>Author</th>
|
|
<th>Created</th>
|
|
<th>Email</th>
|
|
<th>Homepage</th>
|
|
<th>Comment</th>
|
|
for _, c := range comments {
|
|
<tr>
|
|
<td>{ c.AuthorName }</td>
|
|
<td>{ c.Created.Format(time.RFC1123) }</td>
|
|
<td>{ c.AuthorEmail }</td>
|
|
<td>{ c.AuthorSite }</td>
|
|
<td>{ truncateComment(c.CommentText, 15) }</td>
|
|
</tr>
|
|
}
|
|
</table>
|
|
</section>
|
|
{{ url := fmt.Sprintf("/admin/websites/%s", shortIdToSlug(website.ShortId)) }}
|
|
@pagination(url, pageNum, total, pageAmount)
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|