258 lines
11 KiB
Plaintext
258 lines
11 KiB
Plaintext
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>
|
|
}
|