266 lines
8.0 KiB
Plaintext
266 lines
8.0 KiB
Plaintext
package components
|
|
|
|
import "fmt"
|
|
|
|
// Layout is the base HTML layout for the admin panel
|
|
templ Layout(title string, username string) {
|
|
<!DOCTYPE html>
|
|
<html lang="en" class="dark">
|
|
<head>
|
|
<meta charset="UTF-8"/>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
<title>{ title }</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"/>
|
|
</head>
|
|
<body class="bg-gray-900 min-h-screen text-gray-100">
|
|
{ children... }
|
|
<script>
|
|
// Tailwind config for custom styles
|
|
tailwind.config = {
|
|
darkMode: 'class',
|
|
theme: {
|
|
extend: {
|
|
colors: {
|
|
primary: {
|
|
DEFAULT: '#3b82f6',
|
|
dark: '#2563eb',
|
|
},
|
|
accent: {
|
|
DEFAULT: '#8b5cf6',
|
|
dark: '#7c3aed',
|
|
},
|
|
surface: {
|
|
DEFAULT: '#1f2937',
|
|
dark: '#111827',
|
|
lighter: '#374151',
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
}
|
|
|
|
// Header renders the admin header with navigation
|
|
templ Header(username string) {
|
|
<header class="bg-surface-dark border-b border-gray-700 px-6 py-4 mb-6">
|
|
<div class="max-w-7xl mx-auto flex justify-between items-center">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 bg-gradient-to-br from-primary to-accent rounded-lg flex items-center justify-center">
|
|
<i class="fas fa-database text-white text-lg"></i>
|
|
</div>
|
|
<div>
|
|
<h1 class="text-xl font-bold text-white">ZFS Backup</h1>
|
|
<p class="text-xs text-gray-400">Admin Panel</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center gap-4">
|
|
<div class="flex items-center gap-2 text-gray-300">
|
|
<i class="fas fa-user-circle text-lg"></i>
|
|
<span class="text-sm">{ username }</span>
|
|
</div>
|
|
<button
|
|
class="px-4 py-2 bg-red-500/10 hover:bg-red-500/20 text-red-400 rounded-lg transition-colors flex items-center gap-2"
|
|
onclick="logout()"
|
|
>
|
|
<i class="fas fa-sign-out-alt"></i>
|
|
<span>Logout</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
}
|
|
|
|
// StatsCard renders a single statistics card
|
|
templ StatsCard(title string, value string) {
|
|
<div class="bg-surface rounded-xl p-6 border border-gray-700 hover:border-primary/50 transition-colors">
|
|
<div class="flex items-center justify-between mb-2">
|
|
<span class="text-gray-400 text-sm">{ title }</span>
|
|
<i class={ "fas " + statsIcon(title) + " text-primary" }></i>
|
|
</div>
|
|
<div class="text-3xl font-bold text-white">{ value }</div>
|
|
</div>
|
|
}
|
|
|
|
// statsIcon returns the Font Awesome icon for a stats card
|
|
func statsIcon(title string) string {
|
|
switch title {
|
|
case "Clients":
|
|
return "fa-users"
|
|
case "Total Snapshots":
|
|
return "fa-camera"
|
|
case "Total Storage":
|
|
return "fa-hard-drive"
|
|
default:
|
|
return "fa-chart-bar"
|
|
}
|
|
}
|
|
|
|
// TabButton renders a tab navigation button
|
|
templ TabButton(id string, label string, active bool) {
|
|
<button
|
|
class={ "px-5 py-2.5 rounded-lg font-medium transition-all flex items-center gap-2", tabButtonClass(active) }
|
|
data-tab={ id }
|
|
>
|
|
<i class={ "fas " + tabIcon(id) }></i>
|
|
{ label }
|
|
</button>
|
|
}
|
|
|
|
// tabIcon returns the Font Awesome icon for a tab
|
|
func tabIcon(id string) string {
|
|
switch id {
|
|
case "clients":
|
|
return "fa-users"
|
|
case "snapshots":
|
|
return "fa-images"
|
|
case "admins":
|
|
return "fa-user-shield"
|
|
default:
|
|
return "fa-folder"
|
|
}
|
|
}
|
|
|
|
// tabButtonClass returns the CSS class for a tab button
|
|
func tabButtonClass(active bool) string {
|
|
if active {
|
|
return "bg-primary text-white shadow-lg shadow-primary/25"
|
|
}
|
|
return "bg-surface text-gray-400 hover:bg-surface-lighter hover:text-white"
|
|
}
|
|
|
|
// Modal renders a modal dialog
|
|
templ Modal(id string, title string) {
|
|
<div id={ id } class="fixed inset-0 bg-black/70 backdrop-blur-sm hidden items-center justify-center z-50" data-modal-id={ id }>
|
|
<div class="bg-surface rounded-2xl max-w-lg w-full max-h-[90vh] overflow-y-auto border border-gray-700 shadow-2xl modal-content">
|
|
<div class="flex justify-between items-center p-6 border-b border-gray-700">
|
|
<h3 class="text-lg font-semibold text-white">{ title }</h3>
|
|
<button
|
|
type="button"
|
|
class="w-8 h-8 flex items-center justify-center rounded-lg text-gray-400 hover:text-white hover:bg-gray-700 transition-colors modal-close"
|
|
data-modal-id={ id }
|
|
>
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
<div class="p-6">
|
|
{ children... }
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
// FormInput renders a form input field
|
|
templ FormInput(id string, label string, inputType string, placeholder string, required bool) {
|
|
<div class="mb-4">
|
|
<label for={ id } class="block text-sm text-gray-400 mb-2">{ label }</label>
|
|
<input
|
|
type={ inputType }
|
|
id={ id }
|
|
name={ id }
|
|
placeholder={ placeholder }
|
|
if required { required }
|
|
class="w-full px-4 py-3 bg-surface-dark border border-gray-600 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"
|
|
/>
|
|
</div>
|
|
}
|
|
|
|
// FormSelect renders a form select field
|
|
templ FormSelect(id string, label string, options []SelectOption) {
|
|
<div class="mb-4">
|
|
<label for={ id } class="block text-sm text-gray-400 mb-2">{ label }</label>
|
|
<select
|
|
id={ id }
|
|
name={ id }
|
|
class="w-full px-4 py-3 bg-surface-dark border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all"
|
|
>
|
|
for _, opt := range options {
|
|
<option value={ opt.Value } if opt.Selected { selected }>{ opt.Label }</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
}
|
|
|
|
// SelectOption represents an option in a select field
|
|
type SelectOption struct {
|
|
Value string
|
|
Label string
|
|
Selected bool
|
|
}
|
|
|
|
// FormCheckbox renders a form checkbox
|
|
templ FormCheckbox(id string, label string, checked bool) {
|
|
<div class="mb-4 flex items-center gap-3">
|
|
<input
|
|
type="checkbox"
|
|
id={ id }
|
|
name={ id }
|
|
if checked { checked }
|
|
class="w-5 h-5 bg-surface-dark border-gray-600 rounded text-primary focus:ring-primary focus:ring-2"
|
|
/>
|
|
<label for={ id } class="text-sm text-gray-300">{ label }</label>
|
|
</div>
|
|
}
|
|
|
|
// Button renders a styled button
|
|
templ Button(label string, variant string) {
|
|
<button
|
|
class={ "px-4 py-2 rounded-lg font-medium transition-all flex items-center gap-2 " + buttonVariantClass(variant) }
|
|
>
|
|
{ label }
|
|
</button>
|
|
}
|
|
|
|
// buttonVariantClass returns the CSS class for a button variant
|
|
func buttonVariantClass(variant string) string {
|
|
switch variant {
|
|
case "primary":
|
|
return "bg-primary hover:bg-primary-dark text-white shadow-lg shadow-primary/25"
|
|
case "danger":
|
|
return "bg-red-500 hover:bg-red-600 text-white shadow-lg shadow-red-500/25"
|
|
case "success":
|
|
return "bg-emerald-500 hover:bg-emerald-600 text-white shadow-lg shadow-emerald-500/25"
|
|
case "warning":
|
|
return "bg-amber-500 hover:bg-amber-600 text-white shadow-lg shadow-amber-500/25"
|
|
case "purple":
|
|
return "bg-purple-500 hover:bg-purple-600 text-white shadow-lg shadow-purple-500/25"
|
|
case "orange":
|
|
return "bg-orange-500 hover:bg-orange-600 text-white shadow-lg shadow-orange-500/25"
|
|
default:
|
|
return "bg-gray-500 hover:bg-gray-600 text-white"
|
|
}
|
|
}
|
|
|
|
// Badge renders a status badge
|
|
templ Badge(text string, variant string) {
|
|
<span class={ "px-3 py-1 rounded-full text-xs font-medium " + badgeVariantClass(variant) }>
|
|
{ text }
|
|
</span>
|
|
}
|
|
|
|
// badgeVariantClass returns the CSS class for a badge variant
|
|
func badgeVariantClass(variant string) string {
|
|
switch variant {
|
|
case "success":
|
|
return "bg-emerald-500/20 text-emerald-400"
|
|
case "danger":
|
|
return "bg-red-500/20 text-red-400"
|
|
case "info":
|
|
return "bg-blue-500/20 text-blue-400"
|
|
case "warning":
|
|
return "bg-amber-500/20 text-amber-400"
|
|
default:
|
|
return "bg-gray-500/20 text-gray-400"
|
|
}
|
|
}
|
|
|
|
// ProgressBar renders a progress bar
|
|
templ ProgressBar(percent float64) {
|
|
<div class="w-full h-2 bg-gray-700 rounded-full overflow-hidden">
|
|
<div class="h-full bg-gradient-to-r from-primary to-accent transition-all" style={ fmt.Sprintf("width: %.1f%%", percent) }></div>
|
|
</div>
|
|
}
|