This commit is contained in:
2026-02-13 23:27:54 +01:00
parent 1825b50dec
commit 1e126734b0
13 changed files with 2743 additions and 3 deletions

View File

@@ -0,0 +1,257 @@
package pages
import (
"git.ma-al.com/goc_marek/zfs/internal/server/templates/components"
)
// AdminPage renders the main admin panel
templ AdminPage(username string) {
@components.Layout("ZFS Backup Admin Panel", username) {
<div class="max-w-7xl mx-auto px-6">
@components.Header(username)
<!-- Stats Grid -->
<div id="stats-grid" class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
@components.StatsCard("Clients", "Loading...")
@components.StatsCard("Total Snapshots", "Loading...")
@components.StatsCard("Total Storage", "Loading...")
</div>
<!-- Tabs -->
<div class="flex gap-3 mb-6">
@components.TabButton("clients", "Clients", true)
@components.TabButton("snapshots", "Snapshots", false)
@components.TabButton("admins", "Admins", false)
</div>
<!-- Clients Tab -->
<div id="clients-tab">
<div class="bg-surface rounded-xl border border-gray-700 overflow-hidden">
<div class="p-6 border-b border-gray-700 flex justify-between items-center">
<h3 class="text-lg font-semibold text-white flex items-center gap-2">
<i class="fas fa-users text-primary"></i>
Clients
</h3>
<button
class="px-4 py-2 bg-emerald-500 hover:bg-emerald-600 text-white rounded-lg transition-all flex items-center gap-2 shadow-lg shadow-emerald-500/25"
data-action="show-modal"
data-modal="add-client-modal"
>
<i class="fas fa-plus"></i>
Add Client
</button>
</div>
<div class="p-6 overflow-x-auto">
<table class="w-full">
<thead>
<tr class="border-b border-gray-700">
<th class="text-left py-3 px-4 text-gray-400 font-medium text-sm">Client ID</th>
<th class="text-left py-3 px-4 text-gray-400 font-medium text-sm">Storage Type</th>
<th class="text-left py-3 px-4 text-gray-400 font-medium text-sm">Quota</th>
<th class="text-left py-3 px-4 text-gray-400 font-medium text-sm">Used</th>
<th class="text-left py-3 px-4 text-gray-400 font-medium text-sm">Snapshots</th>
<th class="text-left py-3 px-4 text-gray-400 font-medium text-sm">Status</th>
<th class="text-left py-3 px-4 text-gray-400 font-medium text-sm">Actions</th>
</tr>
</thead>
<tbody id="clients-table"></tbody>
</table>
</div>
</div>
</div>
<!-- Snapshots Tab -->
<div id="snapshots-tab" class="hidden">
<div class="bg-surface rounded-xl border border-gray-700 overflow-hidden">
<div class="p-6 border-b border-gray-700 flex justify-between items-center">
<h3 class="text-lg font-semibold text-white flex items-center gap-2">
<i class="fas fa-images text-primary"></i>
Snapshots
</h3>
<select id="snapshot-client-filter" class="px-4 py-2 bg-surface-dark border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-primary">
<option value="">All Clients</option>
</select>
</div>
<div class="p-6 overflow-x-auto">
<table class="w-full">
<thead>
<tr class="border-b border-gray-700">
<th class="text-left py-3 px-4 text-gray-400 font-medium text-sm">Client</th>
<th class="text-left py-3 px-4 text-gray-400 font-medium text-sm">Snapshot ID</th>
<th class="text-left py-3 px-4 text-gray-400 font-medium text-sm">Timestamp</th>
<th class="text-left py-3 px-4 text-gray-400 font-medium text-sm">Size</th>
<th class="text-left py-3 px-4 text-gray-400 font-medium text-sm">Type</th>
<th class="text-left py-3 px-4 text-gray-400 font-medium text-sm">Actions</th>
</tr>
</thead>
<tbody id="snapshots-table"></tbody>
</table>
</div>
</div>
</div>
<!-- Admins Tab -->
<div id="admins-tab" class="hidden">
<div class="bg-surface rounded-xl border border-gray-700 overflow-hidden">
<div class="p-6 border-b border-gray-700 flex justify-between items-center">
<h3 class="text-lg font-semibold text-white flex items-center gap-2">
<i class="fas fa-user-shield text-primary"></i>
Admin Users
</h3>
<button
class="px-4 py-2 bg-emerald-500 hover:bg-emerald-600 text-white rounded-lg transition-all flex items-center gap-2 shadow-lg shadow-emerald-500/25"
data-action="show-modal"
data-modal="add-admin-modal"
>
<i class="fas fa-plus"></i>
Add Admin
</button>
</div>
<div class="p-6 overflow-x-auto">
<table class="w-full">
<thead>
<tr class="border-b border-gray-700">
<th class="text-left py-3 px-4 text-gray-400 font-medium text-sm">ID</th>
<th class="text-left py-3 px-4 text-gray-400 font-medium text-sm">Username</th>
<th class="text-left py-3 px-4 text-gray-400 font-medium text-sm">Role</th>
<th class="text-left py-3 px-4 text-gray-400 font-medium text-sm">Created</th>
<th class="text-left py-3 px-4 text-gray-400 font-medium text-sm">Actions</th>
</tr>
</thead>
<tbody id="admins-table"></tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Modals -->
@AddClientModal()
@EditClientModal()
@AddAdminModal()
@ChangePasswordModal()
@ClientPasswordModal()
@AdminScripts()
}
}
// AddClientModal renders the add client modal
templ AddClientModal() {
@components.Modal("add-client-modal", "Add New Client") {
<form id="add-client-form">
@components.FormInput("new-client-id", "Client ID", "text", "", true)
@components.FormInput("new-client-apikey", "API Key", "text", "", true)
@components.FormSelect("new-client-storage", "Storage Type", []components.SelectOption{
{Value: "s3", Label: "S3", Selected: true},
{Value: "local", Label: "Local ZFS", Selected: false},
})
@components.FormInput("new-client-dataset", "Target Dataset (for local storage)", "text", "backup/client1", false)
@components.FormInput("new-client-quota", "Quota (GB)", "number", "100", true)
@components.FormCheckbox("new-client-enabled", "Enabled", true)
<h4 class="text-gray-400 text-sm mt-6 mb-3 flex items-center gap-2">
<i class="fas fa-clock-rotate-left"></i>
Rotation Policy
</h4>
<div class="grid grid-cols-2 gap-4">
@components.FormInput("new-client-hourly", "Keep Hourly", "number", "24", false)
@components.FormInput("new-client-daily", "Keep Daily", "number", "7", false)
@components.FormInput("new-client-weekly", "Keep Weekly", "number", "4", false)
@components.FormInput("new-client-monthly", "Keep Monthly", "number", "12", false)
</div>
<button type="submit" class="w-full mt-6 px-4 py-3 bg-emerald-500 hover:bg-emerald-600 text-white rounded-lg transition-all flex items-center justify-center gap-2 shadow-lg shadow-emerald-500/25">
<i class="fas fa-plus"></i>
Create Client
</button>
</form>
}
}
// EditClientModal renders the edit client modal
templ EditClientModal() {
@components.Modal("edit-client-modal", "Edit Client") {
<form id="edit-client-form">
<input type="hidden" id="edit-client-id"/>
@components.FormInput("edit-client-apikey", "New API Key (leave empty to keep current)", "text", "Leave empty to keep current", false)
@components.FormSelect("edit-client-storage", "Storage Type", []components.SelectOption{
{Value: "s3", Label: "S3", Selected: true},
{Value: "local", Label: "Local ZFS", Selected: false},
})
@components.FormInput("edit-client-dataset", "Target Dataset", "text", "", false)
@components.FormInput("edit-client-quota", "Quota (GB)", "number", "", true)
@components.FormCheckbox("edit-client-enabled", "Enabled", false)
<h4 class="text-gray-400 text-sm mt-6 mb-3 flex items-center gap-2">
<i class="fas fa-clock-rotate-left"></i>
Rotation Policy
</h4>
<div class="grid grid-cols-2 gap-4">
@components.FormInput("edit-client-hourly", "Keep Hourly", "number", "", false)
@components.FormInput("edit-client-daily", "Keep Daily", "number", "", false)
@components.FormInput("edit-client-weekly", "Keep Weekly", "number", "", false)
@components.FormInput("edit-client-monthly", "Keep Monthly", "number", "", false)
</div>
<button type="submit" class="w-full mt-6 px-4 py-3 bg-primary hover:bg-primary-dark text-white rounded-lg transition-all flex items-center justify-center gap-2 shadow-lg shadow-primary/25">
<i class="fas fa-save"></i>
Update Client
</button>
</form>
}
}
// AddAdminModal renders the add admin modal
templ AddAdminModal() {
@components.Modal("add-admin-modal", "Add New Admin") {
<form id="add-admin-form">
@components.FormInput("new-admin-username", "Username", "text", "", true)
@components.FormInput("new-admin-password", "Password", "password", "", true)
@components.FormSelect("new-admin-role", "Role", []components.SelectOption{
{Value: "admin", Label: "Admin", Selected: true},
})
<button type="submit" class="w-full mt-6 px-4 py-3 bg-emerald-500 hover:bg-emerald-600 text-white rounded-lg transition-all flex items-center justify-center gap-2 shadow-lg shadow-emerald-500/25">
<i class="fas fa-plus"></i>
Create Admin
</button>
</form>
}
}
// ChangePasswordModal renders the change password modal for admins
templ ChangePasswordModal() {
@components.Modal("change-password-modal", "Change Password") {
<form id="change-password-form">
<input type="hidden" id="change-password-admin-id"/>
@components.FormInput("change-password-username", "Admin Username", "text", "", true)
@components.FormInput("change-password-new", "New Password", "password", "", true)
@components.FormInput("change-password-confirm", "Confirm New Password", "password", "", true)
<button type="submit" class="w-full mt-6 px-4 py-3 bg-primary hover:bg-primary-dark text-white rounded-lg transition-all flex items-center justify-center gap-2 shadow-lg shadow-primary/25">
<i class="fas fa-key"></i>
Change Password
</button>
</form>
}
}
// ClientPasswordModal renders the set client API key modal
templ ClientPasswordModal() {
@components.Modal("client-password-modal", "Set Client API Key") {
<form id="client-password-form">
<input type="hidden" id="client-password-client-id"/>
@components.FormInput("client-password-client-name", "Client ID", "text", "", true)
@components.FormInput("client-password-new", "New API Key", "text", "", true)
@components.FormInput("client-password-confirm", "Confirm API Key", "text", "", true)
<button type="submit" class="w-full mt-6 px-4 py-3 bg-primary hover:bg-primary-dark text-white rounded-lg transition-all flex items-center justify-center gap-2 shadow-lg shadow-primary/25">
<i class="fas fa-key"></i>
Set API Key
</button>
</form>
}
}
// AdminScripts renders the JavaScript for the admin panel
templ AdminScripts() {
<script src="/admin/static/admin.js"></script>
}